@@ -155,8 +155,8 @@ impl CtfeValidationMode {
155155
156156/// State for tracking recursive validation of references
157157pub struct RefTracking<T, PATH = ()> {
158- pub seen: FxHashSet<T>,
159- pub todo: Vec<(T, PATH)>,
158+ seen: FxHashSet<T>,
159+ todo: Vec<(T, PATH)>,
160160}
161161
162162impl<T: Clone + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH> {
@@ -169,8 +169,11 @@ impl<T: Clone + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH>
169169 ref_tracking_for_consts.seen.insert(op);
170170 ref_tracking_for_consts
171171 }
172+ pub fn next(&mut self) -> Option<(T, PATH)> {
173+ self.todo.pop()
174+ }
172175
173- pub fn track(&mut self, op: T, path: impl FnOnce() -> PATH) {
176+ fn track(&mut self, op: T, path: impl FnOnce() -> PATH) {
174177 if self.seen.insert(op.clone()) {
175178 trace!("Recursing below ptr {:#?}", op);
176179 let path = path();
@@ -435,95 +438,112 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
435438 if self.ecx.scalar_may_be_null(Scalar::from_maybe_pointer(place.ptr(), self.ecx))? {
436439 throw_validation_failure!(self.path, NullPtr { ptr_kind })
437440 }
438- // Do not allow pointers to uninhabited types.
441+ // Do not allow references to uninhabited types.
439442 if place.layout.abi.is_uninhabited() {
440443 let ty = place.layout.ty;
441444 throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
442445 }
443446 // Recursive checking
444447 if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
445- // Determine whether this pointer expects to be pointing to something mutable.
446- let ptr_expected_mutbl = match ptr_kind {
447- PointerKind::Box => Mutability::Mut,
448- PointerKind::Ref(mutbl) => {
449- // We do not take into account interior mutability here since we cannot know if
450- // there really is an `UnsafeCell` inside `Option<UnsafeCell>` -- so we check
451- // that in the recursive descent behind this reference (controlled by
452- // `allow_immutable_unsafe_cell`).
453- mutbl
454- }
455- };
456448 // Proceed recursively even for ZST, no reason to skip them!
457449 // `!` is a ZST and we want to validate it.
458- if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr(), 0) {
450+ if let Some(ctfe_mode) = self.ctfe_mode {
459451 let mut skip_recursive_check = false;
460- if let Some(GlobalAlloc::Static(did)) = self.ecx.tcx.try_get_global_alloc(alloc_id)
452+ // CTFE imposes restrictions on what references can point to.
453+ if let Ok((alloc_id, _offset, _prov)) =
454+ self.ecx.ptr_try_get_alloc_id(place.ptr(), 0)
461455 {
462- let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else { bug!() };
463- // Special handling for pointers to statics (irrespective of their type).
464- assert!(!self.ecx.tcx.is_thread_local_static(did));
465- assert!(self.ecx.tcx.is_static(did));
466- // Mode-specific checks
467- match self.ctfe_mode {
468- Some(
469- CtfeValidationMode::Static { .. } | CtfeValidationMode::Promoted { .. },
470- ) => {
471- // We skip recursively checking other statics. These statics must be sound by
472- // themselves, and the only way to get broken statics here is by using
473- // unsafe code.
474- // The reasons we don't check other statics is twofold. For one, in all
475- // sound cases, the static was already validated on its own, and second, we
476- // trigger cycle errors if we try to compute the value of the other static
477- // and that static refers back to us (potentially through a promoted).
478- // This could miss some UB, but that's fine.
479- // We still walk nested allocations, as they are fundamentally part of this validation run.
480- // This means we will also recurse into nested statics of *other*
481- // statics, even though we do not recurse into other statics directly.
482- // That's somewhat inconsistent but harmless.
483- skip_recursive_check = !nested;
484- }
485- Some(CtfeValidationMode::Const { .. }) => {
486- // We can't recursively validate `extern static`, so we better reject them.
487- if self.ecx.tcx.is_foreign_item(did) {
488- throw_validation_failure!(self.path, ConstRefToExtern);
456+ if let Some(GlobalAlloc::Static(did)) =
457+ self.ecx.tcx.try_get_global_alloc(alloc_id)
458+ {
459+ let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else {
460+ bug!()
461+ };
462+ // Special handling for pointers to statics (irrespective of their type).
463+ assert!(!self.ecx.tcx.is_thread_local_static(did));
464+ assert!(self.ecx.tcx.is_static(did));
465+ // Mode-specific checks
466+ match ctfe_mode {
467+ CtfeValidationMode::Static { .. }
468+ | CtfeValidationMode::Promoted { .. } => {
469+ // We skip recursively checking other statics. These statics must be sound by
470+ // themselves, and the only way to get broken statics here is by using
471+ // unsafe code.
472+ // The reasons we don't check other statics is twofold. For one, in all
473+ // sound cases, the static was already validated on its own, and second, we
474+ // trigger cycle errors if we try to compute the value of the other static
475+ // and that static refers back to us (potentially through a promoted).
476+ // This could miss some UB, but that's fine.
477+ // We still walk nested allocations, as they are fundamentally part of this validation run.
478+ // This means we will also recurse into nested statics of *other*
479+ // statics, even though we do not recurse into other statics directly.
480+ // That's somewhat inconsistent but harmless.
481+ skip_recursive_check = !nested;
482+ }
483+ CtfeValidationMode::Const { .. } => {
484+ // We can't recursively validate `extern static`, so we better reject them.
485+ if self.ecx.tcx.is_foreign_item(did) {
486+ throw_validation_failure!(self.path, ConstRefToExtern);
487+ }
489488 }
490489 }
491- None => {}
492490 }
493- }
494491
495- // Dangling and Mutability check.
496- let (size, _align, alloc_kind) = self.ecx.get_alloc_info(alloc_id);
497- if alloc_kind == AllocKind::Dead {
498- // This can happen for zero-sized references. We can't have *any* references to non-existing
499- // allocations though, interning rejects them all as the rest of rustc isn't happy with them...
500- // so we throw an error, even though this isn't really UB.
501- // A potential future alternative would be to resurrect this as a zero-sized allocation
502- // (which codegen will then compile to an aligned dummy pointer anyway).
503- throw_validation_failure!(self.path, DanglingPtrUseAfterFree { ptr_kind });
504- }
505- // If this allocation has size zero, there is no actual mutability here.
506- if size != Size::ZERO {
507- let alloc_actual_mutbl = mutability(self.ecx, alloc_id);
508- // Mutable pointer to immutable memory is no good.
509- if ptr_expected_mutbl == Mutability::Mut
510- && alloc_actual_mutbl == Mutability::Not
511- {
512- throw_validation_failure!(self.path, MutableRefToImmutable);
492+ // Dangling and Mutability check.
493+ let (size, _align, alloc_kind) = self.ecx.get_alloc_info(alloc_id);
494+ if alloc_kind == AllocKind::Dead {
495+ // This can happen for zero-sized references. We can't have *any* references to
496+ // non-existing allocations in const-eval though, interning rejects them all as
497+ // the rest of rustc isn't happy with them... so we throw an error, even though
498+ // this isn't really UB.
499+ // A potential future alternative would be to resurrect this as a zero-sized allocation
500+ // (which codegen will then compile to an aligned dummy pointer anyway).
501+ throw_validation_failure!(self.path, DanglingPtrUseAfterFree { ptr_kind });
513502 }
514- // In a const, everything must be completely immutable.
515- if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
503+ // If this allocation has size zero, there is no actual mutability here.
504+ if size != Size::ZERO {
505+ // Determine whether this pointer expects to be pointing to something mutable.
506+ let ptr_expected_mutbl = match ptr_kind {
507+ PointerKind::Box => Mutability::Mut,
508+ PointerKind::Ref(mutbl) => {
509+ // We do not take into account interior mutability here since we cannot know if
510+ // there really is an `UnsafeCell` inside `Option<UnsafeCell>` -- so we check
511+ // that in the recursive descent behind this reference (controlled by
512+ // `allow_immutable_unsafe_cell`).
513+ mutbl
514+ }
515+ };
516+ // Determine what it actually points to.
517+ let alloc_actual_mutbl = mutability(self.ecx, alloc_id);
518+ // Mutable pointer to immutable memory is no good.
516519 if ptr_expected_mutbl == Mutability::Mut
517- || alloc_actual_mutbl == Mutability::Mut
520+ && alloc_actual_mutbl == Mutability::Not
518521 {
519- throw_validation_failure!(self.path, ConstRefToMutable);
522+ throw_validation_failure!(self.path, MutableRefToImmutable);
523+ }
524+ // In a const, everything must be completely immutable.
525+ if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
526+ if ptr_expected_mutbl == Mutability::Mut
527+ || alloc_actual_mutbl == Mutability::Mut
528+ {
529+ throw_validation_failure!(self.path, ConstRefToMutable);
530+ }
520531 }
521532 }
522533 }
523534 // Potentially skip recursive check.
524535 if skip_recursive_check {
525536 return Ok(());
526537 }
538+ } else {
539+ // This is not CTFE, so it's Miri with recursive checking.
540+ // FIXME: we do *not* check behind boxes, since creating a new box first creates it uninitialized
541+ // and then puts the value in there, so briefly we have a box with uninit contents.
542+ // FIXME: should we also skip `UnsafeCell` behind shared references? Currently that is not
543+ // needed since validation reads bypass Stacked Borrows and data race checks.
544+ if matches!(ptr_kind, PointerKind::Box) {
545+ return Ok(());
546+ }
527547 }
528548 let path = &self.path;
529549 ref_tracking.track(place, || {
@@ -1072,11 +1092,23 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
10721092 /// `op` is assumed to cover valid memory if it is an indirect operand.
10731093 /// It will error if the bits at the destination do not match the ones described by the layout.
10741094 #[inline(always)]
1075- pub fn validate_operand(&self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
1095+ pub fn validate_operand(
1096+ &self,
1097+ op: &OpTy<'tcx, M::Provenance>,
1098+ recursive: bool,
1099+ ) -> InterpResult<'tcx> {
10761100 // Note that we *could* actually be in CTFE here with `-Zextra-const-ub-checks`, but it's
10771101 // still correct to not use `ctfe_mode`: that mode is for validation of the final constant
1078- // value, it rules out things like `UnsafeCell` in awkward places. It also can make checking
1079- // recurse through references which, for now, we don't want here, either.
1080- self.validate_operand_internal(op, vec![], None, None)
1102+ // value, it rules out things like `UnsafeCell` in awkward places.
1103+ if !recursive {
1104+ return self.validate_operand_internal(op, vec![], None, None);
1105+ }
1106+ // Do a recursive check.
1107+ let mut ref_tracking = RefTracking::empty();
1108+ self.validate_operand_internal(op, vec![], Some(&mut ref_tracking), None)?;
1109+ while let Some((mplace, path)) = ref_tracking.todo.pop() {
1110+ self.validate_operand_internal(&mplace.into(), path, Some(&mut ref_tracking), None)?;
1111+ }
1112+ Ok(())
10811113 }
10821114}
0 commit comments