Skip to content

Commit 849c929

Browse files
committed
move UnsafeCell-in-const check from interning to validation
1 parent 778ddff commit 849c929

File tree

7 files changed

+74
-53
lines changed

7 files changed

+74
-53
lines changed

compiler/rustc_mir/src/const_eval/eval_queries.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr, MemoryExtra};
22
use crate::interpret::eval_nullary_intrinsic;
33
use crate::interpret::{
4-
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, GlobalId, Immediate,
5-
InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, Scalar,
4+
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
5+
Immediate, InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, Scalar,
66
ScalarMaybeUninit, StackPopCleanup,
77
};
88

@@ -376,13 +376,14 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
376376
// https://github.com/rust-lang/rust/issues/67465 is made
377377
if cid.promoted.is_none() {
378378
let mut ref_tracking = RefTracking::new(mplace);
379+
let mut inner = false;
379380
while let Some((mplace, path)) = ref_tracking.todo.pop() {
380-
ecx.const_validate_operand(
381-
mplace.into(),
382-
path,
383-
&mut ref_tracking,
384-
/*may_ref_to_static*/ ecx.memory.extra.can_access_statics,
385-
)?;
381+
let mode = match tcx.static_mutability(cid.instance.def_id()) {
382+
Some(_) => CtfeValidationMode::Regular, // a `static`
383+
None => CtfeValidationMode::Const { inner },
384+
};
385+
ecx.const_validate_operand(mplace.into(), path, &mut ref_tracking, mode)?;
386+
inner = true;
386387
}
387388
}
388389
};

compiler/rustc_mir/src/interpret/intern.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,7 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx>>(
129129
// See const_eval::machine::MemoryExtra::can_access_statics for why
130130
// immutability is so important.
131131

132-
// There are no sensible checks we can do here; grep for `mutable_memory_in_const` to
133-
// find the checks we are doing elsewhere to avoid even getting here for memory
134-
// that "wants" to be mutable.
132+
// Validation will ensure that there is no `UnsafeCell` on an immutable allocation.
135133
alloc.mutability = Mutability::Not;
136134
};
137135
// link the alloc id to the actual allocation
@@ -176,7 +174,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx>> ValueVisitor<'mir
176174
// they caused. It also helps us to find cases where const-checking
177175
// failed to prevent an `UnsafeCell` (but as `ignore_interior_mut_in_const`
178176
// shows that part is not airtight).
179-
mutable_memory_in_const(self.ecx.tcx, "`UnsafeCell`");
177+
//mutable_memory_in_const(self.ecx.tcx, "`UnsafeCell`");
180178
}
181179
// We are crossing over an `UnsafeCell`, we can mutate again. This means that
182180
// References we encounter inside here are interned as pointing to mutable

compiler/rustc_mir/src/interpret/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackP
2424
pub use self::memory::{AllocCheck, FnVal, Memory, MemoryKind};
2525
pub use self::operand::{ImmTy, Immediate, OpTy, Operand};
2626
pub use self::place::{MPlaceTy, MemPlace, MemPlaceMeta, Place, PlaceTy};
27-
pub use self::validity::RefTracking;
27+
pub use self::validity::{RefTracking, CtfeValidationMode};
2828
pub use self::visitor::{MutValueVisitor, ValueVisitor};
2929

3030
crate use self::intrinsics::eval_nullary_intrinsic;

compiler/rustc_mir/src/interpret/validity.rs

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ pub enum PathElem {
113113
DynDowncast,
114114
}
115115

116+
/// Extra things to check for during validation of CTFE results.
117+
pub enum CtfeValidationMode {
118+
/// Regular validation, nothing special happening.
119+
Regular,
120+
/// Validation of a `const`. `inner` says if this is an inner, indirect allocation (as opposed
121+
/// to the top-level const allocation).
122+
Const { inner: bool },
123+
}
124+
116125
/// State for tracking recursive validation of references
117126
pub struct RefTracking<T, PATH = ()> {
118127
pub seen: FxHashSet<T>,
@@ -202,9 +211,9 @@ struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
202211
/// starts must not be changed! `visit_fields` and `visit_array` rely on
203212
/// this stack discipline.
204213
path: Vec<PathElem>,
205-
ref_tracking_for_consts:
206-
Option<&'rt mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>>,
207-
may_ref_to_static: bool,
214+
ref_tracking: Option<&'rt mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>>,
215+
/// `None` indicates this is not validating for CTFE (but for runtime).
216+
ctfe_mode: Option<CtfeValidationMode>,
208217
ecx: &'rt InterpCx<'mir, 'tcx, M>,
209218
}
210219

