Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 35 additions & 21 deletions compiler/rustc_const_eval/src/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<RangeSet>,
may_dangle: bool,
}

impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
Expand Down Expand Up @@ -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::<Option<AllocId>>::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::<Option<AllocId>>::without_provenance(i))
},
Ub(PointerOutOfBounds { .. }) => DanglingPtrOutOfBounds {
ptr_kind
},
Ub(PointerUseAfterFree(..)) => DanglingPtrUseAfterFree {
ptr_kind,
},
);
}
try_validation!(
self.ecx.check_ptr_align(
place.ptr(),
Expand All @@ -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);
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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)?;
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_middle/src/ty/adt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -436,6 +441,12 @@ impl<'tcx> AdtDef<'tcx> {
self.flags().contains(AdtFlags::IS_MANUALLY_DROP)
}

/// Returns `true` if this is `MaybeDangling<T>`.
#[inline]
pub fn is_maybe_dangling(self) -> bool {
self.flags().contains(AdtFlags::IS_MAYBE_DANGLING)
}

/// Returns `true` if this is `Pin<T>`.
#[inline]
pub fn is_pin(self) -> bool {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,7 @@ symbols! {
maxnumf128,
may_dangle,
may_unwind,
maybe_dangling,
maybe_uninit,
maybe_uninit_uninit,
maybe_uninit_zeroed,
Expand Down
1 change: 1 addition & 0 deletions library/core/src/mem/maybe_dangling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: ?Sized>(P);

impl<P: ?Sized> MaybeDangling<P> {
Expand Down
4 changes: 4 additions & 0 deletions src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tree Borrows will need the same patch.

Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
Expand Down Expand Up @@ -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() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just entirely skip traversing the references inside MaybeDangling? For the aliasing model that should just be a complete NOP.

// 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
Expand Down
3 changes: 3 additions & 0 deletions src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
8 changes: 8 additions & 0 deletions src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![feature(maybe_dangling)]
use std::mem::{transmute, MaybeDangling};
use std::ptr::null;

fn main() {
unsafe { transmute::<MaybeDangling<*const u8>, MaybeDangling<&u8>>(MaybeDangling::new(null())) };
//~^ ERROR: Undefined Behavior: constructing invalid value: encountered a null reference
}
13 changes: 13 additions & 0 deletions src/tools/miri/tests/fail/both_borrows/maybe_dangling_null.stderr
Original file line number Diff line number Diff line change
@@ -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<*const u8>, 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

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![feature(maybe_dangling)]
use std::mem::{transmute, MaybeDangling};

fn main() {
let a = [1u16, 0u16];
unsafe { transmute::<MaybeDangling<*const u16>, MaybeDangling<&u16>>(MaybeDangling::new(a.as_ptr().byte_add(1))) };
//~^ ERROR: Undefined Behavior: constructing invalid value: encountered an unaligned reference
}
Original file line number Diff line number Diff line change
@@ -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<*const u16>, 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

34 changes: 34 additions & 0 deletions src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs
Original file line number Diff line number Diff line change
@@ -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<Box<u32>> = 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<&u32>>(MaybeDangling::new(&local)) }
};

// move the dangling reference (without `MaybeDangling` this causes UB)
let _x: MaybeDangling<&u32> = x;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
0..1: [ SharedReadWrite<TAG> ]
0..1: [ SharedReadWrite<TAG> ]
0..1: [ SharedReadWrite<TAG> ]
0..1: [ SharedReadWrite<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> ]
0..1: [ SharedReadWrite<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> SharedReadOnly<TAG> ]
0..1: [ SharedReadWrite<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> Unique<TAG> ]
0..1: [ SharedReadWrite<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> Disabled<TAG> SharedReadOnly<TAG> ]
0..1: [ unknown-bottom(..<TAG>) ]
Loading