Skip to content

Commit cb86303

Browse files
committed
ctfe interpreter: extend provenance so that it can track whether a pointer is immutable
1 parent 85a4bd8 commit cb86303

File tree

38 files changed

+261
-145
lines changed

38 files changed

+261
-145
lines changed

compiler/rustc_codegen_cranelift/src/constant.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ pub(crate) fn codegen_const_value<'tcx>(
126126
}
127127
}
128128
Scalar::Ptr(ptr, _size) => {
129-
let (alloc_id, offset) = ptr.into_parts(); // we know the `offset` is relative
129+
let (prov, offset) = ptr.into_parts(); // we know the `offset` is relative
130+
let alloc_id = prov.alloc_id();
130131
let base_addr = match fx.tcx.global_alloc(alloc_id) {
131132
GlobalAlloc::Memory(alloc) => {
132133
let data_id = data_id_for_alloc_id(
@@ -374,7 +375,8 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant
374375
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()).to_vec();
375376
data.define(bytes.into_boxed_slice());
376377

377-
for &(offset, alloc_id) in alloc.provenance().ptrs().iter() {
378+
for &(offset, prov) in alloc.provenance().ptrs().iter() {
379+
let alloc_id = prov.alloc_id();
378380
let addend = {
379381
let endianness = tcx.data_layout.endian;
380382
let offset = offset.bytes() as usize;

compiler/rustc_codegen_gcc/src/common.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ impl<'gcc, 'tcx> ConstMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
199199
}
200200
}
201201
Scalar::Ptr(ptr, _size) => {
202-
let (alloc_id, offset) = ptr.into_parts();
202+
let (prov, offset) = ptr.into_parts(); // we know the `offset` is relative
203+
let alloc_id = prov.alloc_id();
203204
let base_addr =
204205
match self.tcx.global_alloc(alloc_id) {
205206
GlobalAlloc::Memory(alloc) => {

compiler/rustc_codegen_gcc/src/consts.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ pub fn const_alloc_to_gcc<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, alloc: ConstAl
285285
let pointer_size = dl.pointer_size.bytes() as usize;
286286

287287
let mut next_offset = 0;
288-
for &(offset, alloc_id) in alloc.provenance().ptrs().iter() {
288+
for &(offset, prov) in alloc.provenance().ptrs().iter() {
289+
let alloc_id = prov.alloc_id();
289290
let offset = offset.bytes();
290291
assert_eq!(offset as usize as u64, offset);
291292
let offset = offset as usize;
@@ -313,7 +314,7 @@ pub fn const_alloc_to_gcc<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, alloc: ConstAl
313314

314315
llvals.push(cx.scalar_to_backend(
315316
InterpScalar::from_pointer(
316-
interpret::Pointer::new(alloc_id, Size::from_bytes(ptr_offset)),
317+
interpret::Pointer::new(prov, Size::from_bytes(ptr_offset)),
317318
&cx.tcx,
318319
),
319320
abi::Scalar::Initialized { value: Primitive::Pointer(address_space), valid_range: WrappingRange::full(dl.pointer_size) },

compiler/rustc_codegen_llvm/src/common.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
246246
}
247247
}
248248
Scalar::Ptr(ptr, _size) => {
249-
let (alloc_id, offset) = ptr.into_parts();
250-
let (base_addr, base_addr_space) = match self.tcx.global_alloc(alloc_id) {
249+
let (prov, offset) = ptr.into_parts();
250+
let (base_addr, base_addr_space) = match self.tcx.global_alloc(prov.alloc_id()) {
251251
GlobalAlloc::Memory(alloc) => {
252252
let init = const_alloc_to_llvm(self, alloc);
253253
let alloc = alloc.inner();

compiler/rustc_codegen_llvm/src/consts.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub fn const_alloc_to_llvm<'ll>(cx: &CodegenCx<'ll, '_>, alloc: ConstAllocation<
7272
}
7373

7474
let mut next_offset = 0;
75-
for &(offset, alloc_id) in alloc.provenance().ptrs().iter() {
75+
for &(offset, prov) in alloc.provenance().ptrs().iter() {
7676
let offset = offset.bytes();
7777
assert_eq!(offset as usize as u64, offset);
7878
let offset = offset as usize;
@@ -92,13 +92,10 @@ pub fn const_alloc_to_llvm<'ll>(cx: &CodegenCx<'ll, '_>, alloc: ConstAllocation<
9292
.expect("const_alloc_to_llvm: could not read relocation pointer")
9393
as u64;
9494

95-
let address_space = cx.tcx.global_alloc(alloc_id).address_space(cx);
95+
let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx);
9696

9797
llvals.push(cx.scalar_to_backend(
98-
InterpScalar::from_pointer(
99-
Pointer::new(alloc_id, Size::from_bytes(ptr_offset)),
100-
&cx.tcx,
101-
),
98+
InterpScalar::from_pointer(Pointer::new(prov, Size::from_bytes(ptr_offset)), &cx.tcx),
10299
Scalar::Initialized {
103100
value: Primitive::Pointer(address_space),
104101
valid_range: WrappingRange::full(dl.pointer_size),

compiler/rustc_codegen_ssa/src/mir/operand.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
105105
bug!("from_const: invalid ScalarPair layout: {:#?}", layout);
106106
};
107107
let a = Scalar::from_pointer(
108-
Pointer::new(bx.tcx().reserve_and_set_memory_alloc(data), Size::ZERO),
108+
Pointer::new(bx.tcx().reserve_and_set_memory_alloc(data).into(), Size::ZERO),
109109
&bx.tcx(),
110110
);
111111
let a_llval = bx.scalar_to_backend(

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ pub(super) fn op_to_const<'tcx>(
155155
match immediate {
156156
Left(ref mplace) => {
157157
// We know `offset` is relative to the allocation, so we can use `into_parts`.
158-
let (alloc_id, offset) = mplace.ptr().into_parts();
159-
let alloc_id = alloc_id.expect("cannot have `fake` place fot non-ZST type");
158+
let (prov, offset) = mplace.ptr().into_parts();
159+
let alloc_id = prov.expect("cannot have `fake` place for non-ZST type").alloc_id();
160160
ConstValue::Indirect { alloc_id, offset }
161161
}
162162
// see comment on `let force_as_immediate` above
@@ -178,8 +178,8 @@ pub(super) fn op_to_const<'tcx>(
178178
);
179179
let msg = "`op_to_const` on an immediate scalar pair must only be used on slice references to the beginning of an actual allocation";
180180
// We know `offset` is relative to the allocation, so we can use `into_parts`.
181-
let (alloc_id, offset) = a.to_pointer(ecx).expect(msg).into_parts();
182-
let alloc_id = alloc_id.expect(msg);
181+
let (prov, offset) = a.to_pointer(ecx).expect(msg).into_parts();
182+
let alloc_id = prov.expect(msg).alloc_id();
183183
let data = ecx.tcx.global_alloc(alloc_id).unwrap_memory();
184184
assert!(offset == abi::Size::ZERO, "{}", msg);
185185
let meta = b.to_target_usize(ecx).expect(msg);
@@ -353,7 +353,7 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
353353
let validation =
354354
const_validate_mplace(&ecx, &mplace, is_static, cid.promoted.is_some());
355355

356-
let alloc_id = mplace.ptr().provenance.unwrap();
356+
let alloc_id = mplace.ptr().provenance.unwrap().alloc_id();
357357

358358
// Validation failed, report an error.
359359
if let Err(error) = validation {

compiler/rustc_const_eval/src/const_eval/machine.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
4949
pub(super) num_evaluated_steps: usize,
5050

5151
/// The virtual call stack.
52-
pub(super) stack: Vec<Frame<'mir, 'tcx, AllocId, ()>>,
52+
pub(super) stack: Vec<Frame<'mir, 'tcx>>,
5353

5454
/// We need to make sure consts never point to anything mutable, even recursively. That is
5555
/// relied on for pattern matching on consts with references.
@@ -638,10 +638,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
638638
}
639639

640640
#[inline(always)]
641-
fn expose_ptr(
642-
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
643-
_ptr: Pointer<AllocId>,
644-
) -> InterpResult<'tcx> {
641+
fn expose_ptr(_ecx: &mut InterpCx<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx> {
645642
// This is only reachable with -Zunleash-the-miri-inside-of-you.
646643
throw_unsup_format!("exposing pointers is not possible at compile-time")
647644
}

compiler/rustc_const_eval/src/interpret/eval_context.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ use hir::CRATE_HIR_ID;
77
use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData};
88
use rustc_index::IndexVec;
99
use rustc_middle::mir;
10-
use rustc_middle::mir::interpret::{ErrorHandled, InvalidMetaKind, ReportedErrorInfo};
10+
use rustc_middle::mir::interpret::{
11+
CtfeProvenance, ErrorHandled, InvalidMetaKind, ReportedErrorInfo,
12+
};
1113
use rustc_middle::query::TyCtxtAt;
1214
use rustc_middle::ty::layout::{
1315
self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers,
@@ -20,9 +22,9 @@ use rustc_span::Span;
2022
use rustc_target::abi::{call::FnAbi, Align, HasDataLayout, Size, TargetDataLayout};
2123

2224
use super::{
23-
AllocId, GlobalId, Immediate, InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace,
24-
MemPlaceMeta, Memory, MemoryKind, OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic,
25-
Projectable, Provenance, Scalar, StackPopJump,
25+
GlobalId, Immediate, InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta,
26+
Memory, MemoryKind, OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable,
27+
Provenance, Scalar, StackPopJump,
2628
};
2729
use crate::errors;
2830
use crate::util;
@@ -84,7 +86,7 @@ impl Drop for SpanGuard {
8486
}
8587

8688
/// A stack frame.
87-
pub struct Frame<'mir, 'tcx, Prov: Provenance = AllocId, Extra = ()> {
89+
pub struct Frame<'mir, 'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> {
8890
////////////////////////////////////////////////////////////////////////////////
8991
// Function and callsite information
9092
////////////////////////////////////////////////////////////////////////////////
@@ -156,7 +158,7 @@ pub enum StackPopCleanup {
156158

157159
/// State of a local variable including a memoized layout
158160
#[derive(Clone)]
159-
pub struct LocalState<'tcx, Prov: Provenance = AllocId> {
161+
pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> {
160162
value: LocalValue<Prov>,
161163
/// Don't modify if `Some`, this is only used to prevent computing the layout twice.
162164
/// Avoids computing the layout of locals that are never actually initialized.
@@ -177,7 +179,7 @@ impl<Prov: Provenance> std::fmt::Debug for LocalState<'_, Prov> {
177179
/// This does not store the type of the local; the type is given by `body.local_decls` and can never
178180
/// change, so by not storing here we avoid having to maintain that as an invariant.
179181
#[derive(Copy, Clone, Debug)] // Miri debug-prints these
180-
pub(super) enum LocalValue<Prov: Provenance = AllocId> {
182+
pub(super) enum LocalValue<Prov: Provenance = CtfeProvenance> {
181183
/// This local is not currently alive, and cannot be used at all.
182184
Dead,
183185
/// A normal, live local.

compiler/rustc_const_eval/src/interpret/intern.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use super::validity::RefTracking;
1818
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
1919
use rustc_errors::ErrorGuaranteed;
2020
use rustc_hir as hir;
21-
use rustc_middle::mir::interpret::InterpResult;
21+
use rustc_middle::mir::interpret::{CtfeProvenance, InterpResult};
2222
use rustc_middle::ty::{self, layout::TyAndLayout, Ty};
2323

2424
use rustc_ast::Mutability;
@@ -34,7 +34,7 @@ pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine<
3434
'mir,
3535
'tcx,
3636
MemoryKind = T,
37-
Provenance = AllocId,
37+
Provenance = CtfeProvenance,
3838
ExtraFnVal = !,
3939
FrameExtra = (),
4040
AllocExtra = (),
@@ -135,7 +135,7 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
135135
alloc.mutability = Mutability::Not;
136136
};
137137
// link the alloc id to the actual allocation
138-
leftover_allocations.extend(alloc.provenance().ptrs().iter().map(|&(_, alloc_id)| alloc_id));
138+
leftover_allocations.extend(alloc.provenance().ptrs().iter().map(|&(_, prov)| prov.alloc_id()));
139139
let alloc = tcx.mk_const_alloc(alloc);
140140
tcx.set_alloc_id_memory(alloc_id, alloc);
141141
None
@@ -178,10 +178,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
178178
tcx.struct_tail_erasing_lifetimes(referenced_ty, self.ecx.param_env).kind()
179179
{
180180
let ptr = mplace.meta().unwrap_meta().to_pointer(&tcx)?;
181-
if let Some(alloc_id) = ptr.provenance {
181+
if let Some(prov) = ptr.provenance {
182182
// Explicitly choose const mode here, since vtables are immutable, even
183183
// if the reference of the fat pointer is mutable.
184-
self.intern_shallow(alloc_id, InternMode::Const, None);
184+
self.intern_shallow(prov.alloc_id(), InternMode::Const, None);
185185
} else {
186186
// Validation will error (with a better message) on an invalid vtable pointer.
187187
// Let validation show the error message, but make sure it *does* error.
@@ -191,7 +191,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
191191
}
192192
// Check if we have encountered this pointer+layout combination before.
193193
// Only recurse for allocation-backed pointers.
194-
if let Some(alloc_id) = mplace.ptr().provenance {
194+
if let Some(prov) = mplace.ptr().provenance {
195195
// Compute the mode with which we intern this. Our goal here is to make as many
196196
// statics as we can immutable so they can be placed in read-only memory by LLVM.
197197
let ref_mode = match self.mode {
@@ -234,7 +234,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
234234
InternMode::Const
235235
}
236236
};
237-
match self.intern_shallow(alloc_id, ref_mode, Some(referenced_ty)) {
237+
match self.intern_shallow(prov.alloc_id(), ref_mode, Some(referenced_ty)) {
238238
// No need to recurse, these are interned already and statics may have
239239
// cycles, so we don't want to recurse there
240240
Some(IsStaticOrFn) => {}
@@ -353,7 +353,7 @@ pub fn intern_const_alloc_recursive<
353353
leftover_allocations,
354354
// The outermost allocation must exist, because we allocated it with
355355
// `Memory::allocate`.
356-
ret.ptr().provenance.unwrap(),
356+
ret.ptr().provenance.unwrap().alloc_id(),
357357
base_intern_mode,
358358
Some(ret.layout.ty),
359359
);
@@ -431,7 +431,8 @@ pub fn intern_const_alloc_recursive<
431431
}
432432
let alloc = tcx.mk_const_alloc(alloc);
433433
tcx.set_alloc_id_memory(alloc_id, alloc);
434-
for &(_, alloc_id) in alloc.inner().provenance().ptrs().iter() {
434+
for &(_, prov) in alloc.inner().provenance().ptrs().iter() {
435+
let alloc_id = prov.alloc_id();
435436
if leftover_allocations.insert(alloc_id) {
436437
todo.push(alloc_id);
437438
}
@@ -503,10 +504,11 @@ impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
503504
// `allocate` picks a fresh AllocId that we will associate with its data below.
504505
let dest = self.allocate(layout, MemoryKind::Stack)?;
505506
f(self, &dest.clone().into())?;
506-
let mut alloc = self.memory.alloc_map.remove(&dest.ptr().provenance.unwrap()).unwrap().1;
507+
let mut alloc =
508+
self.memory.alloc_map.remove(&dest.ptr().provenance.unwrap().alloc_id()).unwrap().1;
507509
alloc.mutability = Mutability::Not;
508510
let alloc = self.tcx.mk_const_alloc(alloc);
509-
let alloc_id = dest.ptr().provenance.unwrap(); // this was just allocated, it must have provenance
511+
let alloc_id = dest.ptr().provenance.unwrap().alloc_id(); // this was just allocated, it must have provenance
510512
self.tcx.set_alloc_id_memory(alloc_id, alloc);
511513
Ok(alloc_id)
512514
}

compiler/rustc_const_eval/src/interpret/machine.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ use rustc_target::abi::{Align, Size};
1616
use rustc_target::spec::abi::Abi as CallAbi;
1717

1818
use super::{
19-
AllocBytes, AllocId, AllocKind, AllocRange, Allocation, ConstAllocation, FnArg, Frame, ImmTy,
20-
InterpCx, InterpResult, MPlaceTy, MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance,
19+
AllocBytes, AllocId, AllocKind, AllocRange, Allocation, ConstAllocation, CtfeProvenance, FnArg,
20+
Frame, ImmTy, InterpCx, InterpResult, MPlaceTy, MemoryKind, Misalignment, OpTy, PlaceTy,
21+
Pointer, Provenance,
2122
};
2223

2324
/// Data returned by Machine::stack_pop,
@@ -513,8 +514,8 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
513514
/// A lot of the flexibility above is just needed for `Miri`, but all "compile-time" machines
514515
/// (CTFE and ConstProp) use the same instance. Here, we share that code.
515516
pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
516-
type Provenance = AllocId;
517-
type ProvenanceExtra = ();
517+
type Provenance = CtfeProvenance;
518+
type ProvenanceExtra = (); // FIXME extract the "immutable" bool?
518519

519520
type ExtraFnVal = !;
520521

@@ -567,22 +568,22 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
567568
def_id: DefId,
568569
) -> InterpResult<$tcx, Pointer> {
569570
// Use the `AllocId` associated with the `DefId`. Any actual *access* will fail.
570-
Ok(Pointer::new(ecx.tcx.reserve_and_set_static_alloc(def_id), Size::ZERO))
571+
Ok(Pointer::new(ecx.tcx.reserve_and_set_static_alloc(def_id).into(), Size::ZERO))
571572
}
572573

573574
#[inline(always)]
574575
fn adjust_alloc_base_pointer(
575576
_ecx: &InterpCx<$mir, $tcx, Self>,
576-
ptr: Pointer<AllocId>,
577-
) -> InterpResult<$tcx, Pointer<AllocId>> {
577+
ptr: Pointer<CtfeProvenance>,
578+
) -> InterpResult<$tcx, Pointer<CtfeProvenance>> {
578579
Ok(ptr)
579580
}
580581

581582
#[inline(always)]
582583
fn ptr_from_addr_cast(
583584
_ecx: &InterpCx<$mir, $tcx, Self>,
584585
addr: u64,
585-
) -> InterpResult<$tcx, Pointer<Option<AllocId>>> {
586+
) -> InterpResult<$tcx, Pointer<Option<CtfeProvenance>>> {
586587
// Allow these casts, but make the pointer not dereferenceable.
587588
// (I.e., they behave like transmutation.)
588589
// This is correct because no pointers can ever be exposed in compile-time evaluation.
@@ -592,10 +593,10 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
592593
#[inline(always)]
593594
fn ptr_get_alloc(
594595
_ecx: &InterpCx<$mir, $tcx, Self>,
595-
ptr: Pointer<AllocId>,
596+
ptr: Pointer<CtfeProvenance>,
596597
) -> Option<(AllocId, Size, Self::ProvenanceExtra)> {
597598
// We know `offset` is relative to the allocation, so we can use `into_parts`.
598-
let (alloc_id, offset) = ptr.into_parts();
599-
Some((alloc_id, offset, ()))
599+
let (prov, offset) = ptr.into_parts();
600+
Some((prov.alloc_id(), offset, ()))
600601
}
601602
}

compiler/rustc_const_eval/src/interpret/memory.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ use crate::fluent_generated as fluent;
2222

2323
use super::{
2424
alloc_range, AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckAlignMsg,
25-
CheckInAllocMsg, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, Misalignment, Pointer,
26-
PointerArithmetic, Provenance, Scalar,
25+
CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak,
26+
Misalignment, Pointer, PointerArithmetic, Provenance, Scalar,
2727
};
2828

2929
#[derive(Debug, PartialEq, Copy, Clone)]
@@ -159,9 +159,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
159159
#[inline]
160160
pub fn global_base_pointer(
161161
&self,
162-
ptr: Pointer<AllocId>,
162+
ptr: Pointer<CtfeProvenance>,
163163
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
164-
let alloc_id = ptr.provenance;
164+
let alloc_id = ptr.provenance.alloc_id();
165165
// We need to handle `extern static`.
166166
match self.tcx.try_get_global_alloc(alloc_id) {
167167
Some(GlobalAlloc::Static(def_id)) if self.tcx.is_thread_local_static(def_id) => {

0 commit comments

Comments
 (0)