@@ -418,27 +427,15 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
418427
{ "a dangling {} (use-after-free)", kind },
419428
);
420429
// Recursive checking
421-
if let Some(ref mut ref_tracking) = self.ref_tracking_for_consts {
430+
if let Some(ref mut ref_tracking) = self.ref_tracking {
422431
if let Some(ptr) = ptr {
423432
// not a ZST
424433
// Skip validation entirely for some external statics
425434
let alloc_kind = self.ecx.tcx.get_global_alloc(ptr.alloc_id);
426435
if let Some(GlobalAlloc::Static(did)) = alloc_kind {
427436
assert!(!self.ecx.tcx.is_thread_local_static(did));
428437
assert!(self.ecx.tcx.is_static(did));
429-
if self.may_ref_to_static {
430-
// We skip checking other statics. These statics must be sound by
431-
// themselves, and the only way to get broken statics here is by using
432-
// unsafe code.
433-
// The reasons we don't check other statics is twofold. For one, in all
434-
// sound cases, the static was already validated on its own, and second, we
435-
// trigger cycle errors if we try to compute the value of the other static
436-
// and that static refers back to us.
437-
// We might miss const-invalid data,
438-
// but things are still sound otherwise (in particular re: consts
439-
// referring to statics).
440-
return Ok(());
441-
} else {
438+
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
442439
// See const_eval::machine::MemoryExtra::can_access_statics for why
443440
// this check is so important.
444441
// This check is reachable when the const just referenced the static,
@@ -447,6 +444,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
447444
{ "a {} pointing to a static variable", kind }
448445
);
449446
}
447+
// We skip checking other statics. These statics must be sound by
448+
// themselves, and the only way to get broken statics here is by using
449+
// unsafe code.
450+
// The reasons we don't check other statics is twofold. For one, in all
451+
// sound cases, the static was already validated on its own, and second, we
452+
// trigger cycle errors if we try to compute the value of the other static
453+
// and that static refers back to us.
454+
// We might miss const-invalid data,
455+
// but things are still sound otherwise (in particular re: consts
456+
// referring to statics).
457+
return Ok(());
450458
}
451459
}
452460
// Proceed recursively even for ZST, no reason to skip them!
@@ -504,7 +512,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
504512
let value = self.ecx.read_scalar(value)?;
505513
// NOTE: Keep this in sync with the array optimization for int/float
506514
// types below!
507-
if self.ref_tracking_for_consts.is_some() {
515+
if self.ctfe_mode.is_some() {
508516
// Integers/floats in CTFE: Must be scalar bits, pointers are dangerous
509517
let is_bits = value.check_init().map_or(false, |v| v.is_bits());
510518
if !is_bits {
@@ -723,6 +731,15 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
723731
// Sanity check: `builtin_deref` does not know any pointers that are not primitive.
724732
assert!(op.layout.ty.builtin_deref(true).is_none());
725733

734+
// Special check preventing `UnsafeCell` in constants
735+
if let Some(def) = op.layout.ty.ty_adt_def() {
736+
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true }))
737+
&& Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type()
738+
{
739+
throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
740+
}
741+
}
742+
726743
// Recursively walk the value at its type.
727744
self.walk_value(op)?;
728745

@@ -814,7 +831,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
814831
self.ecx,
815832
ptr,
816833
size,
817-
/*allow_uninit_and_ptr*/ self.ref_tracking_for_consts.is_none(),
834+
/*allow_uninit_and_ptr*/ self.ctfe_mode.is_none(),
818835
) {
819836
// In the happy case, we needn't check anything else.
820837
Ok(()) => {}
@@ -865,16 +882,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
865882
&self,
866883
op: OpTy<'tcx, M::PointerTag>,
867884
path: Vec<PathElem>,
868-
ref_tracking_for_consts: Option<
869-
&mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>,
870-
>,
871-
may_ref_to_static: bool,
885+
ref_tracking: Option<&mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>>,
886+
ctfe_mode: Option<CtfeValidationMode>,
872887
) -> InterpResult<'tcx> {
873888
trace!("validate_operand_internal: {:?}, {:?}", *op, op.layout.ty);
874889

875890
// Construct a visitor
876-
let mut visitor =
877-
ValidityVisitor { path, ref_tracking_for_consts, may_ref_to_static, ecx: self };
891+
let mut visitor = ValidityVisitor { path, ref_tracking, ctfe_mode, ecx: self };
878892

879893
// Try to cast to ptr *once* instead of all the time.
880894
let op = self.force_op_ptr(op).unwrap_or(op);
@@ -902,23 +916,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
902916
/// `ref_tracking` is used to record references that we encounter so that they
903917
/// can be checked recursively by an outside driving loop.
904918
///
905-
/// `may_ref_to_static` controls whether references are allowed to point to statics.
919+
/// `constant` controls whether this must satisfy the rules for constants:
920+
/// - no pointers to statics.
921+
/// - no `UnsafeCell` or non-ZST `&mut`.
906922
#[inline(always)]
907923
pub fn const_validate_operand(
908924
&self,
909925
op: OpTy<'tcx, M::PointerTag>,
910926
path: Vec<PathElem>,
911927
ref_tracking: &mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>,
912-
may_ref_to_static: bool,
928+
ctfe_mode: CtfeValidationMode,
913929
) -> InterpResult<'tcx> {
914-
self.validate_operand_internal(op, path, Some(ref_tracking), may_ref_to_static)
930+
self.validate_operand_internal(op, path, Some(ref_tracking), Some(ctfe_mode))
915931
}
916932

