From 8201a7b4bd655461299236b4e7b815411f1a42e5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 6 Sep 2026 10:57:37 +0200 Subject: [PATCH 1/3] give noalias back to refs in closures --- compiler/rustc_middle/src/ty/sty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index fe569bf282d36..103d9c37a2297 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -2190,7 +2190,7 @@ impl<'tcx> Ty<'tcx> { def.flags().contains(AdtFlags::IS_MAYBE_DANGLING) || def.flags().contains(AdtFlags::IS_MANUALLY_DROP) } - ty::Closure(..) | ty::Coroutine(..) | ty::CoroutineClosure(..) => true, + ty::Coroutine(..) | ty::CoroutineClosure(..) => true, _ => false, } } From b81ddc962702e4b908fed61282c607d2150c3a56 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 23 Sep 2026 18:09:51 +0200 Subject: [PATCH 2/3] Revert "remove no-longer-needed MaybeDangling from thread spawning" This reverts commit 95d43bf5355ae272eb6b9b33202a3a8cae4fca4c. Now that closures have field retagging again, we need this MaybeDangling again. --- library/std/src/thread/lifecycle.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/std/src/thread/lifecycle.rs b/library/std/src/thread/lifecycle.rs index c22b97c39ecd5..b435f7185f043 100644 --- a/library/std/src/thread/lifecycle.rs +++ b/library/std/src/thread/lifecycle.rs @@ -7,6 +7,7 @@ use super::thread::Thread; use super::{Result, spawnhook}; use crate::cell::UnsafeCell; use crate::marker::PhantomData; +use crate::mem::MaybeDangling; use crate::sync::Arc; use crate::sync::atomic::{Atomic, AtomicUsize, Ordering}; use crate::sys::{AsInner, IntoInner, thread as imp}; @@ -56,9 +57,14 @@ where Arc::new(Packet { scope: scope_data, result: UnsafeCell::new(None), _marker: PhantomData }); let their_packet = my_packet.clone(); + // Pass `f` in `MaybeDangling` because actually that closure might *run longer than the lifetime of `F`*. + // See for more details. + let f = MaybeDangling::new(f); + // The entrypoint of the Rust thread, after platform-specific thread // initialization is done. let rust_start = move || { + let f = f.into_inner(); let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| { crate::sys::backtrace::__rust_begin_short_backtrace(|| hooks.inherit_and_run()); crate::sys::backtrace::__rust_begin_short_backtrace(f) From a18393e284772b14529f788bbd72ad5c9ca57795 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 23 Sep 2026 18:20:40 +0200 Subject: [PATCH 3/3] update miri tests --- .../fail/async-shared-mutable.stack.stderr | 8 +++- .../fail/async-shared-mutable.tree.stderr | 8 +++- .../fail/both_borrows/closure-captured-ref.rs | 18 ++++++++ .../closure-captured-ref.stack.stderr | 26 ++++++++++++ .../closure-captured-ref.tree.stderr | 41 +++++++++++++++++++ .../tests/pass/both_borrows/maybe_dangling.rs | 14 ------- 6 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 src/tools/miri/tests/fail/both_borrows/closure-captured-ref.rs create mode 100644 src/tools/miri/tests/fail/both_borrows/closure-captured-ref.stack.stderr create mode 100644 src/tools/miri/tests/fail/both_borrows/closure-captured-ref.tree.stderr diff --git a/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr b/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr index 4435541bc0a1c..bdd004d5da99f 100644 --- a/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr +++ b/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr @@ -9,8 +9,12 @@ LL | *x = 1; help: was created by a Unique retag at offsets [RANGE] --> tests/fail/async-shared-mutable.rs:LL:CC | -LL | let x = &mut 0u8; - | ^^^^^^^^ +LL | / core::future::poll_fn(move |_| { +LL | | *x = 1; +LL | | Poll::<()>::Pending +LL | | }) +LL | | .await + | |______________^ help: was later invalidated at offsets [RANGE] by a SharedReadOnly retag --> tests/fail/async-shared-mutable.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr index bbb62a7e27b2b..f9e75082758dd 100644 --- a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr +++ b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr @@ -10,8 +10,12 @@ LL | *x = 1; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/async-shared-mutable.rs:LL:CC | -LL | let x = &mut 0u8; - | ^^^^^^^^ +LL | / core::future::poll_fn(move |_| { +LL | | *x = 1; +LL | | Poll::<()>::Pending +LL | | }) +LL | | .await + | |______________^ help: the accessed tag later transitioned to Unique due to a child write access at offsets [RANGE] --> tests/fail/async-shared-mutable.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.rs b/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.rs new file mode 100644 index 0000000000000..3b4c7f425ee36 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.rs @@ -0,0 +1,18 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +//@[stack]error-in-other-file: protected +//@[tree]error-in-other-file: forbidden + +// A reference in a closure is treated like a reference in a struct. +fn main() { + fn invoke(f: impl FnOnce()) { + // The closure has captured a reference that will be freed while `invoke` runs. + f() + } + + let p = Box::leak(Box::new(0i32)); + invoke(move || { + drop(unsafe { Box::from_raw(p) }); + }); +} diff --git a/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.stack.stderr b/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.stack.stderr new file mode 100644 index 0000000000000..d7b2888106650 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.stack.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: deallocating while item [Unique for ] is strongly protected + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | self.1.deallocate(From::from(ptr.cast()), layout); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = 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 + = note: stack backtrace: + 0: as std::ops::Drop>::drop + at RUSTLIB/alloc/src/boxed.rs:LL:CC + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 2: std::mem::drop::> + at RUSTLIB/core/src/mem/mod.rs:LL:CC + 3: main::{closure#0} + at tests/fail/both_borrows/closure-captured-ref.rs:LL:CC + 4: main::invoke::<{closure@tests/fail/both_borrows/closure-captured-ref.rs:LL:CC}> + at tests/fail/both_borrows/closure-captured-ref.rs:LL:CC + 5: main + at tests/fail/both_borrows/closure-captured-ref.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/both_borrows/closure-captured-ref.tree.stderr b/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.tree.stderr new file mode 100644 index 0000000000000..b7c284969438a --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/closure-captured-ref.tree.stderr @@ -0,0 +1,41 @@ +error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | self.1.deallocate(From::from(ptr.cast()), layout); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 allocation of the accessed tag also contains the strongly protected tag + = help: the strongly protected tag disallows deallocations +help: the accessed tag was created here + --> tests/fail/both_borrows/closure-captured-ref.rs:LL:CC + | +LL | drop(unsafe { Box::from_raw(p) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the strongly protected tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/closure-captured-ref.rs:LL:CC + | +LL | invoke(move || { + | ____________^ +LL | | drop(unsafe { Box::from_raw(p) }); +LL | | }); + | |_____^ + = note: stack backtrace: + 0: as std::ops::Drop>::drop + at RUSTLIB/alloc/src/boxed.rs:LL:CC + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 2: std::mem::drop::> + at RUSTLIB/core/src/mem/mod.rs:LL:CC + 3: main::{closure#0} + at tests/fail/both_borrows/closure-captured-ref.rs:LL:CC + 4: main::invoke::<{closure@tests/fail/both_borrows/closure-captured-ref.rs:LL:CC}> + at tests/fail/both_borrows/closure-captured-ref.rs:LL:CC + 5: main + at tests/fail/both_borrows/closure-captured-ref.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/pass/both_borrows/maybe_dangling.rs b/src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs index 2d37344d24072..028dcef8fa2d3 100644 --- a/src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs +++ b/src/tools/miri/tests/pass/both_borrows/maybe_dangling.rs @@ -14,7 +14,6 @@ fn main() { reference(); write_through_shared_ref(); large(); - closure(); } fn boxy() { @@ -65,16 +64,3 @@ fn large() { // Used to be rejected due to faulty logic for the "does this fit the address space" check. let _x: MaybeDangling<&i8> = unsafe { mem::transmute(usize::MAX - 127) }; } - -// A closure acts like MaybeDangling. -fn closure() { - fn invoke(f: impl FnOnce()) { - // The closure has captured a reference that will be freed while `invoke` runs. - f() - } - - let p = Box::leak(Box::new(0i32)); - invoke(move || { - drop(unsafe { Box::from_raw(p) }); - }); -}