From f379b40190c8469e2bd6646aa60bee9fb4756b6d Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 23 Dec 2025 04:59:25 +0000 Subject: [PATCH 1/9] Prepare for merging from rust-lang/rust This updates the rust-version file to a0c97e3255e2b9140f131baec1c93eef57640d21. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 024cbd2852073..596e757d3fcc7 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -cb79c42008b970269f6a06b257e5f04b93f24d03 +a0c97e3255e2b9140f131baec1c93eef57640d21 From b1dbc2403e2e8aeaf558d5ca2afcd5da2bc9ef84 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 24 Dec 2025 04:59:20 +0000 Subject: [PATCH 2/9] Prepare for merging from rust-lang/rust This updates the rust-version file to 8796b3b8b4ac6f38a80bf80ce89dd7bd7f92edd7. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 596e757d3fcc7..329ee6a1bad5d 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -a0c97e3255e2b9140f131baec1c93eef57640d21 +8796b3b8b4ac6f38a80bf80ce89dd7bd7f92edd7 From 519e57d78ea4c49d39d1251a204465611c8b6727 Mon Sep 17 00:00:00 2001 From: Roy Ammerschuber Date: Tue, 2 Dec 2025 09:57:12 +0100 Subject: [PATCH 3/9] Tree Visitor: only skip possible children during protector end access --- .../src/borrow_tracker/tree_borrows/tree.rs | 56 ++++++++++++- .../wildcard/protector_release.rs | 79 ++++++++++++++++++ .../wildcard/protector_release.stderr | 25 ++++++ .../wildcard/protector_release2.rs | 81 +++++++++++++++++++ .../wildcard/protector_release2.stderr | 25 ++++++ 5 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.rs create mode 100644 src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.stderr create mode 100644 src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.rs create mode 100644 src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.stderr 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 900e9c3729c84..2e96c0c61cdb7 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -661,6 +661,7 @@ impl<'tcx> Tree { global, ChildrenVisitMode::VisitChildrenOfAccessed, &diagnostics, + /* min_exposed_child */ None, // only matters for protector end access, )?; } interp_ok(()) @@ -686,6 +687,13 @@ impl<'tcx> Tree { let source_idx = self.tag_mapping.get(&tag).unwrap(); + let min_exposed_child = if self.roots.len() > 1 { + LocationTree::get_min_exposed_child(source_idx, &self.nodes) + } else { + // There's no point in computing this when there is just one tree. + None + }; + // This is a special access through the entire allocation. // It actually only affects `accessed` locations, so we need // to filter on those before initiating the traversal. @@ -716,6 +724,7 @@ impl<'tcx> Tree { global, ChildrenVisitMode::SkipChildrenOfAccessed, &diagnostics, + min_exposed_child, )?; } } @@ -876,9 +885,36 @@ impl Tree { } impl<'tcx> LocationTree { + /// Returns the smallest exposed tag, if any, that is a transitive child of `root`. + fn get_min_exposed_child(root: UniIndex, nodes: &UniValMap) -> Option { + // We cannot use the wildcard datastructure to improve this lookup. This is because + // the datastructure only tracks enabled nodes and we need to also consider disabled ones. + let mut stack = vec![root]; + let mut min_tag = None; + while let Some(idx) = stack.pop() { + let node = nodes.get(idx).unwrap(); + if min_tag.is_some_and(|min| min < node.tag) { + // The minimum we found before is bigger than this tag, and therefore + // also bigger than all its children, so we can skip this subtree. + continue; + } + stack.extend_from_slice(node.children.as_slice()); + if node.is_exposed { + min_tag = match min_tag { + Some(prev) if prev < node.tag => Some(prev), + _ => Some(node.tag), + }; + } + } + min_tag + } + /// Performs an access on this location. /// * `access_source`: The index, if any, where the access came from. /// * `visit_children`: Whether to skip updating the children of `access_source`. + /// * `min_exposed_child`: The tag of the smallest exposed (transitive) child of the accessed node. + /// This is only used with `visit_children == SkipChildrenOfAccessed`, where we need to skip children + /// of the accessed node. fn perform_access( &mut self, roots: impl Iterator, @@ -888,6 +924,7 @@ impl<'tcx> LocationTree { global: &GlobalState, visit_children: ChildrenVisitMode, diagnostics: &DiagnosticInfo, + min_exposed_child: Option, ) -> InterpResult<'tcx> { let accessed_root = if let Some(idx) = access_source { Some(self.perform_normal_access( @@ -906,11 +943,22 @@ impl<'tcx> LocationTree { }; let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag); - if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) { - // FIXME: approximate which roots could be children of the accessed node and only skip them instead of all other trees. - return interp_ok(()); - } for root in roots { + let tag = nodes.get(root).unwrap().tag; + // On a protector release access we have to skip the children of the accessed tag. + // However, if the tag has exposed children then some of the wildcard subtrees could + // also be children of the accessed node and would also need to be skipped. We can + // narrow down which wildcard trees might be children by comparing their root tag to the + // minimum exposed child of the accessed node. As the parent tag is always smaller + // than the child tag this means we only need to skip subtrees with a root tag larger + // than `min_exposed_child`. Once we find such a root, we can leave the loop because roots + // are sorted by tag. + if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) + && let Some(min_exposed_child) = min_exposed_child + && tag > min_exposed_child + { + break; + } // We don't perform a wildcard access on the tree we already performed a // normal access on. if Some(root) == accessed_root { diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.rs new file mode 100644 index 0000000000000..d1bb9d781575f --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.rs @@ -0,0 +1,79 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +use std::cell::UnsafeCell; + +/// This is a variant of the test in `../protector-write-lazy.rs`, but with +/// wildcard references. +/// Checks that a protector release access correctly determines that certain tags +/// cannot be children of the protected tag and that it updates them accordingly. +/// +/// For this version we know the tag is not a child because its wildcard root has +/// a smaller tag then the released reference. +pub fn main() { + // We need two locations so that we can create a new reference + // that is foreign to an already active tag. + let mut x: UnsafeCell<[u32; 2]> = UnsafeCell::new([32, 33]); + let ref1 = &mut x; + let cell_ptr = ref1.get() as *mut u32; + + let int = ref1 as *mut UnsafeCell<[u32; 2]> as usize; + let wild = int as *mut UnsafeCell; + + let ref2 = unsafe { &mut *cell_ptr }; + + // `ref3` gets created before the protected ref `arg4`. + let ref3 = unsafe { &mut *wild.wrapping_add(1) }; + + let protect = |arg4: &mut u32| { + // Activates arg4. This would disable ref3 at [0] if it wasn't a cell. + *arg4 = 41; + + // Creates an exposed child of arg4. + let ref5 = &mut *arg4; + let _int = ref5 as *mut u32 as usize; + + // This creates ref6 from ref3 at [1], so that it doesn't disable arg4 at [0]. + let ref6 = unsafe { &mut *ref3.get() }; + + // ┌───────────┐ + // │ ref1* │ + // │ Cel │ Cel │ * + // └─────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ ref2│ │ │ │ ref3│ + // │ Act │ Res │ │ Cel │ Cel │ + // └─────┬─────┘ └─────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ arg4│ │ │ │ ref6│ + // │ Act │ Res │ │ Res │ Res │ + // └─────┬─────┘ └───────────┘ + // │ + // │ + // ▼ + // ┌───────────┐ + // │ref5*| │ + // │ Res │ Res │ + // └───────────┘ + + // Creates a pointer to [0] with the provenance of ref6. + return (ref6 as *mut u32).wrapping_sub(1); + + // Protector release on arg4 happens here. + // This should cause a foreign write on all foreign nodes, + // unless they could be a child of arg4. + // arg4 has an exposed child, so some tags with a wildcard + // ancestor could be its children. + // However, the root of ref6 was created before arg4, so it + // cannot be a child of it. So it should get disabled + // (at location [0]). + }; + // ptr has provenance of ref6 + let ptr = protect(ref2); + // ref6 is disabled at [0]. + let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.stderr new file mode 100644 index 0000000000000..cd25ef40aad19 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protector_release.rs:LL:CC + | +LL | let _fail = unsafe { *ptr }; + | ^^^^ 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/tree_borrows/wildcard/protector_release.rs:LL:CC + | +LL | let ref6 = unsafe { &mut *ref3.get() }; + | ^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a protector release (acting as a foreign write access) on every location previously accessed by this tag + --> tests/fail/tree_borrows/wildcard/protector_release.rs:LL:CC + | +LL | }; + | ^ + = 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/wildcard/protector_release2.rs b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.rs new file mode 100644 index 0000000000000..7bef1cb2665ed --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.rs @@ -0,0 +1,81 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance +use std::cell::UnsafeCell; + +/// This is a variant of the test in `../protector-write-lazy.rs`, but with +/// wildcard references. +/// Checks that a protector release access correctly determines that certain tags +/// cannot be children of the protected tag and that it updates them accordingly. +/// +/// For this version we know the tag is not a child because its wildcard root has +/// a smaller tag then the exposed child of the protected tag. +/// So this test checks that we don't just compare with the accessed tag but instead +/// find the smallest exposed child. +pub fn main() { + // We need two locations so that we can create a new reference + // that is foreign to an already active tag. + let mut x: UnsafeCell<[u32; 2]> = UnsafeCell::new([32, 33]); + let ref1 = &mut x; + let cell_ptr = ref1.get() as *mut u32; + + let int = ref1 as *mut UnsafeCell<[u32; 2]> as usize; + let wild = int as *mut UnsafeCell; + + let ref2 = unsafe { &mut *cell_ptr }; + + let protect = |arg3: &mut u32| { + // `ref4` gets created after the protected ref `arg3` but before the exposed `ref5`. + let ref4 = unsafe { &mut *wild.wrapping_add(1) }; + + // Activates arg4. This would disable ref3 at [0] if it wasn't a cell. + *arg3 = 41; + + // Creates an exposed child of arg3. + let ref5 = &mut *arg3; + let _int = ref5 as *mut u32 as usize; + + // This creates ref6 from ref4 at [1], so that it doesn't disable arg3 at [0]. + let ref6 = unsafe { &mut *ref4.get() }; + + // ┌───────────┐ + // │ ref1* │ + // │ Cel │ Cel │ * + // └─────┬─────┘ │ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ ref2| │ │ | ref4│ + // │ Act │ Res │ │ Cel │ Cel │ + // └─────┬─────┘ └─────┬─────┘ + // │ │ + // │ │ + // ▼ ▼ + // ┌───────────┐ ┌───────────┐ + // │ arg3| │ │ | ref6│ + // │ Act │ Res │ │ Res │ Res │ + // └─────┬─────┘ └───────────┘ + // │ + // │ + // ▼ + // ┌───────────┐ + // │ref5*| │ + // │ Res │ Res │ + // └───────────┘ + + // Creates a pointer to [0] with the provenance of ref6. + return (ref6 as *mut u32).wrapping_sub(1); + + // Protector release on arg3 happens here. + // This should cause a foreign write on all foreign nodes, + // unless they could be a child of arg3. + // arg3 has an exposed child, so some tags with a wildcard + // ancestor could be its children. + // However, the root of ref6 was created before the exposed + // child ref5, so it cannot be a child of it. So it should + // get disabled (at location [0]). + }; + // ptr has provenance of ref6 + let ptr = protect(ref2); + // ref6 is disabled at [0]. + let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.stderr b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.stderr new file mode 100644 index 0000000000000..cd5f42aa0484b --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/wildcard/protector_release2.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: read access through at ALLOC[0x0] is forbidden + --> tests/fail/tree_borrows/wildcard/protector_release2.rs:LL:CC + | +LL | let _fail = unsafe { *ptr }; + | ^^^^ 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/tree_borrows/wildcard/protector_release2.rs:LL:CC + | +LL | let ref6 = unsafe { &mut *ref4.get() }; + | ^^^^^^^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a protector release (acting as a foreign write access) on every location previously accessed by this tag + --> tests/fail/tree_borrows/wildcard/protector_release2.rs:LL:CC + | +LL | }; + | ^ + = 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 + From 634251cba8edb4ebcd01e330c7241e14d76322a1 Mon Sep 17 00:00:00 2001 From: Devon Loehr Date: Fri, 26 Dec 2025 15:40:40 +0000 Subject: [PATCH 4/9] Accommodate upstream PassPlugin rename --- compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index 95cbec1b37b4f..733f5fd0df0af 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -23,7 +23,11 @@ #include "llvm/MC/TargetRegistry.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Passes/PassBuilder.h" +#if LLVM_VERSION_GE(22, 0) +#include "llvm/Plugins/PassPlugin.h" +#else #include "llvm/Passes/PassPlugin.h" +#endif #include "llvm/Passes/StandardInstrumentations.h" #include "llvm/Support/CBindingWrapping.h" #include "llvm/Support/FileSystem.h" From 84d4d70e63552acaff636b9cc0c8206a892cd72b Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 27 Dec 2025 04:58:15 +0000 Subject: [PATCH 5/9] Prepare for merging from rust-lang/rust This updates the rust-version file to 38ed7700e7ba3adb7af96e3dcb2ba6dfa3a0c951. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 329ee6a1bad5d..e33a484f148fa 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -8796b3b8b4ac6f38a80bf80ce89dd7bd7f92edd7 +38ed7700e7ba3adb7af96e3dcb2ba6dfa3a0c951 From b5b46c3b0f899d0954c28ead75de3e35a247d0b9 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 27 Dec 2025 15:09:36 +0100 Subject: [PATCH 6/9] show a warning when combing native-lib mode and many-seeds --- src/tools/miri/src/bin/miri.rs | 27 +++++++++++++++++---------- src/tools/miri/src/diagnostics.rs | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index efe707156c40c..19fbf90246c93 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -710,20 +710,18 @@ fn main() { if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict { fatal_error!("strict provenance is not compatible with calling native functions"); } + // Native calls and many-seeds are an "intersting" combination. + if !miri_config.native_lib.is_empty() && many_seeds.is_some() { + eprintln!( + "warning: `-Zmiri-many-seeds` runs multiple instances of the program in the same address space, \ + so if the native library has global state, it will leak across execution bundaries" + ); + } // You can set either one seed or many. if many_seeds.is_some() && miri_config.seed.is_some() { fatal_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set"); } - - // Ensure we have parallelism for many-seeds mode. - if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) { - // Clamp to 20 threads; things get a less efficient beyond that due to lock contention. - let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(20); - rustc_args.push(format!("-Zthreads={threads}")); - } - let many_seeds = - many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going }); - + // We cannot emulate weak memory without the data race detector. if miri_config.weak_memory_emulation && !miri_config.data_race_detector { fatal_error!( "Weak memory emulation cannot be enabled when the data race detector is disabled" @@ -737,6 +735,15 @@ fn main() { fatal_error!("Invalid settings: {err}"); } + // Ensure we have parallelism for many-seeds mode. + if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) { + // Clamp to 20 threads; things get a less efficient beyond that due to lock contention. + let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(20); + rustc_args.push(format!("-Zthreads={threads}")); + } + let many_seeds = + many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going }); + debug!("rustc arguments: {:?}", rustc_args); debug!("crate arguments: {:?}", miri_config.args); if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing { diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 189f493a5ed82..10467fa79d9e7 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -548,7 +548,7 @@ pub fn report_leaks<'tcx>( /// We want to present a multi-line span message for some errors. Diagnostics do not support this /// directly, so we pass the lines as a `Vec` and display each line after the first with an /// additional `span_label` or `note` call. -pub fn report_msg<'tcx>( +fn report_msg<'tcx>( diag_level: DiagLevel, title: String, span_msg: Vec, From 4e2ad8a12abb7b5f3c6f19437dbec91f94fc7f45 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sun, 28 Dec 2025 05:02:03 +0000 Subject: [PATCH 7/9] Prepare for merging from rust-lang/rust This updates the rust-version file to 23d01cd2412583491621ab1ca4f1b01e37d11e39. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index e33a484f148fa..6a2835bc2d9eb 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -38ed7700e7ba3adb7af96e3dcb2ba6dfa3a0c951 +23d01cd2412583491621ab1ca4f1b01e37d11e39 From 2d35541eb5e04b018f7859600ea7a01c575ba3ac Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Mon, 29 Dec 2025 05:04:35 +0000 Subject: [PATCH 8/9] Prepare for merging from rust-lang/rust This updates the rust-version file to 7fefa09b90ca57b8a0e0e4717d672d38a0ae58b5. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 6a2835bc2d9eb..d32b6d0d2fc73 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -23d01cd2412583491621ab1ca4f1b01e37d11e39 +7fefa09b90ca57b8a0e0e4717d672d38a0ae58b5 From a6c2e50e8241d9e86fb428f163f03f8e8ea4feb6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 29 Dec 2025 12:58:15 +0100 Subject: [PATCH 9/9] fix ICE for particular data race situations --- src/tools/miri/src/concurrency/data_race.rs | 7 +++-- .../miri/src/concurrency/vector_clock.rs | 12 ++++++++- .../data_race/mixed_size_read_write_read.rs | 27 +++++++++++++++++++ .../mixed_size_read_write_read.stderr | 26 ++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs create mode 100644 src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.stderr diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 75c1d0f6a798e..c18b780998606 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1116,6 +1116,7 @@ impl VClockAlloc { { (AccessType::AtomicStore, idx, &atomic.write_vector) } else if !access.is_atomic() && + !access.is_read() && let Some(atomic) = mem_clocks.atomic() && let Some(idx) = Self::find_gt_index(&atomic.read_vector, &active_clocks.clock) { @@ -1124,7 +1125,7 @@ impl VClockAlloc { } else if mem_clocks.write.1 > active_clocks.clock[mem_clocks.write.0] { write_clock = mem_clocks.write(); (AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock) - } else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) { + } else if !access.is_read() && let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) { (AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read) // Finally, mixed-size races. } else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != Some(access_size) { @@ -1157,7 +1158,9 @@ impl VClockAlloc { assert!(!involves_non_atomic); Some("overlapping unsynchronized atomic accesses must use the same access size") } else if access.is_read() && other_access.is_read() { - panic!("there should be no same-size read-read races") + panic!( + "there should be no same-size read-read races\naccess: {access:?}\nother_access: {other_access:?}" + ) } else { None }; diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs index 494e7922d2b2b..d7ea3b0c6ce3d 100644 --- a/src/tools/miri/src/concurrency/vector_clock.rs +++ b/src/tools/miri/src/concurrency/vector_clock.rs @@ -48,7 +48,7 @@ const SMALL_VECTOR: usize = 4; /// The time-stamps recorded in the data-race detector consist of both /// a 32-bit unsigned integer which is the actual timestamp, and a `Span` /// so that diagnostics can report what code was responsible for an operation. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] pub(super) struct VTimestamp { /// The lowest bit indicates read type, the rest is the time. /// `1` indicates a retag read, `0` a regular read. @@ -56,6 +56,16 @@ pub(super) struct VTimestamp { pub span: Span, } +impl Debug for VTimestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VTimestamp") + .field("time", &self.time()) + .field("read_type", &self.read_type()) + .field("span", &self.span) + .finish() + } +} + impl VTimestamp { pub const ZERO: VTimestamp = VTimestamp::new(0, NaReadType::Read, DUMMY_SP); diff --git a/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs b/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs new file mode 100644 index 0000000000000..c84895799b695 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.rs @@ -0,0 +1,27 @@ +//@compile-flags: -Zmiri-deterministic-concurrency +// A case that is not covered by `mixed_size_read_write`. +#![feature(ptr_as_ref_unchecked)] + +use std::sync::atomic::*; +use std::thread; + +fn main() { + let data = AtomicI32::new(0); + + thread::scope(|s| { + s.spawn(|| unsafe { + let _val = (&raw const data).read(); + let _val = (&raw const data).cast::().as_ref_unchecked().compare_exchange( + 0, + 1, + Ordering::Relaxed, + Ordering::Relaxed, + ); + thread::yield_now(); + unreachable!(); + }); + s.spawn(|| { + let _val = data.load(Ordering::Relaxed); //~ERROR: /Race condition detected between \(1\) 1-byte atomic store .* and \(2\) 4-byte atomic load/ + }); + }); +} diff --git a/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.stderr b/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.stderr new file mode 100644 index 0000000000000..f1884bf404f15 --- /dev/null +++ b/src/tools/miri/tests/fail/data_race/mixed_size_read_write_read.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: Race condition detected between (1) 1-byte atomic store on thread `unnamed-ID` and (2) 4-byte atomic load on thread `unnamed-ID` at ALLOC + --> tests/fail/data_race/mixed_size_read_write_read.rs:LL:CC + | +LL | ... let _val = data.load(Ordering::Relaxed); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (2) just happened here + | +help: and (1) occurred earlier here + --> tests/fail/data_race/mixed_size_read_write_read.rs:LL:CC + | +LL | let _val = (&raw const data).cast::().as_ref_unchecked().compare_exchange( + | ________________________^ +LL | | 0, +LL | | 1, +LL | | Ordering::Relaxed, +LL | | Ordering::Relaxed, +LL | | ); + | |_____________^ + = help: overlapping unsynchronized atomic accesses must use the same access size + = help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error +