917933
/// This function checks the data at `op` to be runtime-valid.
918934
/// `op` is assumed to cover valid memory if it is an indirect operand.
919935
/// It will error if the bits at the destination do not match the ones described by the layout.
920936
#[inline(always)]
921937
pub fn validate_operand(&self, op: OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
922-
self.validate_operand_internal(op, vec![], None, false)
938+
self.validate_operand_internal(op, vec![], None, None)
923939
}
924940
}

compiler/rustc_mir/src/transform/const_prop.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use rustc_hir::def::DefKind;
99
use rustc_hir::HirId;
1010
use rustc_index::bit_set::BitSet;
1111
use rustc_index::vec::IndexVec;
12-
use rustc_middle::mir::interpret::{InterpResult, Scalar};
1312
use rustc_middle::mir::visit::{
1413
MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
1514
};
@@ -28,9 +27,10 @@ use rustc_trait_selection::traits;
2827

2928
use crate::const_eval::ConstEvalErr;
3029
use crate::interpret::{
31-
self, compile_time_machine, truncate, AllocId, Allocation, ConstValue, Frame, ImmTy, Immediate,
32-
InterpCx, LocalState, LocalValue, MemPlace, Memory, MemoryKind, OpTy, Operand as InterpOperand,
33-
PlaceTy, Pointer, ScalarMaybeUninit, StackPopCleanup,
30+
self, compile_time_machine, truncate, AllocId, Allocation, ConstValue, CtfeValidationMode,
31+
Frame, ImmTy, Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemPlace, Memory,
32+
MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Pointer, Scalar, ScalarMaybeUninit,
33+
StackPopCleanup,
3434
};
3535
use crate::transform::MirPass;
3636

@@ -805,8 +805,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
805805
value,
806806
vec![],
807807
// FIXME: is ref tracking too expensive?
808+
// FIXME: what is the point of ref tracking if we do not even check the tracked refs?
808809
&mut interpret::RefTracking::empty(),
809-
/*may_ref_to_static*/ true,
810+
CtfeValidationMode::Regular,
810811
) {
811812
trace!("validation error, attempt failed: {:?}", e);
812813
return;

src/test/ui/consts/miri_unleashed/mutable_references_err.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ unsafe impl Sync for Meh {}
1313

1414
// the following will never be ok! no interior mut behind consts, because
1515
// all allocs interned here will be marked immutable.
16-
const MUH: Meh = Meh { //~ ERROR: mutable memory (`UnsafeCell`) is not allowed in constant
16+
const MUH: Meh = Meh { //~ ERROR: it is undefined behavior to use this value
1717
x: &UnsafeCell::new(42),
1818
};
1919

@@ -24,7 +24,7 @@ unsafe impl Sync for Synced {}
2424

2525
// Make sure we also catch this behind a type-erased `dyn Trait` reference.
2626
const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) };
27-
//~^ ERROR: mutable memory (`UnsafeCell`) is not allowed in constant
27+
//~^ ERROR: it is undefined behavior to use this value
2828

2929
// Make sure we also catch mutable references.
3030
const BLUNT: &mut i32 = &mut 42;

src/test/ui/consts/miri_unleashed/mutable_references_err.stderr

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
error: mutable memory (`UnsafeCell`) is not allowed in constant
1+
error[E0080]: it is undefined behavior to use this value
22
--> $DIR/mutable_references_err.rs:16:1
33
|
44
LL | / const MUH: Meh = Meh {
55
LL | | x: &UnsafeCell::new(42),
66
LL | | };
7-
| |__^
7+
| |__^ type validation failed: encountered `UnsafeCell` in a `const` at .x.<deref>
8+
|
9+
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
810

9-
error: mutable memory (`UnsafeCell`) is not allowed in constant
11+
error[E0080]: it is undefined behavior to use this value
1012
--> $DIR/mutable_references_err.rs:26:1
1113
|
1214
LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) };
13-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered `UnsafeCell` in a `const` at .<deref>.<dyn-downcast>.x
16+
|
17+
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
1418

1519
error: mutable memory (`&mut`) is not allowed in constant
1620
--> $DIR/mutable_references_err.rs:30:1
@@ -38,3 +42,4 @@ LL | const BLUNT: &mut i32 = &mut 42;
3842

3943
error: aborting due to 3 previous errors; 1 warning emitted
4044

45+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)