diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index e8b4cb343794c..e021761a59231 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -541,6 +541,21 @@ impl NoArgsAttributeParser for RustcNoMirInlineParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoMirInline; } +pub(crate) struct RustcNoWritableParser; + +impl NoArgsAttributeParser for RustcNoWritableParser { + const PATH: &[Symbol] = &[sym::rustc_no_writable]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Closure), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Trait { body: true })), + ]); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoWritable; +} + pub(crate) struct RustcLintQueryInstabilityParser; impl NoArgsAttributeParser for RustcLintQueryInstabilityParser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 259a73de59853..4fef40481259e 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -309,6 +309,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 214d653d23c13..1a4f1f246c702 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -809,6 +809,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Also returns a ptr to `self.extra` so that the caller can use it in parallel with the /// allocation. /// + /// This triggers UB if the allocation is not mutable! + /// /// You almost certainly want to use `get_ptr_alloc`/`get_ptr_alloc_mut` instead. pub fn get_alloc_raw_mut( &mut self, @@ -841,6 +843,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Gives raw, mutable access to the `Allocation` address, without bounds or alignment checks. /// The caller is responsible for calling the access hooks! + /// + /// This triggers UB if the allocation is not mutable! pub fn get_alloc_bytes_unchecked_raw_mut( &mut self, id: AllocId, @@ -893,6 +897,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } /// Return the `extra` field of the given allocation. + /// + /// This triggers UB if the allocation is not mutable! pub fn get_alloc_extra_mut<'a>( &'a mut self, id: AllocId, @@ -1086,6 +1092,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(ty) } + /// This triggers UB if the allocation is not mutable! pub fn alloc_mark_immutable(&mut self, id: AllocId) -> InterpResult<'tcx> { self.get_alloc_raw_mut(id)?.0.mutability = Mutability::Not; interp_ok(()) diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index d74450e67cf1d..7fb68b795c96b 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1414,6 +1414,9 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_scalable_vector, Normal, template!(List: &["count"]), WarnFollowing, EncodeCrossCrate::Yes, "`#[rustc_scalable_vector]` defines a scalable vector type" ), + rustc_attr!( + rustc_no_writable, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, "`#[rustc_no_writable]` stops the compiler from adding the `writable` flag in LLVM, thus under Tree Borrows, mutable retags no longer count as writes" + ), // ========================================================================== // Internal attributes, Testing: diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index a18ddff947099..fa26ac8cfd677 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1489,6 +1489,9 @@ pub enum AttributeKind { /// Represents `#[rustc_no_mir_inline]` RustcNoMirInline, + /// Represents `#[rustc_no_writable]` + RustcNoWritable, + /// Represents `#[rustc_non_const_trait_method]`. RustcNonConstTraitMethod, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index c19fc6976c6e6..c49629a2504a8 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -161,6 +161,7 @@ impl AttributeKind { RustcNoImplicitAutorefs => Yes, RustcNoImplicitBounds => No, RustcNoMirInline => Yes, + RustcNoWritable => Yes, RustcNonConstTraitMethod => No, // should be reported via other queries like `constness` RustcNonnullOptimizationGuaranteed => Yes, RustcNounwind => No, diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index f9d583bdf18c7..ba46751e68db8 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -834,6 +834,7 @@ fn test_unstable_options_tracking_hash() { tracked!(no_profiler_runtime, true); tracked!(no_trait_vptr, true); tracked!(no_unique_section_names, true); + tracked!(no_writable, true); tracked!(offload, vec![Offload::Device]); tracked!(on_broken_pipe, OnBrokenPipe::Kill); tracked!(osx_rpath_install_name, true); diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 9945802d70c14..bc31b281e986b 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -357,6 +357,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcNoImplicitAutorefs | AttributeKind::RustcNoImplicitBounds | AttributeKind::RustcNoMirInline + | AttributeKind::RustcNoWritable | AttributeKind::RustcNonConstTraitMethod | AttributeKind::RustcNonnullOptimizationGuaranteed | AttributeKind::RustcNounwind diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index fb1b3c8679481..fb97015560090 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2530,6 +2530,8 @@ options! { "disable generation of trait vptr in vtable for upcasting"), no_unique_section_names: bool = (false, parse_bool, [TRACKED], "do not use unique names for text and data sections when -Z function-sections is used"), + no_writable: bool = (false, parse_bool, [TRACKED], + "do not insert the writable LLVM attribute; mutable retags don't count as writes under Tree Borrows"), normalize_docs: bool = (false, parse_bool, [TRACKED], "normalize associated items in rustdoc when generating documentation"), offload: Vec = (Vec::new(), parse_offload, [TRACKED], diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 738c9b975fd00..d887fdea52b5f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1751,6 +1751,7 @@ symbols! { rustc_no_implicit_autorefs, rustc_no_implicit_bounds, rustc_no_mir_inline, + rustc_no_writable, rustc_non_const_trait_method, rustc_nonnull_optimization_guaranteed, rustc_nounwind, diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 36dd4d6782ac1..dbde8fb8cf372 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -754,6 +754,7 @@ impl [T] { #[rustc_as_ptr] #[inline(always)] #[must_use] + #[rustc_no_writable] pub const fn as_mut_ptr(&mut self) -> *mut T { self as *mut [T] as *mut T } diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 0d52bfb8c9aa4..5d9f1bef6de42 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -589,6 +589,7 @@ impl str { #[rustc_as_ptr] #[must_use] #[inline(always)] + #[rustc_no_writable] pub const fn as_mut_ptr(&mut self) -> *mut u8 { self as *mut str as *mut u8 } diff --git a/src/doc/unstable-book/src/compiler-flags/no-writable.md b/src/doc/unstable-book/src/compiler-flags/no-writable.md new file mode 100644 index 0000000000000..164b82d2cc24e --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/no-writable.md @@ -0,0 +1,5 @@ +# `no-writable` + +--- + +This flag will globally stop the compiler from inserting the [writable](https://llvm.org/docs/LangRef.html#writable) LLVM flag. It also stops [Miri](github.com/rust-lang/miri) from testing for undefined behavior when inserting writes. It has the same effect as applying `#[rustc_no_writable]` to every function. diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 4808e53698c68..7c6eb00082063 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -529,6 +529,7 @@ Some native rustc `-Z` flags are also very relevant for Miri: sets this flag per default. * `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri enables this per default because it is needed for [Stacked Borrows] and [Tree Borrows]. +* `-Zno-writable` disables the strong mode globally, thus disabling the tracking of spurious writes. This also stops the compiler from adding the `writable` attribute. This only has an effect in Miri if [Tree Borrows] are enabled. Moreover, Miri recognizes some environment variables: diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index e73c870508bd0..3a8dae22f4cbf 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -519,6 +519,7 @@ fn main() -> ExitCode { miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams { precise_interior_mut: true, + writable: true, })); } else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" { match &mut miri_config.borrow_tracker { @@ -530,6 +531,16 @@ fn main() -> ExitCode { "`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-no-precise-interior-mut`" ), }; + } else if arg == "-Zno-writable" { + match &mut miri_config.borrow_tracker { + Some(BorrowTrackerMethod::TreeBorrows(params)) => { + params.writable = false; + } + _ => + eprintln!( + "Warning: `-Zno-writable` only has an effect in Miri if `-Zmiri-tree-borrows` is before it. The flag still will have an effect in the compiler." + ), + }; } else if arg == "-Zmiri-disable-data-race-detector" { miri_config.data_race_detector = false; miri_config.weak_memory_emulation = false; diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index 9aa0ce48e09a0..3f392bd6687be 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -224,6 +224,7 @@ pub enum BorrowTrackerMethod { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TreeBorrowsParams { pub precise_interior_mut: bool, + pub writable: bool, } impl BorrowTrackerMethod { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index 093dada405af8..16e89ee460de0 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -15,7 +15,7 @@ use crate::*; #[derive(Clone, Copy, Debug)] pub enum AccessCause { Explicit(AccessKind), - Reborrow, + Reborrow(AccessKind), Dealloc, FnExit(AccessKind), } @@ -24,7 +24,7 @@ impl fmt::Display for AccessCause { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Explicit(kind) => write!(f, "{kind}"), - Self::Reborrow => write!(f, "reborrow"), + Self::Reborrow(_) => write!(f, "reborrow"), Self::Dealloc => write!(f, "deallocation"), // This is dead code, since the protector release access itself can never // cause UB (while the protector is active, if some other access invalidates @@ -40,7 +40,7 @@ impl AccessCause { let rel = if is_foreign { "foreign" } else { "child" }; match self { Self::Explicit(kind) => format!("{rel} {kind}"), - Self::Reborrow => format!("reborrow (acting as a {rel} read access)"), + Self::Reborrow(kind) => format!("reborrow (acting as a {rel} {kind})"), Self::Dealloc => format!("deallocation (acting as a {rel} write access)"), Self::FnExit(kind) => format!("protector release (acting as a {rel} {kind})"), } 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 a205502327307..4a32151791eb8 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -1,4 +1,6 @@ use rustc_abi::Size; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::find_attr; use rustc_middle::mir::{Mutability, RetagKind}; use rustc_middle::ty::layout::HasTypingEnv; use rustc_middle::ty::{self, Ty}; @@ -6,7 +8,7 @@ use rustc_middle::ty::{self, Ty}; use self::foreign_access_skipping::IdempotentForeignAccess; use self::tree::LocationState; use crate::borrow_tracker::{AccessKind, GlobalState, GlobalStateInner, ProtectorKind}; -use crate::concurrency::data_race::NaReadType; +use crate::concurrency::data_race::{NaReadType, NaWriteType}; use crate::*; pub mod diagnostics; @@ -109,13 +111,13 @@ impl<'tcx> Tree { pub struct NewPermission { /// Permission for the frozen part of the range. freeze_perm: Permission, - /// Whether a read access should be performed on the frozen part on a retag. - freeze_access: bool, + /// Whether and what kind of access should be performed on the frozen part on a retag. + freeze_access: Option, + /// Whether a write access should be performed on the frozen part on a retag. /// Permission for the non-frozen part of the range. nonfreeze_perm: Permission, - /// Whether a read access should be performed on the non-frozen - /// part on a retag. - nonfreeze_access: bool, + /// Whether and what kind of access should be performed on the non-frozen part on a retag. + nonfreeze_access: Option, /// Permission for memory outside the range. outside_perm: Permission, /// Whether this pointer is part of the arguments of a function call. @@ -138,6 +140,19 @@ impl<'tcx> NewPermission { let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env()); let is_protected = retag_kind == RetagKind::FnEntry; + // Check if the strong mode / writable optimization has been disabled for this function using the `#[rustc_no_writable]` attribute or the `-Zno-writable` flag + let def_id = cx.frame().instance().def_id(); + let no_writable = find_attr!(cx.tcx, def_id, AttributeKind::RustcNoWritable) + || !cx + .machine + .borrow_tracker + .as_ref() + .unwrap() + .borrow() + .borrow_tracker_method + .get_tree_borrows_params() + .writable; + if matches!(ref_mutability, Some(Mutability::Mut) | None if !ty_is_unpin) { // Mutable reference / Box to pinning type: retagging is a NOP. // FIXME: with `UnsafePinned`, this should do proper per-byte tracking. @@ -159,14 +174,32 @@ impl<'tcx> NewPermission { _ => Permission::new_reserved_im(), }; - // Everything except for `Cell` gets an initial access. - let initial_access = |perm: &Permission| !perm.is_cell(); + // [0] contains freeze information, [1] contains non freeze information + let mut access_perms = [(freeze_perm, None), (nonfreeze_perm, None)]; + for (permission, access) in &mut access_perms { + // Everything except for `Cell` gets an initial read. + if !permission.is_cell() { + // Every explicit mutable reference that gets an initial read also gets an initial write (except if it has been explicitly disabled using the `#[rustc_no_writable]` attribute or the `-Zno-writable` flag). + // Thus implicit mutable references (Two Phase borrowing) and Raw pointers are excluded. + if !no_writable + && Some(Mutability::Mut) == ref_mutability + && matches!(retag_kind, RetagKind::FnEntry) + { + *access = Some(AccessKind::Write); + + // if cell gets a write, permission is gonna be set to Unique + *permission = Permission::new_unique(); + } else { + *access = Some(AccessKind::Read); + } + } + } Some(NewPermission { - freeze_perm, - freeze_access: initial_access(&freeze_perm), - nonfreeze_perm, - nonfreeze_access: initial_access(&nonfreeze_perm), + freeze_perm: access_perms[0].0, + freeze_access: access_perms[0].1, + nonfreeze_perm: access_perms[1].0, + nonfreeze_access: access_perms[1].1, outside_perm: if ty_is_freeze { freeze_perm } else { nonfreeze_perm }, protector: is_protected.then_some(if ref_mutability.is_some() { // Strong protector for references @@ -287,31 +320,36 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { .precise_interior_mut; // Compute initial "inside" permissions. - let loc_state = |frozen: bool| -> LocationState { + let loc_state = |frozen: bool| -> (LocationState, Option) { let (perm, access) = if frozen { (new_perm.freeze_perm, new_perm.freeze_access) } else { (new_perm.nonfreeze_perm, new_perm.nonfreeze_access) }; let sifa = perm.strongest_idempotent_foreign_access(protected); - if access { + + let state = if access.is_some() { LocationState::new_accessed(perm, sifa) } else { LocationState::new_non_accessed(perm, sifa) - } + }; + + (state, access) }; let inside_perms = if !precise_interior_mut { // For `!Freeze` types, just pretend the entire thing is an `UnsafeCell`. let ty_is_freeze = place.layout.ty.is_freeze(*this.tcx, this.typing_env()); - let state = loc_state(ty_is_freeze); - DedupRangeMap::new(ptr_size, state) + DedupRangeMap::new(ptr_size, loc_state(ty_is_freeze)) } else { // The initial state will be overwritten by the visitor below. - let mut perms_map: DedupRangeMap = DedupRangeMap::new( + let mut perms_map = DedupRangeMap::new( ptr_size, - LocationState::new_accessed( - Permission::new_disabled(), - IdempotentForeignAccess::None, + ( + LocationState::new_accessed( + Permission::new_disabled(), + IdempotentForeignAccess::None, + ), + None, ), ); this.visit_freeze_sensitive(place, ptr_size, |range, frozen| { @@ -324,42 +362,71 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { perms_map }; - let alloc_extra = this.get_alloc_extra(alloc_id)?; - let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); + for (perm_range, (_perm, access)) in inside_perms.iter_all() { + if let Some(access) = access { + // Some reborrows incur a read/write access to the parent. + // As a write also implies a read, a single write is performed instead of a read and a write. + + // writing to an immutable allocation (static variables) is UB, check this here + if *access == AccessKind::Write + && this.get_alloc_mutability(alloc_id).unwrap().is_not() + { + throw_ub!(WriteToReadOnly(alloc_id)) + } - for (perm_range, perm) in inside_perms.iter_all() { - if perm.accessed() { - // Some reborrows incur a read access to the parent. // Adjust range to be relative to allocation start (rather than to `place`). let range_in_alloc = AllocRange { start: Size::from_bytes(perm_range.start) + base_offset, size: Size::from_bytes(perm_range.end - perm_range.start), }; + let alloc_extra = this.get_alloc_extra(alloc_id)?; + let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); + tree_borrows.perform_access( parent_prov, range_in_alloc, - AccessKind::Read, - diagnostics::AccessCause::Reborrow, + *access, + diagnostics::AccessCause::Reborrow(*access), this.machine.borrow_tracker.as_ref().unwrap(), alloc_id, this.machine.current_user_relevant_span(), )?; + // don't need it anymore + drop(tree_borrows); + // Also inform the data race model (but only if any bytes are actually affected). if range_in_alloc.size.bytes() > 0 { if let Some(data_race) = alloc_extra.data_race.as_vclocks_ref() { - data_race.read_non_atomic( - alloc_id, - range_in_alloc, - NaReadType::Retag, - Some(place.layout.ty), - &this.machine, - )? + match access { + AccessKind::Read => + data_race.read_non_atomic( + alloc_id, + range_in_alloc, + NaReadType::Retag, + Some(place.layout.ty), + &this.machine, + )?, + AccessKind::Write => + data_race.write_non_atomic( + alloc_id, + range_in_alloc, + NaWriteType::Retag, + Some(place.layout.ty), + &this.machine, + )?, + }; } } } } + + let mut tree_borrows = this.get_alloc_extra(alloc_id)?.borrow_tracker_tb().borrow_mut(); + + // remove access information + let inside_perms = inside_perms.transform(|_, (loc_state, _)| loc_state); + // Record the parent-child pair in the tree. tree_borrows.new_child( base_offset, @@ -550,9 +617,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // argument doesn't matter // (`ty_is_freeze || true` in `new_reserved` will always be `true`). freeze_perm: Permission::new_reserved_frz(), - freeze_access: true, + freeze_access: Some(AccessKind::Write), nonfreeze_perm: Permission::new_reserved_frz(), - nonfreeze_access: true, + nonfreeze_access: Some(AccessKind::Write), outside_perm: Permission::new_reserved_frz(), protector: Some(ProtectorKind::StrongProtector), }; diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs index b62c5f242c374..eee0b83b5e7d7 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs @@ -91,7 +91,7 @@ impl PartialOrd for PermissionPriv { impl PermissionPriv { /// Check if `self` can be the initial state of a pointer. fn is_initial(&self) -> bool { - matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM | Cell) + matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM | Cell | Unique) } /// Reject `ReservedIM` that cannot exist in the presence of a protector. @@ -265,14 +265,19 @@ impl Permission { self.inner == Cell } - /// Default initial permission of the root of a new tree at inbounds positions. - /// Must *only* be used for the root, this is not in general an "initial" permission! + #[cfg(test)] + pub fn is_unique(&self) -> bool { + self.inner == Unique + } + + /// Default initial permission for mutable references at the start of a function that is either + /// protected or not interior mutable (see *writable* and *strong mode*) and of the root of a new tree at inbounds positions. pub fn new_unique() -> Self { Self { inner: Unique } } /// Default initial permission of a reborrowed mutable reference that is either - /// protected or not interior mutable. + /// protected or not interior mutable and not at the start of a function (see *writable* and *strong mode*). pub fn new_reserved_frz() -> Self { Self { inner: ReservedFrz { conflicted: false } } } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index 54a0e0cefe743..9b93086eb879a 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -73,6 +73,14 @@ impl LocationState { Self { permission, accessed: true, idempotent_foreign_access: sifa } } + #[cfg(test)] + pub fn possible(&self) -> bool { + if !self.accessed && self.permission.is_unique() { + return false; + } + return true; + } + /// Check if the location has been accessed, i.e. if it has /// ever been accessed through a child pointer. pub fn accessed(&self) -> bool { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs index 52fe1c08a3092..500f13eada7bc 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree/tests.rs @@ -9,13 +9,17 @@ use crate::borrow_tracker::tree_borrows::exhaustive::{Exhaustive, precondition}; impl Exhaustive for LocationState { fn exhaustive() -> Box> { // We keep `latest_foreign_access` at `None` as that's just a cache. - Box::new(<(Permission, bool)>::exhaustive().map(|(permission, accessed)| { - Self { - permission, - accessed, - idempotent_foreign_access: IdempotentForeignAccess::default(), - } - })) + Box::new( + <(Permission, bool)>::exhaustive() + .map(|(permission, accessed)| { + Self { + permission, + accessed, + idempotent_foreign_access: IdempotentForeignAccess::default(), + } + }) + .filter(|x| x.possible()), + ) } } @@ -438,17 +442,19 @@ mod spurious_read { /// Perform a read on the given pointer if its state is `accessed`. /// Must be called just after reborrowing a pointer, and just after /// removing a protector. - fn read_if_accessed(self, ptr: PtrSelector) -> Result { + fn retag_dependent_access(self, ptr: PtrSelector) -> Result { let accessed = match ptr { - PtrSelector::X => self.x.state.accessed, - PtrSelector::Y => self.y.state.accessed, + PtrSelector::X => + self.x.state.permission.protector_end_access().filter(|_| self.x.state.accessed), + PtrSelector::Y => + self.y.state.permission.protector_end_access().filter(|_| self.y.state.accessed), PtrSelector::Other => panic!( "the `accessed` status of `PtrSelector::Other` is unknown, do not pass it to `read_if_accessed`" ), }; - if accessed { - self.perform_test_access(&TestAccess { ptr, kind: AccessKind::Read }) + if let Some(kind) = accessed { + self.perform_test_access(&TestAccess { ptr, kind }) } else { Ok(self) } @@ -457,13 +463,13 @@ mod spurious_read { /// Remove the protector of `x`, including the implicit read on function exit. fn end_protector_x(self) -> Result { let x = self.x.end_protector(); - Self { x, ..self }.read_if_accessed(PtrSelector::X) + Self { x, ..self }.retag_dependent_access(PtrSelector::X) } /// Remove the protector of `y`, including the implicit read on function exit. fn end_protector_y(self) -> Result { let y = self.y.end_protector(); - Self { y, ..self }.read_if_accessed(PtrSelector::Y) + Self { y, ..self }.retag_dependent_access(PtrSelector::Y) } fn retag_y(self, new_y: LocStateProt) -> Result { @@ -473,7 +479,7 @@ mod spurious_read { } // `xy_rel` changes to "mutually foreign" now: `y` can no longer be a parent of `x`. Self { y: new_y, xy_rel: RelPosXY::MutuallyForeign, ..self } - .read_if_accessed(PtrSelector::Y) + .retag_dependent_access(PtrSelector::Y) } fn perform_test_event(self, evt: &TestEvent) -> Result { @@ -696,11 +702,12 @@ mod spurious_read { fn initial_state(&self) -> Result { let (x, y) = self.retag_permissions(); let state = LocStateProtPair { xy_rel: self.xy_rel, x, y }; - state.read_if_accessed(PtrSelector::X) + state.retag_dependent_access(PtrSelector::X) } } #[test] + // TODO: this now fails, as `is_initial` now also can be Unique /// For each of the patterns described above, execute it once /// as-is, and once with a spurious read inserted. Report any UB /// in the target but not in the source. diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 336abff6c52d6..76f735d457cf5 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1243,21 +1243,21 @@ impl VClockAlloc { /// operation. The `ty` parameter is used for diagnostics, letting /// the user know which type was written. pub fn write_non_atomic<'tcx>( - &mut self, + &self, alloc_id: AllocId, access_range: AllocRange, write_type: NaWriteType, ty: Option>, - machine: &mut MiriMachine<'_>, + machine: &MiriMachine<'_>, ) -> InterpResult<'tcx> { let current_span = machine.current_user_relevant_span(); - let global = machine.data_race.as_vclocks_mut().unwrap(); + let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return interp_ok(()); } let (index, mut thread_clocks) = global.active_thread_state_mut(&machine.threads); for (mem_clocks_range, mem_clocks) in - self.alloc_ranges.get_mut().iter_mut(access_range.start, access_range.size) + self.alloc_ranges.borrow_mut().iter_mut(access_range.start, access_range.size) { if let Err(DataRace) = mem_clocks.write_race_detect(&mut thread_clocks, index, write_type, current_span) diff --git a/src/tools/miri/src/data_structures/dedup_range_map.rs b/src/tools/miri/src/data_structures/dedup_range_map.rs index 56e9883b1caaf..d7f8d7367d414 100644 --- a/src/tools/miri/src/data_structures/dedup_range_map.rs +++ b/src/tools/miri/src/data_structures/dedup_range_map.rs @@ -55,6 +55,22 @@ impl DedupRangeMap { .unwrap() } + /// Creates a new [DedupRangeMap] of type `N` by applying `f` to all it's elements. + /// + /// The range for each element remains unchanged, use [DedupRangeMap::iter_mut] to change it. For more fine-grained control, the range is provided to `f`. + pub fn transform(self, mut f: F) -> DedupRangeMap + where + F: FnMut(&ops::Range, T) -> N, + { + DedupRangeMap { + v: self + .v + .into_iter() + .map(|elem| Elem { data: f(&elem.range, elem.data), range: elem.range }) + .collect(), + } + } + /// Provides read-only iteration over everything in the given range. This does /// *not* split items if they overlap with the edges. Do not use this to mutate /// through interior mutability. diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs index a8494eaf0aa49..d6ee23d8373af 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs @@ -4,7 +4,8 @@ use std::mem; fn safe(x: &mut i32, y: &mut i32) { //~[stack]^ ERROR: protect - *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ + //~[tree]^^ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ + *x = 1; *y = 2; } diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr index d26a0fc0586ed..3224109d3822c 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr @@ -1,23 +1,24 @@ -error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | *x = 1; - | ^^^^^^ Undefined Behavior occurred here +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag has state Reserved (conflicted) which forbids this child write access -help: the accessed tag was created here, in the initial state Reserved + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this reborrow (acting as a foreign write access) would cause the protected tag (currently Unique) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | fn safe(x: &mut i32, y: &mut i32) { - | ^ -help: the accessed tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] +LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; + | ^^^^^^ +help: the protected tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | LL | fn safe(x: &mut i32, y: &mut i32) { - | ^ - = help: this transition corresponds to a temporary loss of write permissions until function exit + | ^ = note: stack backtrace: 0: safe at tests/fail/both_borrows/aliasing_mut1.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs index c1320a25cafa4..04d0692a35b32 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs @@ -4,8 +4,9 @@ use std::mem; fn safe(x: &i32, y: &mut i32) { //~[stack]^ ERROR: protect + //~[tree]^^ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ let _v = *x; - *y = 2; //~[tree] ERROR: /write access through .* is forbidden/ + *y = 2; } fn main() { diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr index 77597e4aa8313..4e0cd77627c06 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr @@ -1,23 +1,24 @@ -error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | *y = 2; - | ^^^^^^ Undefined Behavior occurred here +LL | fn safe(x: &i32, y: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag has state Reserved (conflicted) which forbids this child write access -help: the accessed tag was created here, in the initial state Reserved + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this reborrow (acting as a foreign write access) would cause the protected tag (currently Frozen) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | fn safe(x: &i32, y: &mut i32) { - | ^ -help: the accessed tag later transitioned to Reserved (conflicted) due to a foreign read access at offsets [0x0..0x4] +LL | let xref = &mut x; + | ^^^^^^ +help: the protected tag was created here, in the initial state Frozen --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | let _v = *x; - | ^^ - = help: this transition corresponds to a temporary loss of write permissions until function exit +LL | fn safe(x: &i32, y: &mut i32) { + | ^ = note: stack backtrace: 0: safe at tests/fail/both_borrows/aliasing_mut2.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs index 555e4478224c8..0b06799de2ff2 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs @@ -4,7 +4,8 @@ use std::mem; fn safe(x: &mut i32, y: &i32) { //~[stack]^ ERROR: borrow stack - *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ + //~[tree]^^ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ + *x = 1; let _v = *y; } diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr index 32f980da6f7ed..1c84f12baae74 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr @@ -1,23 +1,23 @@ -error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | *x = 1; - | ^^^^^^ Undefined Behavior occurred here +LL | fn safe(x: &mut i32, y: &i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag has state Reserved (conflicted) which forbids this child write access -help: the accessed tag was created here, in the initial state Reserved + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Frozen --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | fn safe(x: &mut i32, y: &i32) { - | ^ -help: the accessed tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] +LL | let xshr = &*xref; + | ^^^^^^ +help: the accessed tag later transitioned to Disabled due to a reborrow (acting as a foreign write access) at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | LL | fn safe(x: &mut i32, y: &i32) { - | ^ - = help: this transition corresponds to a temporary loss of write permissions until function exit + | ^ + = help: this transition corresponds to a loss of read permissions = note: stack backtrace: 0: safe at tests/fail/both_borrows/aliasing_mut3.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs index 22484972f4d1c..a6c4e9d9d612e 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs @@ -1,12 +1,12 @@ //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows -//@[tree]error-in-other-file: /write access through .* is forbidden/ use std::cell::Cell; use std::mem; // Make sure &mut UnsafeCell also is exclusive fn safe(x: &i32, y: &mut Cell) { //~[stack]^ ERROR: protect + //~[tree]^^ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ y.set(1); let _load = *x; } diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr index 70a96197b00a0..81dbd45f6540e 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr @@ -1,34 +1,28 @@ -error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden - --> RUSTLIB/core/src/mem/mod.rs:LL:CC +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | crate::intrinsics::write_via_move(dest, src); - | ^^^ Undefined Behavior occurred here +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) - = help: this foreign write access would cause the protected tag (currently Frozen) to become Disabled + = help: this reborrow (acting as a foreign write access) would cause the protected tag (currently Frozen) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | y.set(1); - | ^^^^^^^^ +LL | let xref = &mut x; + | ^^^^^^ help: the protected tag was created here, in the initial state Frozen --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | LL | fn safe(x: &i32, y: &mut Cell) { | ^ = note: stack backtrace: - 0: std::mem::replace - at RUSTLIB/core/src/mem/mod.rs:LL:CC - 1: std::cell::Cell::replace - at RUSTLIB/core/src/cell.rs:LL:CC - 2: std::cell::Cell::set - at RUSTLIB/core/src/cell.rs:LL:CC - 3: safe + 0: safe at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC - 4: main + 1: main at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs b/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.rs similarity index 65% rename from src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs rename to src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.rs index 37214bebb8285..a7bb8f0594fe9 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.rs +++ b/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.rs @@ -1,3 +1,5 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows // Test that spans displayed in diagnostics identify the function call, not the function // definition, as the location of invalidation due to FnEntry retag. Technically the FnEntry retag // occurs inside the function, but what the user wants to know is which call produced the @@ -7,7 +9,8 @@ fn main() { let z = &mut x as *mut i32; x.do_bad(); unsafe { - let _oof = *z; //~ ERROR: /read access .* tag does not exist in the borrow stack/ + let _oof = *z; //~[stack] ERROR: /read access .* tag does not exist in the borrow stack/ + //~[tree]^ ERROR: /Undefined Behavior: read access through .* at .* is forbidden/ } } diff --git a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr b/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.stack.stderr similarity index 83% rename from src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr rename to src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.stack.stderr index 8e107e7f89a4c..16473236ec645 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/fnentry_invalidation.stderr +++ b/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.stack.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> tests/fail/stacked_borrows/fnentry_invalidation.rs:LL:CC + --> tests/fail/both_borrows/fnentry_invalidation.rs:LL:CC | LL | let _oof = *z; | ^^ this error occurs as part of an access at ALLOC[0x0..0x4] @@ -7,12 +7,12 @@ LL | let _oof = *z; = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: was created by a SharedReadWrite retag at offsets [0x0..0x4] - --> tests/fail/stacked_borrows/fnentry_invalidation.rs:LL:CC + --> tests/fail/both_borrows/fnentry_invalidation.rs:LL:CC | LL | let z = &mut x as *mut i32; | ^^^^^^ help: was later invalidated at offsets [0x0..0x4] by a Unique function-entry retag inside this call - --> tests/fail/stacked_borrows/fnentry_invalidation.rs:LL:CC + --> tests/fail/both_borrows/fnentry_invalidation.rs:LL:CC | LL | x.do_bad(); | ^^^^^^^^^^ diff --git a/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.tree.stderr b/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.tree.stderr new file mode 100644 index 0000000000000..449f787d11398 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/fnentry_invalidation.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/fnentry_invalidation.rs:LL:CC + | +LL | let _oof = *z; + | ^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/fnentry_invalidation.rs:LL:CC + | +LL | let z = &mut x as *mut i32; + | ^^^^^^ +help: the accessed tag later transitioned to Disabled due to a reborrow (acting as a foreign write access) at offsets [0x0..0x4] + --> tests/fail/both_borrows/fnentry_invalidation.rs:LL:CC + | +LL | fn do_bad(&mut self) { + | ^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + +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/illegal_write6.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr index 923dd33a0a17d..c76ac8f3e5af9 100644 --- a/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr @@ -19,12 +19,6 @@ help: the protected tag was created here, in the initial state Reserved | LL | fn foo(a: &mut u32, y: *mut u32) -> u32 { | ^ -help: the protected tag later transitioned to Unique due to a child write access at offsets [0x0..0x4] - --> tests/fail/both_borrows/illegal_write6.rs:LL:CC - | -LL | *a = 1; - | ^^^^^^ - = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference = note: stack backtrace: 0: foo at tests/fail/both_borrows/illegal_write6.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs index 57a7d2f9e7bd9..b8e870b3c842c 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs @@ -3,7 +3,7 @@ //@[stack]error-in-other-file: which is strongly protected //@[tree]error-in-other-file: /deallocation through .* is forbidden/ -struct Newtype<'a>(#[allow(dead_code)] &'a mut i32, #[allow(dead_code)] i32); +struct Newtype<'a>(#[allow(dead_code)] &'a i32, #[allow(dead_code)] i32); fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { dealloc(); @@ -15,7 +15,7 @@ fn main() { #[rustfmt::skip] // I like my newlines unsafe { dealloc_while_running( - Newtype(&mut *ptr, 0), + Newtype(&*ptr, 0), || drop(Box::from_raw(ptr)), ) }; diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr index d7688680e7da2..98181dcd005a6 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected --> RUSTLIB/alloc/src/boxed.rs:LL:CC | LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr index 68b9d1c86d1a8..61479d7319bbb 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr @@ -7,24 +7,18 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) - = help: this deallocation (acting as a foreign write access) would cause the protected tag (currently Reserved (conflicted)) to become Disabled + = help: this deallocation (acting as a foreign write access) would cause the protected tag (currently Frozen) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here --> tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC | LL | || drop(Box::from_raw(ptr)), | ^^^^^^^^^^^^^^^^^^^^^^^^ -help: the protected tag was created here, in the initial state Reserved +help: the protected tag was created here, in the initial state Frozen --> tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC | LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { | ^^ -help: the protected tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] - --> tests/fail/both_borrows/newtype_pair_retagging.rs:LL:CC - | -LL | || drop(Box::from_raw(ptr)), - | ^^^^^^^^^^^^^^^^^^ - = help: this transition corresponds to a temporary loss of write permissions until function exit = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs index 746d04d1af13d..5926120160a55 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs @@ -4,7 +4,7 @@ //@[stack]error-in-other-file: which is strongly protected //@[tree]error-in-other-file: /deallocation through .* is forbidden/ -struct Newtype<'a>(#[allow(dead_code)] &'a mut i32); +struct Newtype<'a>(#[allow(dead_code)] &'a i32); fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { dealloc(); @@ -16,7 +16,7 @@ fn main() { #[rustfmt::skip] // I like my newlines unsafe { dealloc_while_running( - Newtype(&mut *ptr), + Newtype(&*ptr), || drop(Box::from_raw(ptr)), ) }; diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr index b7b0f28126698..ae09c7338da11 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected --> RUSTLIB/alloc/src/boxed.rs:LL:CC | LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr index 5069b1d01cd2b..e85797018975c 100644 --- a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr @@ -7,24 +7,18 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) - = help: this deallocation (acting as a foreign write access) would cause the protected tag (currently Reserved (conflicted)) to become Disabled + = help: this deallocation (acting as a foreign write access) would cause the protected tag (currently Frozen) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here --> tests/fail/both_borrows/newtype_retagging.rs:LL:CC | LL | || drop(Box::from_raw(ptr)), | ^^^^^^^^^^^^^^^^^^^^^^^^ -help: the protected tag was created here, in the initial state Reserved +help: the protected tag was created here, in the initial state Frozen --> tests/fail/both_borrows/newtype_retagging.rs:LL:CC | LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { | ^^ -help: the protected tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] - --> tests/fail/both_borrows/newtype_retagging.rs:LL:CC - | -LL | || drop(Box::from_raw(ptr)), - | ^^^^^^^^^^^^^^^^^^ - = help: this transition corresponds to a temporary loss of write permissions until function exit = note: stack backtrace: 0: as std::ops::Drop>::drop at RUSTLIB/alloc/src/boxed.rs:LL:CC diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr index 3c1038d364bb4..2028acbc851ee 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.tree.stderr @@ -7,7 +7,7 @@ LL | Call(_non_copy = callee(Move(_non_copy)), ReturnTo(after_call), = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) - = help: this reborrow (acting as a foreign read access) would cause the protected tag (currently Unique) to become Disabled + = help: this reborrow (acting as a foreign write access) would cause the protected tag (currently Unique) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here --> tests/fail/function_calls/arg_inplace_locals_alias_ret.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.rs b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.rs deleted file mode 100644 index a21cbab6a9fc1..0000000000000 --- a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.rs +++ /dev/null @@ -1,29 +0,0 @@ -//@compile-flags: -Zmiri-tree-borrows -// This test is the TB counterpart to fail/stacked_borrows/fnentry_invalidation, -// but the SB version passes TB without error. -// An additional write access is inserted so that this test properly fails. - -// Test that spans displayed in diagnostics identify the function call, not the function -// definition, as the location of invalidation due to FnEntry retag. Technically the FnEntry retag -// occurs inside the function, but what the user wants to know is which call produced the -// invalidation. - -fn main() { - let mut x = 0i32; - let z = &mut x as *mut i32; - unsafe { - *z = 1; - } - x.do_bad(); - unsafe { - *z = 2; //~ ERROR: /write access through .* is forbidden/ - } -} - -trait Bad { - fn do_bad(&mut self) { - // who knows - } -} - -impl Bad for i32 {} diff --git a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr deleted file mode 100644 index 54013b27dee3d..0000000000000 --- a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr +++ /dev/null @@ -1,31 +0,0 @@ -error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden - --> tests/fail/tree_borrows/fnentry_invalidation.rs:LL:CC - | -LL | *z = 2; - | ^^^^^^ Undefined Behavior occurred here - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag has state Frozen which forbids this child write access -help: the accessed tag was created here, in the initial state Reserved - --> tests/fail/tree_borrows/fnentry_invalidation.rs:LL:CC - | -LL | let z = &mut x as *mut i32; - | ^^^^^^ -help: the accessed tag later transitioned to Unique due to a child write access at offsets [0x0..0x4] - --> tests/fail/tree_borrows/fnentry_invalidation.rs:LL:CC - | -LL | *z = 1; - | ^^^^^^ - = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference -help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] - --> tests/fail/tree_borrows/fnentry_invalidation.rs:LL:CC - | -LL | x.do_bad(); - | ^ - = help: this transition corresponds to a loss of write permissions - -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/tree_borrows/pass_invalid_mut.rs b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.rs index a22e3118341a1..cc31c76304eed 100644 --- a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.rs +++ b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.rs @@ -6,15 +6,14 @@ // Make sure that we cannot use a `&mut` whose parent got invalidated. // fail/both_borrows/pass_invalid_shr is already checking a forbidden read, // so the new thing that this tests is a forbidden write. -fn foo(nope: &mut i32) { - *nope = 31; //~ ERROR: /write access through .* is forbidden/ +fn foo(_: &mut i32) { //~ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ } fn main() { let x = &mut 42; let xraw = x as *mut _; let xref = unsafe { &mut *xraw }; - *xref = 18; // activate xref + *xref = 18; // activate xref, only difference to SB let _val = unsafe { *xraw }; // invalidate xref for writing foo(xref); } diff --git a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr index 029b0a56f305f..038b7c2896c23 100644 --- a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr @@ -1,18 +1,18 @@ -error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden --> tests/fail/tree_borrows/pass_invalid_mut.rs:LL:CC | -LL | *nope = 31; - | ^^^^^^^^^^ Undefined Behavior occurred here +LL | fn foo(_: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information = help: the accessed tag is a child of the conflicting tag - = help: the conflicting tag has state Frozen which forbids this child write access + = help: the conflicting tag has state Frozen which forbids this reborrow (acting as a child write access) help: the accessed tag was created here --> tests/fail/tree_borrows/pass_invalid_mut.rs:LL:CC | -LL | fn foo(nope: &mut i32) { - | ^^^^ +LL | foo(xref); + | ^^^^ help: the conflicting tag was created here, in the initial state Reserved --> tests/fail/tree_borrows/pass_invalid_mut.rs:LL:CC | @@ -21,7 +21,7 @@ LL | let xref = unsafe { &mut *xraw }; help: the conflicting tag later transitioned to Unique due to a child write access at offsets [0x0..0x4] --> tests/fail/tree_borrows/pass_invalid_mut.rs:LL:CC | -LL | *xref = 18; // activate xref +LL | *xref = 18; // activate xref, only difference to SB | ^^^^^^^^^^ = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference help: the conflicting tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4] diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr index 29397a8fd0bc7..036ed07afa7b1 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr @@ -2,10 +2,10 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| ReIM| └─┬── -| ReIM| ├─┬── -| ReIM| │ └─┬── -| Res | │ └──── Strongly protected +| Act | └─┬── +| Act | ├─┬── +| Act | │ └─┬── +| Act | │ └──── Strongly protected | ReIM| └──── ────────────────────────────────────────────────── error: Undefined Behavior: write access through (y, callee:y, caller:y) at ALLOC[0x0] is forbidden @@ -17,7 +17,7 @@ LL | *y = 1; = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information = help: the accessed tag (y, callee:y, caller:y) is foreign to the protected tag (callee:x) (i.e., it is not a child) - = help: this foreign write access would cause the protected tag (callee:x) (currently Reserved) to become Disabled + = help: this foreign write access would cause the protected tag (callee:x) (currently Unique) to become Disabled = help: protected tags must never be Disabled help: the accessed tag was created here --> tests/fail/tree_borrows/reserved/cell-protected-write.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr index 3bddd2ce1de63..3c09fc899cfcc 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr @@ -2,11 +2,11 @@ Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Res | └─┬── -| Res | ├─┬── -| Res | │ └─┬── -| Res | │ └──── Strongly protected -| Res | └──── +| Act | └─┬── +| Act | ├─┬── +| Act | │ └─┬── +| Act | │ └──── Strongly protected +| Dis | └──── ────────────────────────────────────────────────── error: Undefined Behavior: write access through (y, callee:y, caller:y) at ALLOC[0x0] is forbidden --> tests/fail/tree_borrows/reserved/int-protected-write.rs:LL:CC @@ -16,19 +16,18 @@ LL | *y = 0; | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag (y, callee:y, caller:y) is foreign to the protected tag (callee:x) (i.e., it is not a child) - = help: this foreign write access would cause the protected tag (callee:x) (currently Reserved) to become Disabled - = help: protected tags must never be Disabled -help: the accessed tag was created here + = help: the accessed tag (y, callee:y, caller:y) has state Disabled which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved --> tests/fail/tree_borrows/reserved/int-protected-write.rs:LL:CC | LL | let y = (&mut *n) as *mut _; | ^^^^^^^^^ -help: the protected tag was created here, in the initial state Reserved +help: the accessed tag later transitioned to Disabled due to a reborrow (acting as a foreign write access) at offsets [0x0..0x1] --> tests/fail/tree_borrows/reserved/int-protected-write.rs:LL:CC | LL | unsafe fn write_second(x: &mut u8, y: *mut u8) { | ^ + = help: this transition corresponds to a loss of read and write permissions = note: stack backtrace: 0: main::write_second at tests/fail/tree_borrows/reserved/int-protected-write.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs b/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs index 4ca0fb9433813..fa2ea3198e25e 100644 --- a/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs +++ b/src/tools/miri/tests/fail/tree_borrows/spurious_read.rs @@ -5,6 +5,9 @@ //@compile-flags: -Zmiri-deterministic-concurrency //@compile-flags: -Zmiri-tree-borrows +#![feature(rustc_attrs)] +#![allow(internal_features)] + use std::sync::{Arc, Barrier}; use std::thread; @@ -70,10 +73,11 @@ fn retagx_retagy_retx_writey_rety() { synchronized!(b, "start"); let ptr = ptr; synchronized!(b, "retag x (&mut, protect)"); + #[rustc_no_writable] // restore the old behavior, as we're testing reads here, not writes fn as_mut(x: &mut u8, b: (usize, Arc)) -> *mut u8 { synchronized!(b, "retag y (&mut, protect)"); synchronized!(b, "location where spurious read of x would happen in the target"); - // This is ensuring taht we have UB *without* the spurious read, + // This is ensuring that we have UB *without* the spurious read, // so we don't read here. synchronized!(b, "ret x"); synchronized!(b, "write y"); @@ -97,6 +101,7 @@ fn retagx_retagy_retx_writey_rety() { let ptr = ptr; synchronized!(b, "retag x (&mut, protect)"); synchronized!(b, "retag y (&mut, protect)"); + #[rustc_no_writable] fn as_mut(y: &mut u8, b: (usize, Arc)) -> *mut u8 { synchronized!(b, "location where spurious read of x would happen in the target"); synchronized!(b, "ret x"); diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/as_mut_ptr.rs b/src/tools/miri/tests/fail/tree_borrows/strong_mode/as_mut_ptr.rs new file mode 100644 index 0000000000000..831cf4be39120 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/as_mut_ptr.rs @@ -0,0 +1,18 @@ +// This code no longer works using the strong mode in tree borrows. +// This code tests that. The passing version is in `pass/tree_borrows/strong_mode/as_mut_ptr.rs`. +//@compile-flags: -Zmiri-tree-borrows + +fn main() { + let mut x = ["one", "two", "three"]; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = as_mut_ptr(a); + println!("{:?}", *b); //~ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ +} + +pub const fn as_mut_ptr(x: &mut [&str; 3]) -> *mut str { + x as *mut [&str] as *mut str +} diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/as_mut_ptr.stderr b/src/tools/miri/tests/fail/tree_borrows/strong_mode/as_mut_ptr.stderr new file mode 100644 index 0000000000000..0fa0fed68c207 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/as_mut_ptr.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/strong_mode/as_mut_ptr.rs:LL:CC + | +LL | println!("{:?}", *b); + | ^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/strong_mode/as_mut_ptr.rs:LL:CC + | +LL | let b = unsafe { &mut *ptr }; + | ^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a reborrow (acting as a foreign write access) at offsets [0x0..0x30] + --> tests/fail/tree_borrows/strong_mode/as_mut_ptr.rs:LL:CC + | +LL | pub const fn as_mut_ptr(x: &mut [&str; 3]) -> *mut str { + | ^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/strong_mode/mut_option.rs b/src/tools/miri/tests/fail/tree_borrows/strong_mode/mut_option.rs new file mode 100644 index 0000000000000..985570ac1858d --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/mut_option.rs @@ -0,0 +1,17 @@ +// This tests an interesting edge case for the writable attribute. Even though `_x` is not a mutable borrow, the underlying implementation still behaves like a mutable borrow. Thus, the new UB introduced by the writable attribute also affects this case. This tests shows that behavior. +// A matching test that shows the old behavior is in `pass/tree_borrows/strong_mode/mut_option.rs`. The only difference is the addition of the `#[rustc_no_writable]` attribute. +//@compile-flags: -Zmiri-tree-borrows + +fn main() { + let mut x = 42; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = foo(Some(a)); + println!("{:?}", *b); //~ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ +} + +// Even though `_x` is not a mutable borrow, the writable attribute is still inserted in LLVM (due to the implementation of `Option``) and thus a write is inserted in the semantics. +fn foo(_x: Option<&mut i32>) {} diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/mut_option.stderr b/src/tools/miri/tests/fail/tree_borrows/strong_mode/mut_option.stderr new file mode 100644 index 0000000000000..78dff6a46a5c0 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/mut_option.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/strong_mode/mut_option.rs:LL:CC + | +LL | println!("{:?}", *b); + | ^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/strong_mode/mut_option.rs:LL:CC + | +LL | let b = unsafe { &mut *ptr }; + | ^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a reborrow (acting as a foreign write access) at offsets [0x0..0x4] + --> tests/fail/tree_borrows/strong_mode/mut_option.rs:LL:CC + | +LL | fn foo(_x: Option<&mut i32>) {} + | ^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/strong_mode/ptr_write1.rs b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write1.rs new file mode 100644 index 0000000000000..052f2715c21e3 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write1.rs @@ -0,0 +1,16 @@ +// Tests that UB is detected for reading from a mutable reference by another pointer (as there could be a write on that mutable reference now) +//@compile-flags: -Zmiri-tree-borrows + +fn foo(_x: &mut u8, y: *mut u8) -> u8 { + // spurious write inserted here for x + let val = unsafe { *y }; //~ ERROR: read access + // *x = 42; // we'd like to add this write, so there must already be UB without it because there sure is with it. + val +} + +fn main() { + let mut x = 0u8; + let ptr = &raw mut x; + let res = foo(&mut x, ptr); + assert_eq!(res, 0); +} diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write1.stderr b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write1.stderr new file mode 100644 index 0000000000000..ab9d41a104554 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write1.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: read access through (root of the allocation) at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/strong_mode/ptr_write1.rs:LL:CC + | +LL | let val = unsafe { *y }; + | ^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign read access would cause the protected tag (currently Unique) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> tests/fail/tree_borrows/strong_mode/ptr_write1.rs:LL:CC + | +LL | let ptr = &raw mut x; + | ^^^^^^^^^^ +help: the protected tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/strong_mode/ptr_write1.rs:LL:CC + | +LL | fn foo(_x: &mut u8, y: *mut u8) -> u8 { + | ^^ + = note: stack backtrace: + 0: foo + at tests/fail/tree_borrows/strong_mode/ptr_write1.rs:LL:CC + 1: main + at tests/fail/tree_borrows/strong_mode/ptr_write1.rs:LL:CC + +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/tree_borrows/strong_mode/ptr_write2.rs b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write2.rs new file mode 100644 index 0000000000000..f6300bd719e4b --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write2.rs @@ -0,0 +1,19 @@ +// Tests that UB exists for explicit write, even without writable +//@compile-flags: -Zmiri-tree-borrows + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +#[rustc_no_writable] +fn foo(x: &mut u8, y: *mut u8) -> u8 { + let val = unsafe { *y }; + *x = 42; //~ ERROR: write access + val +} + +fn main() { + let mut x = 0u8; + let ptr = &raw mut x; + let res = foo(&mut x, ptr); + assert_eq!(res, 0); +} diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write2.stderr b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write2.stderr new file mode 100644 index 0000000000000..a5cb15cd34d8e --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/ptr_write2.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: write access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/strong_mode/ptr_write2.rs:LL:CC + | +LL | *x = 42; + | ^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Reserved (conflicted) which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/strong_mode/ptr_write2.rs:LL:CC + | +LL | fn foo(x: &mut u8, y: *mut u8) -> u8 { + | ^ +help: the accessed tag later transitioned to Reserved (conflicted) due to a foreign read access at offsets [0x0..0x1] + --> tests/fail/tree_borrows/strong_mode/ptr_write2.rs:LL:CC + | +LL | let val = unsafe { *y }; + | ^^ + = help: this transition corresponds to a temporary loss of write permissions until function exit + = note: stack backtrace: + 0: foo + at tests/fail/tree_borrows/strong_mode/ptr_write2.rs:LL:CC + 1: main + at tests/fail/tree_borrows/strong_mode/ptr_write2.rs:LL:CC + +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/tree_borrows/strong_mode/rustc_no_writable.rs b/src/tools/miri/tests/fail/tree_borrows/strong_mode/rustc_no_writable.rs new file mode 100644 index 0000000000000..1560a088bad5e --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/rustc_no_writable.rs @@ -0,0 +1,16 @@ +// This code tests that the `#[rustc_no_writable]` attribute and the `-Zno-writable` flag have the desired effect. +// With them, this test should pass, which is tested in `pass/tree_borrows/strong_mode/rustc_no_writable.rs` and `pass/tree_borrows/strong_mode/no_writable.rs`. +//@compile-flags: -Zmiri-tree-borrows + +fn main() { + let mut x = 42; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = foo(a); + println!("{:?}", *b); //~ ERROR: /Undefined Behavior: reborrow through .* at .* is forbidden/ +} + +pub fn foo(_x: &mut i32) {} diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/rustc_no_writable.stderr b/src/tools/miri/tests/fail/tree_borrows/strong_mode/rustc_no_writable.stderr new file mode 100644 index 0000000000000..6cd6351f5fbc1 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/rustc_no_writable.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: reborrow through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/strong_mode/rustc_no_writable.rs:LL:CC + | +LL | println!("{:?}", *b); + | ^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Reserved + --> tests/fail/tree_borrows/strong_mode/rustc_no_writable.rs:LL:CC + | +LL | let b = unsafe { &mut *ptr }; + | ^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a reborrow (acting as a foreign write access) at offsets [0x0..0x4] + --> tests/fail/tree_borrows/strong_mode/rustc_no_writable.rs:LL:CC + | +LL | pub fn foo(_x: &mut i32) {} + | ^^ + = help: this transition corresponds to a loss of read and write permissions + +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/tree_borrows/strong_mode/static_memory_modification.rs b/src/tools/miri/tests/fail/tree_borrows/strong_mode/static_memory_modification.rs new file mode 100644 index 0000000000000..c9277db691a01 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/static_memory_modification.rs @@ -0,0 +1,11 @@ +// Tests that inserting an implicit write to a read-only allocation under the strong mode generates the correct error message +//@compile-flags: -Zmiri-tree-borrows +static X: usize = 5; + +#[allow(mutable_transmutes)] +fn main() { + let x = unsafe { std::mem::transmute::<&usize, &mut usize>(&X) }; + foo(x); +} + +fn foo(_x: &mut usize) {} //~ ERROR: writing to alloc1 which is read-only diff --git a/src/tools/miri/tests/fail/tree_borrows/strong_mode/static_memory_modification.stderr b/src/tools/miri/tests/fail/tree_borrows/strong_mode/static_memory_modification.stderr new file mode 100644 index 0000000000000..2c8440ddd497f --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/strong_mode/static_memory_modification.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: writing to ALLOC which is read-only + --> tests/fail/tree_borrows/strong_mode/static_memory_modification.rs:LL:CC + | +LL | fn foo(_x: &mut usize) {} + | ^^ 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: stack backtrace: + 0: foo + at tests/fail/tree_borrows/strong_mode/static_memory_modification.rs:LL:CC + 1: main + at tests/fail/tree_borrows/strong_mode/static_memory_modification.rs:LL:CC + +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/tree_borrows/subtree_traversal_skipping_diagnostics.rs b/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.rs index 94a3bb805442c..b62a67d5d244b 100644 --- a/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.rs +++ b/src/tools/miri/tests/fail/tree_borrows/subtree_traversal_skipping_diagnostics.rs @@ -3,11 +3,15 @@ // Shows the effect of the optimization of #4008. // The diagnostics change, but not the error itself. +#![feature(rustc_attrs)] +#![allow(internal_features)] + // When this method is called, the tree will be a single line and look like this, // with other_ptr being the root at the top // other_ptr = root : Unique // intermediary : Frozen // an intermediary node // m : Reserved +#[rustc_no_writable] // Force test to have old behavior. This makes sense here as we want to show the changed diagnostic tree and not specifically a special borrow. fn write_to_mut(m: &mut u8, other_ptr: *const u8) { unsafe { std::hint::black_box(*other_ptr); diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs index 18fe931b9e356..3e504be91c9f1 100644 --- a/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.rs @@ -1,5 +1,9 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +#![feature(rustc_attrs)] +#![allow(internal_features)] +#![feature(stmt_expr_attributes)] + /// Checks if we pass a reference derived from a wildcard pointer /// that it gets correctly protected. pub fn main() { @@ -12,7 +16,8 @@ pub fn main() { let wild = int2 as *mut u32; let wild_ref = unsafe { &mut *wild }; - let mut protect = |_arg: &mut u32| { + let mut protect = #[rustc_no_writable] // forces test to have old behavior, thus testing the wanted property + |_arg: &mut u32| { // _arg is a protected pointer with wildcard parent. // ┌────────────┐ diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr index 28deb09664c73..ff8f104ddae7b 100644 --- a/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protected_wildcard.stderr @@ -12,8 +12,7 @@ LL | *ref1 = 13; help: the accessed tag was created here --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC | -LL | let mut protect = |_arg: &mut u32| { - | _______________________^ +LL | / |_arg: &mut u32| { ... | LL | | *ref1 = 13; LL | | }; @@ -21,8 +20,8 @@ LL | | }; help: the protected tag was created here, in the initial state Reserved --> tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC | -LL | let mut protect = |_arg: &mut u32| { - | ^^^^ +LL | |_arg: &mut u32| { + | ^^^^ = note: stack backtrace: 0: main::{closure#0} at tests/fail/tree_borrows/wildcard/protected_wildcard.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_conflicted.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_conflicted.rs index 620cf34d2a088..12994a7564e26 100644 --- a/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_conflicted.rs +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_conflicted.rs @@ -1,5 +1,9 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +#![feature(rustc_attrs)] +#![allow(internal_features)] +#![feature(stmt_expr_attributes)] + /// Checks that wildcard accesses correctly infers the allowed permissions /// on protected conflicted pointers. pub fn main() { @@ -9,7 +13,8 @@ pub fn main() { let ref1 = unsafe { &mut *ptr_base }; let ref2 = unsafe { &mut *ptr_base }; - let protect = |arg: &mut u32| { + let protect = #[rustc_no_writable] // TODO: disable new behavior for now to make test the old thing again. probably have to rewrite this to test the desired behavior instead of disabling new feature + |arg: &mut u32| { // Expose arg. let int = arg as *mut u32 as usize; let wild = int as *mut u32; diff --git a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr index e44f306f7fad1..7ee80765af659 100644 --- a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr @@ -6,7 +6,7 @@ LL | fn add(&mut self, n: u64) -> u64 { | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information - = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child write access) help: the accessed tag was created here, in the initial state Reserved --> tests/fail/tree_borrows/write-during-2phase.rs:LL:CC | diff --git a/src/tools/miri/tests/pass/both_borrows/smallvec.rs b/src/tools/miri/tests/pass/both_borrows/smallvec.rs index fa5cfb03de2a9..800fe397322ed 100644 --- a/src/tools/miri/tests/pass/both_borrows/smallvec.rs +++ b/src/tools/miri/tests/pass/both_borrows/smallvec.rs @@ -5,6 +5,9 @@ //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows +#![feature(rustc_attrs)] +#![allow(internal_features)] + use std::marker::PhantomData; use std::mem::{ManuallyDrop, MaybeUninit}; use std::ptr::NonNull; @@ -24,10 +27,12 @@ impl RawSmallVec { Self { inline: ManuallyDrop::new(inline) } } + #[rustc_no_writable] const fn as_mut_ptr_inline(&mut self) -> *mut T { &raw mut self.inline as *mut T } + #[rustc_no_writable] const unsafe fn as_mut_ptr_heap(&mut self) -> *mut T { self.heap.0.as_ptr() } @@ -77,6 +82,7 @@ impl SmallVec { size_of::() == 0 } + #[rustc_no_writable] pub const fn as_mut_ptr(&mut self) -> *mut T { if self.len.on_heap(Self::is_zst()) { // SAFETY: see above diff --git a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr index 4d77d96776d31..d33bfcdc0097a 100644 --- a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr @@ -9,19 +9,19 @@ Warning: this tree is indicative only. Some tags may have been hidden. Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Res | └─┬── -| Res | └─┬── -| Res | └─┬── -| Res | └──── Strongly protected +| Act | └─┬── +| Act | └─┬── +| Act | └─┬── +| Act | └──── Strongly protected ────────────────────────────────────────────────── ────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act | └─┬── -| Res | └─┬── -| Res | ├─┬── -| Res | │ └─┬── -| Res | │ └──── +| Act | └─┬── +| Frz | ├─┬── +| Frz | │ └─┬── +| Frz | │ └──── | Res | └──── ────────────────────────────────────────────────── ────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/reserved.rs b/src/tools/miri/tests/pass/tree_borrows/reserved.rs index 83e1d9c70ed50..6bb6d2cbb14dc 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reserved.rs +++ b/src/tools/miri/tests/pass/tree_borrows/reserved.rs @@ -1,6 +1,9 @@ // We disable the GC for this test because it would change what is printed. //@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0 +#![feature(rustc_attrs)] +#![allow(internal_features)] + #[path = "../../utils/mod.rs"] #[macro_use] mod utils; @@ -31,6 +34,7 @@ fn print(msg: &str) { eprintln!("{msg}"); } +#[rustc_no_writable] // force test to have old behavior, such that the correct property is tested unsafe fn read_second(x: &mut T, y: *mut u8) { name!(x as *mut T as *mut u8=>1, "caller:x"); name!(x as *mut T as *mut u8, "callee:x"); diff --git a/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs b/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs index 7da20e76229db..8632ed2213c51 100644 --- a/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs +++ b/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs @@ -2,33 +2,10 @@ // These tests fail Stacked Borrows, but pass Tree Borrows. -// The first four have in common that in SB a mutable reborrow is enough to produce +// The first three have in common that in SB a mutable reborrow is enough to produce // write access errors, but in TB an actual write is needed. // A modified version of each is also available that fails Tree Borrows. -mod fnentry_invalidation { - // Copied directly from fail/stacked_borrows/fnentry_invalidation.rs - // Version that fails TB: fail/tree_borrows/fnentry_invalidation.rs - pub fn main() { - let mut x = 0i32; - let z = &mut x as *mut i32; - x.do_bad(); - unsafe { - let _oof = *z; - // In SB this is an error, but in TB the mutable reborrow did - // not invalidate z for reading. - } - } - - trait Bad { - fn do_bad(&mut self) { - // who knows - } - } - - impl Bad for i32 {} -} - mod pass_invalid_mut { // Copied directly from fail/stacked_borrows/pass_invalid_mut.rs // Version that fails TB: fail/tree_borrows/pass_invalid_mut.rs @@ -88,7 +65,6 @@ fn interior_mut_reborrow() { } fn main() { - fnentry_invalidation::main(); pass_invalid_mut::main(); return_invalid_mut::main(); static_memory_modification::main(); diff --git a/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs b/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs index 840832c633cf5..c6105f92078ab 100644 --- a/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs +++ b/src/tools/miri/tests/pass/tree_borrows/spurious_read.rs @@ -5,6 +5,9 @@ //@compile-flags: -Zmiri-deterministic-concurrency //@compile-flags: -Zmiri-tree-borrows +#![feature(rustc_attrs)] +#![allow(internal_features)] + use std::sync::{Arc, Barrier}; use std::thread; @@ -70,6 +73,7 @@ fn retagx_retagy_spuriousx_retx_rety_writey() { synchronized!(b, "start"); let ptr = ptr; synchronized!(b, "retag x (&mut, protect)"); + #[rustc_no_writable] // restore the old behavior, as we're testing reads here, not writes fn as_mut(x: &mut u8, b: (usize, Arc)) -> *mut u8 { synchronized!(b, "retag y (&mut, protect)"); synchronized!(b, "spurious read x"); @@ -95,6 +99,7 @@ fn retagx_retagy_spuriousx_retx_rety_writey() { let ptr = ptr; synchronized!(b, "retag x (&mut, protect)"); synchronized!(b, "retag y (&mut, protect)"); + #[rustc_no_writable] fn as_mut(y: &mut u8, b: (usize, Arc)) -> *mut u8 { synchronized!(b, "spurious read x"); synchronized!(b, "ret x"); diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/as_mut_ptr.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/as_mut_ptr.rs new file mode 100644 index 0000000000000..fa67451ab37e8 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/as_mut_ptr.rs @@ -0,0 +1,22 @@ +// This code should work under the weak mode of tree borrows, enforced by the `rustc_no_writable` flag. +// Under the strong mode, this code no longer passes. This is tested in `fail/tree_borrows/strong_mode/as_mut_ptr.rs`. The only difference is the removal of the `rustc_no_writable` flag. +//@compile-flags: -Zmiri-tree-borrows + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +fn main() { + let mut x = ["one", "two", "three"]; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = as_mut_ptr(a); + println!("{:?}", *b); +} + +#[rustc_no_writable] +pub const fn as_mut_ptr(x: &mut [&str; 3]) -> *mut str { + x as *mut [&str] as *mut str +} diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/as_mut_ptr.stdout b/src/tools/miri/tests/pass/tree_borrows/strong_mode/as_mut_ptr.stdout new file mode 100644 index 0000000000000..24b776a667130 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/as_mut_ptr.stdout @@ -0,0 +1 @@ +["one", "two", "three"] diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/mut_option.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/mut_option.rs new file mode 100644 index 0000000000000..8bcc1c1f627cb --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/mut_option.rs @@ -0,0 +1,19 @@ +// This test shows the old behavior of the test in `fail/tree_borrows/strong_mode/mut_option.rs`. The only difference is the addition of the `#[rustc_no_writable]` attribute. +//@compile-flags: -Zmiri-tree-borrows + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +fn main() { + let mut x = 42; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = foo(Some(a)); + println!("{:?}", *b); +} + +#[rustc_no_writable] +fn foo(_x: Option<&mut i32>) {} diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/mut_option.stdout b/src/tools/miri/tests/pass/tree_borrows/strong_mode/mut_option.stdout new file mode 100644 index 0000000000000..d81cc0710eb6c --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/mut_option.stdout @@ -0,0 +1 @@ +42 diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/no_writable.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/no_writable.rs new file mode 100644 index 0000000000000..ae0bbb14eb5fb --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/no_writable.rs @@ -0,0 +1,16 @@ +// This code tests that the `-Zno-writable` flag has the desired effect. +// Without it, this test should fail, which is tested in `fail/tree_borrows/strong_mode/rustc_no_writable.rs`. +//@compile-flags: -Zmiri-tree-borrows -Zno-writable + +fn main() { + let mut x = 42; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = foo(a); + println!("{:?}", *b); +} + +pub fn foo(_x: &mut i32) {} diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/no_writable.stdout b/src/tools/miri/tests/pass/tree_borrows/strong_mode/no_writable.stdout new file mode 100644 index 0000000000000..d81cc0710eb6c --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/no_writable.stdout @@ -0,0 +1 @@ +42 diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/permissions.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/permissions.rs new file mode 100644 index 0000000000000..c2703dd923314 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/permissions.rs @@ -0,0 +1,17 @@ +// Tests that the permissions are as expected after reborrowing. +// To be precise, this tests that at the start of a function, a mutable reference is Unique. +//@compile-flags: -Zmiri-tree-borrows + +unsafe extern "Rust" { + safe fn miri_get_alloc_id(ptr: *const u8) -> u64; + safe fn miri_print_borrow_state(alloc_id: u64, show_unnamed: bool); +} + +fn bar(x: &mut u8) { + miri_print_borrow_state(miri_get_alloc_id(x), true); +} + +fn main() { + let mut x = 0u8; + bar(&mut x); +} diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/permissions.stderr b/src/tools/miri/tests/pass/tree_borrows/strong_mode/permissions.stderr new file mode 100644 index 0000000000000..1dd871530caad --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/permissions.stderr @@ -0,0 +1,7 @@ +────────────────────────────────────────────── +0.. 1 +| Act | └─┬── +| Act | └─┬── +| Act | └─┬── +| Act | └──── Strongly protected +────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/ptr_write.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/ptr_write.rs new file mode 100644 index 0000000000000..783bbdb9f3c55 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/ptr_write.rs @@ -0,0 +1,20 @@ +// Shows that without spurious writes, `tests/fail/tree_borrows/strong_mode/ptr_write1.rs` would pass +//@compile-flags: -Zmiri-tree-borrows + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +#[rustc_no_writable] +fn foo(_x: &mut u8, y: *mut u8) -> u8 { + // spurious write inserted here for x + let val = unsafe { *y }; + // *x = 42; // we'd like to add this write, so there must already be UB without it because there sure is with it. + val +} + +fn main() { + let mut x = 0u8; + let ptr = &raw mut x; + let res = foo(&mut x, ptr); + assert_eq!(res, 0); +} diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/rustc_no_writable.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/rustc_no_writable.rs new file mode 100644 index 0000000000000..cdd4451201b8f --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/rustc_no_writable.rs @@ -0,0 +1,20 @@ +// This code tests that the `#[rustc_no_writable]` attribute has the desired effect. +// Without it, this test should fail, which is tested in `fail/tree_borrows/strong_mode/rustc_no_writable.rs`. +//@compile-flags: -Zmiri-tree-borrows + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +fn main() { + let mut x = 42; + + let ptr = std::ptr::from_mut(&mut x); + let a = unsafe { &mut *ptr }; + let b = unsafe { &mut *ptr }; + + let _c = foo(a); + println!("{:?}", *b); +} + +#[rustc_no_writable] +pub fn foo(_x: &mut i32) {} diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/rustc_no_writable.stdout b/src/tools/miri/tests/pass/tree_borrows/strong_mode/rustc_no_writable.stdout new file mode 100644 index 0000000000000..d81cc0710eb6c --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/rustc_no_writable.stdout @@ -0,0 +1 @@ +42 diff --git a/src/tools/miri/tests/pass/tree_borrows/strong_mode/writable.rs b/src/tools/miri/tests/pass/tree_borrows/strong_mode/writable.rs new file mode 100644 index 0000000000000..b35b3076c1307 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/strong_mode/writable.rs @@ -0,0 +1,29 @@ +// Simple test that checks if the writeable flag works. +//@compile-flags: -Zmiri-tree-borrows + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +fn main() { + let mut x = 1; + foo(&mut x); + assert_eq!(x, 2); + + let bar = Bar {}; + bar.foo(&mut x); + assert_eq!(x, 3); +} + +#[rustc_no_writable] +fn foo(x: &mut i32) { + *x += 1; +} + +struct Bar {} + +impl Bar { + #[rustc_no_writable] + fn foo(&self, x: &mut i32) { + crate::foo(x) + } +} diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs index 73392543f7d2a..012119a2272f0 100644 --- a/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/undetected_ub.rs @@ -3,7 +3,6 @@ pub fn main() { uncertain_provenance(); - protected_exposed(); cross_tree_update_older_invalid_exposed(); } @@ -53,58 +52,6 @@ pub fn uncertain_provenance() { //ref2 : UB Frozen } -/// If a reference is protected, then all foreign writes to it cause UB. -/// This effectively means any write needs to happen through a child of -/// the protected reference. -/// With this information we could further narrow the possible candidates -/// for a wildcard write. -/// However, currently tree borrows doesn't do this, so this test has UB -/// that isn't detected. -pub fn protected_exposed() { - let mut x: u32 = 42; - - let ptr_base = &mut x as *mut u32; - let ref1 = unsafe { &mut *ptr_base }; - let ref2 = unsafe { &mut *ptr_base }; - - let _int2 = ref2 as *mut u32 as usize; - - fn protect(ref3: &mut u32) { - let int3 = ref3 as *mut u32 as usize; - - // ┌────────────┐ - // │ │ - // │ ptr_base ├──────────────┐ - // │ │ │ - // └──────┬─────┘ │ - // │ │ - // │ │ - // ▼ ▼ - // ┌────────────┐ ┌────────────┐ - // │ │ │ │ - // │ ref1(Res) │ │ ref2(Res)* │ - // │ │ │ │ - // └──────┬─────┘ └────────────┘ - // │ - // │ - // ▼ - // ┌────────────┐ - // │ │ - // │ ref3(Res)* │ - // │ │ - // └────────────┘ - - // Since ref3 is protected, we could know that every write from outside it will be UB. - // This means we know that the access is through ref3, disabling ref2. - let wild = int3 as *mut u32; - unsafe { wild.write(13) } - } - protect(ref1); - - // ref2 should be disabled, so this read causes UB, but we currently don't detect this. - let _fail = *ref2; -} - /// Checks how accesses from one subtree affect other subtrees. /// This test shows an example where we don't update a node whose exposed /// children are greater than `max_local_tag`. diff --git a/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs b/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs index 01385313dc1ed..3b801e720280e 100644 --- a/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs +++ b/src/tools/miri/tests/pass/tree_borrows/wildcard/wildcard.rs @@ -1,5 +1,9 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +#![feature(rustc_attrs)] +#![allow(internal_features)] +#![feature(stmt_expr_attributes)] + pub fn main() { multiple_exposed_siblings(); multiple_exposed_child(); @@ -132,7 +136,8 @@ fn protector_conflicted_release() { let ref1 = unsafe { &mut *ptr_base }; let ref2 = unsafe { &mut *ptr_base }; - let protect = |arg: &mut u32| { + let protect = #[rustc_no_writable] // forces test to have old behavior, thus testing the wanted property + |arg: &mut u32| { // Expose arg. let int = arg as *mut u32 as usize; let wild = int as *mut u32;