From 4efbe0ca95e49171d2192914c41985564338ae3b Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 29 Oct 2025 19:25:57 +0100 Subject: [PATCH 1/2] add an adt flag for `MaybeDangling` --- compiler/rustc_hir/src/lang_items.rs | 3 ++- compiler/rustc_middle/src/ty/adt.rs | 11 +++++++++++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/mem/maybe_dangling.rs | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index f57b1a9c8dea3..05f4b66c0e27a 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -341,7 +341,8 @@ language_item_table! { PhantomData, sym::phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1); - ManuallyDrop, sym::manually_drop, manually_drop, Target::Struct, GenericRequirement::None; + ManuallyDrop, sym::manually_drop, manually_drop, Target::Struct, GenericRequirement::Exact(1); + MaybeDangling, sym::maybe_dangling, maybe_dangling, Target::Struct, GenericRequirement::Exact(1); BikeshedGuaranteedNoDrop, sym::bikeshed_guaranteed_no_drop, bikeshed_guaranteed_no_drop, Target::Trait, GenericRequirement::Exact(0); MaybeUninit, sym::maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None; diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index 020669588ac5c..b2b53ffa08f72 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -58,6 +58,8 @@ bitflags::bitflags! { const IS_PIN = 1 << 11; /// Indicates whether the type is `#[pin_project]`. const IS_PIN_PROJECT = 1 << 12; + /// Indicates whether the type is `MaybeDangling<_>`. + const IS_MAYBE_DANGLING = 1 << 13; } } rustc_data_structures::external_bitflags_debug! { AdtFlags } @@ -312,6 +314,9 @@ impl AdtDefData { if tcx.is_lang_item(did, LangItem::ManuallyDrop) { flags |= AdtFlags::IS_MANUALLY_DROP; } + if tcx.is_lang_item(did, LangItem::MaybeDangling) { + flags |= AdtFlags::IS_MAYBE_DANGLING; + } if tcx.is_lang_item(did, LangItem::UnsafeCell) { flags |= AdtFlags::IS_UNSAFE_CELL; } @@ -436,6 +441,12 @@ impl<'tcx> AdtDef<'tcx> { self.flags().contains(AdtFlags::IS_MANUALLY_DROP) } + /// Returns `true` if this is `MaybeDangling`. + #[inline] + pub fn is_maybe_dangling(self) -> bool { + self.flags().contains(AdtFlags::IS_MAYBE_DANGLING) + } + /// Returns `true` if this is `Pin`. #[inline] pub fn is_pin(self) -> bool { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3d40b7317459b..52f383039f892 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1216,6 +1216,7 @@ symbols! { maxnumf128, may_dangle, may_unwind, + maybe_dangling, maybe_uninit, maybe_uninit_uninit, maybe_uninit_zeroed, diff --git a/library/core/src/mem/maybe_dangling.rs b/library/core/src/mem/maybe_dangling.rs index a5f77e667f975..914ae33414ef9 100644 --- a/library/core/src/mem/maybe_dangling.rs +++ b/library/core/src/mem/maybe_dangling.rs @@ -73,6 +73,7 @@ use crate::{mem, ptr}; #[repr(transparent)] #[rustc_pub_transparent] #[derive(Debug, Copy, Clone, Default)] +#[lang = "maybe_dangling"] pub struct MaybeDangling(P); impl MaybeDangling

{ From 959b31bbd9068a898acd7a31cfcff4ff8b65e496 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 29 Oct 2025 19:29:29 +0100 Subject: [PATCH 2/2] miri/const eval: support `MaybeDangling` --- .../src/interpret/validity.rs | 56 ++++++++++++------- .../src/borrow_tracker/stacked_borrows/mod.rs | 4 ++ .../src/borrow_tracker/tree_borrows/mod.rs | 3 + .../fail/both_borrows/maybe_dangling_null.rs | 8 +++ .../both_borrows/maybe_dangling_null.stderr | 13 +++++ .../both_borrows/maybe_dangling_unalighed.rs | 8 +++ .../maybe_dangling_unalighed.stderr | 13 +++++ .../tests/pass/both_borrows/maybe_dangling.rs | 34 +++++++++++ .../stacked_borrows/stack-printing.stdout | 4 +- 9 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.rs create mode 100644 src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.stderr create mode 100644 src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.rs create mode 100644 src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.stderr create mode 100644 src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 2cf490350e907..9c460f8a687c4 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -7,6 +7,7 @@ use std::borrow::Cow; use std::fmt::Write; use std::hash::Hash; +use std::mem; use std::num::NonZero; use either::{Left, Right}; @@ -288,6 +289,7 @@ struct ValidityVisitor<'rt, 'tcx, M: Machine<'tcx>> { /// If this is `Some`, then `reset_provenance_and_padding` must be true (but not vice versa: /// we might not track data vs padding bytes if the operand isn't stored in memory anyway). data_bytes: Option, + may_dangle: bool, } impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { @@ -503,27 +505,29 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { // alignment and size determined by the layout (size will be 0, // alignment should take attributes into account). .unwrap_or_else(|| (place.layout.size, place.layout.align.abi)); - // Direct call to `check_ptr_access_align` checks alignment even on CTFE machines. - try_validation!( - self.ecx.check_ptr_access( - place.ptr(), - size, - CheckInAllocMsg::Dereferenceable, // will anyway be replaced by validity message - ), - self.path, - Ub(DanglingIntPointer { addr: 0, .. }) => NullPtr { ptr_kind, maybe: false }, - Ub(DanglingIntPointer { addr: i, .. }) => DanglingPtrNoProvenance { - ptr_kind, - // FIXME this says "null pointer" when null but we need translate - pointer: format!("{}", Pointer::>::without_provenance(i)) - }, - Ub(PointerOutOfBounds { .. }) => DanglingPtrOutOfBounds { - ptr_kind - }, - Ub(PointerUseAfterFree(..)) => DanglingPtrUseAfterFree { - ptr_kind, - }, - ); + if !self.may_dangle { + // Direct call to `check_ptr_access_align` checks alignment even on CTFE machines. + try_validation!( + self.ecx.check_ptr_access( + place.ptr(), + size, + CheckInAllocMsg::Dereferenceable, // will anyway be replaced by validity message + ), + self.path, + Ub(DanglingIntPointer { addr: 0, .. }) => NullPtr { ptr_kind, maybe: false }, + Ub(DanglingIntPointer { addr: i, .. }) => DanglingPtrNoProvenance { + ptr_kind, + // FIXME this says "null pointer" when null but we need translate + pointer: format!("{}", Pointer::>::without_provenance(i)) + }, + Ub(PointerOutOfBounds { .. }) => DanglingPtrOutOfBounds { + ptr_kind + }, + Ub(PointerUseAfterFree(..)) => DanglingPtrUseAfterFree { + ptr_kind, + }, + ); + } try_validation!( self.ecx.check_ptr_align( place.ptr(), @@ -536,6 +540,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { found_bytes: has.bytes() }, ); + // Make sure this is non-null. We checked dereferenceability above, but if `size` is zero // that does not imply non-null. let scalar = Scalar::from_maybe_pointer(place.ptr(), self.ecx); @@ -1265,6 +1270,14 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, ty::PatternKind::Or(_patterns) => {} } } + ty::Adt(adt, _) if adt.is_maybe_dangling() => { + let could_dangle = mem::replace(&mut self.may_dangle, true); + + let inner = self.ecx.project_field(val, FieldIdx::ZERO)?; + self.visit_value(&inner)?; + + self.may_dangle = could_dangle; + } _ => { // default handler try_validation!( @@ -1350,6 +1363,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ecx, reset_provenance_and_padding, data_bytes: reset_padding.then_some(RangeSet(Vec::new())), + may_dangle: false, }; v.visit_value(val)?; v.reset_padding(val)?; diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index 617b619a7df90..66e804d972b76 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -917,6 +917,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { RetagInfo { cause: self.retag_cause, in_field: self.in_field }, )?; self.ecx.write_immediate(*val, place)?; + interp_ok(()) } } @@ -964,6 +965,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // even if field retagging is not enabled. *shrug*) self.walk_value(place)?; } + ty::Adt(adt, _) if adt.is_maybe_dangling() => { + // Skip traversing for everything inside of `MaybeDangling` + } _ => { // Not a reference/pointer/box. Recurse. let in_field = mem::replace(&mut self.in_field, true); // remember and restore old value diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 1a7c0af2988a3..a205502327307 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -523,6 +523,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // even if field retagging is not enabled. *shrug*) self.walk_value(place)?; } + ty::Adt(adt, _) if adt.is_maybe_dangling() => { + // Skip traversing for everything inside of `MaybeDangling` + } _ => { // Not a reference/pointer/box. Recurse. self.walk_value(place)?; diff --git a/src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.rs b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.rs new file mode 100644 index 0000000000000..56d963c30039f --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.rs @@ -0,0 +1,8 @@ +#![feature(maybe_dangling)] +use std::mem::{transmute, MaybeDangling}; +use std::ptr::null; + +fn main() { + unsafe { transmute::, MaybeDangling<&u8>>(MaybeDangling::new(null())) }; + //~^ ERROR: Undefined Behavior: constructing invalid value: encountered a null reference +} diff --git a/src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.stderr b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.stderr new file mode 100644 index 0000000000000..78c2b77fcbe6b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: constructing invalid value: encountered a null reference + --> tests/fail/both_borrows/maybe_dangling_null.rs:LL:CC + | +LL | unsafe { transmute::, MaybeDangling<&u8>>(MaybeDangling::new(null())) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.rs b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.rs new file mode 100644 index 0000000000000..66d7aaea46b1b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.rs @@ -0,0 +1,8 @@ +#![feature(maybe_dangling)] +use std::mem::{transmute, MaybeDangling}; + +fn main() { + let a = [1u16, 0u16]; + unsafe { transmute::, MaybeDangling<&u16>>(MaybeDangling::new(a.as_ptr().byte_add(1))) }; + //~^ ERROR: Undefined Behavior: constructing invalid value: encountered an unaligned reference +} diff --git a/src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.stderr b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.stderr new file mode 100644 index 0000000000000..b9aec1c90a7ac --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/maybe_dangling_unalighed.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: constructing invalid value: encountered an unaligned reference (required ALIGN byte alignment but found ALIGN) + --> tests/fail/both_borrows/maybe_dangling_unalighed.rs:LL:CC + | +LL | unsafe { transmute::, MaybeDangling<&u16>>(MaybeDangling::new(a.as_ptr().byte_add(1))) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs b/src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs new file mode 100644 index 0000000000000..a458cca3e0eb9 --- /dev/null +++ b/src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs @@ -0,0 +1,34 @@ +// Check that `MaybeDangling` actually prevents UB when it wraps dangling +// boxes and references +#![feature(maybe_dangling)] +use std::mem::{self, MaybeDangling}; +use std::ptr::drop_in_place; + +fn main() { + boxy(); + reference(); +} + +fn boxy() { + let mut x = MaybeDangling::new(Box::new(1)); + + // make the box dangle + unsafe { drop_in_place(x.as_mut()) }; + + // move the dangling box (without `MaybeDangling` this causes UB) + let x: MaybeDangling> = x; + + mem::forget(x); +} + +fn reference() { + let x = { + let local = 0; + + // erase the lifetime to make a dangling reference + unsafe { mem::transmute::, MaybeDangling<&u32>>(MaybeDangling::new(&local)) } + }; + + // move the dangling reference (without `MaybeDangling` this causes UB) + let _x: MaybeDangling<&u32> = x; +} diff --git a/src/tools/miri/tests/pass/stacked_borrows/stack-printing.stdout b/src/tools/miri/tests/pass/stacked_borrows/stack-printing.stdout index de7da309271da..296339e738455 100644 --- a/src/tools/miri/tests/pass/stacked_borrows/stack-printing.stdout +++ b/src/tools/miri/tests/pass/stacked_borrows/stack-printing.stdout @@ -1,6 +1,6 @@ 0..1: [ SharedReadWrite ] 0..1: [ SharedReadWrite ] 0..1: [ SharedReadWrite ] -0..1: [ SharedReadWrite Unique Unique Unique Unique Unique Unique Unique Unique Unique Unique Unique ] -0..1: [ SharedReadWrite Disabled Disabled Disabled Disabled Disabled Disabled Disabled Disabled Disabled Disabled Disabled SharedReadOnly ] +0..1: [ SharedReadWrite Unique Unique Unique Unique Unique Unique Unique ] +0..1: [ SharedReadWrite Disabled Disabled Disabled Disabled Disabled Disabled Disabled SharedReadOnly ] 0..1: [ unknown-bottom(..) ]