Skip to content

Commit c226533

Browse files
max-hellerRalfJung
authored andcommitted
allow allocations referenced by main thread TLS to leak
1 parent fe0b970 commit c226533

File tree

7 files changed

+148
-10
lines changed

7 files changed

+148
-10
lines changed

src/tools/miri/src/concurrency/thread.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ enum SchedulingAction {
3333
Sleep(Duration),
3434
}
3535

36+
/// What to do with TLS allocations from terminated threads
37+
pub enum TlsAllocAction {
38+
/// Deallocate backing memory of thread-local statics as usual
39+
Deallocate,
40+
/// Skip deallocating backing memory of thread-local statics and consider all memory reachable
41+
/// from them as "allowed to leak" (like global `static`s).
42+
Leak,
43+
}
44+
3645
/// Trait for callbacks that can be executed when some event happens, such as after a timeout.
3746
pub trait MachineCallback<'mir, 'tcx>: VisitTags {
3847
fn call(&self, ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx>;
@@ -1051,7 +1060,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10511060
// See if this thread can do something else.
10521061
match this.run_on_stack_empty()? {
10531062
Poll::Pending => {} // keep going
1054-
Poll::Ready(()) => this.terminate_active_thread()?,
1063+
Poll::Ready(()) =>
1064+
this.terminate_active_thread(TlsAllocAction::Deallocate)?,
10551065
}
10561066
}
10571067
}
@@ -1066,21 +1076,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10661076
}
10671077

10681078
/// Handles thread termination of the active thread: wakes up threads joining on this one,
1069-
/// and deallocated thread-local statics.
1079+
/// and deals with the thread's thread-local statics according to `tls_alloc_action`.
10701080
///
10711081
/// This is called by the eval loop when a thread's on_stack_empty returns `Ready`.
10721082
#[inline]
1073-
fn terminate_active_thread(&mut self) -> InterpResult<'tcx> {
1083+
fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
10741084
let this = self.eval_context_mut();
10751085
let thread = this.active_thread_mut();
10761086
assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");
10771087
thread.state = ThreadState::Terminated;
10781088

10791089
let current_span = this.machine.current_span();
1080-
for ptr in
1081-
this.machine.threads.thread_terminated(this.machine.data_race.as_mut(), current_span)
1082-
{
1083-
this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?;
1090+
let thread_local_allocations =
1091+
this.machine.threads.thread_terminated(this.machine.data_race.as_mut(), current_span);
1092+
for ptr in thread_local_allocations {
1093+
match tls_alloc_action {
1094+
TlsAllocAction::Deallocate =>
1095+
this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?,
1096+
TlsAllocAction::Leak =>
1097+
if let Some(alloc) = ptr.provenance.get_alloc_id() {
1098+
trace!("Thread-local static leaked and stored as static root: {:?}", alloc);
1099+
this.machine.static_roots.push(alloc);
1100+
},
1101+
}
10841102
}
10851103
Ok(())
10861104
}

src/tools/miri/src/eval.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use log::info;
1111
use rustc_middle::ty::Ty;
1212

1313
use crate::borrow_tracker::RetagFields;
14+
use crate::concurrency::thread::TlsAllocAction;
1415
use crate::diagnostics::report_leaks;
1516
use rustc_data_structures::fx::FxHashSet;
1617
use rustc_hir::def::Namespace;
@@ -244,9 +245,9 @@ impl MainThreadState {
244245
// Figure out exit code.
245246
let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
246247
let exit_code = this.read_target_isize(&ret_place)?;
247-
// Need to call this ourselves since we are not going to return to the scheduler
248-
// loop, and we want the main thread TLS to not show up as memory leaks.
249-
this.terminate_active_thread()?;
248+
// Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS
249+
// to be like a global `static`, so that all memory reached by it is considered to "not leak".
250+
this.terminate_active_thread(TlsAllocAction::Leak)?;
250251
// Stop interpreter loop.
251252
throw_machine_stop!(TerminationInfo::Exit { code: exit_code, leak_check: true });
252253
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@error-in-other-file: memory leaked
2+
//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
3+
4+
use std::cell::Cell;
5+
6+
pub fn main() {
7+
thread_local! {
8+
static TLS: Cell<Option<&'static i32>> = Cell::new(None);
9+
}
10+
11+
std::thread::spawn(|| {
12+
TLS.with(|cell| {
13+
cell.set(Some(Box::leak(Box::new(123))));
14+
});
15+
})
16+
.join()
17+
.unwrap();
18+
19+
// Imagine the program running for a long time while the thread is gone
20+
// and this memory still sits around, unused -- leaked.
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here:
2+
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
3+
|
4+
LL | __rust_alloc(layout.size(), layout.align())
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
8+
= note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC
9+
= note: inside `<std::alloc::Global as std::alloc::Allocator>::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
10+
= note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
11+
= note: inside `std::boxed::Box::<i32>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC
12+
note: inside closure
13+
--> $DIR/leak_in_lib_tls.rs:LL:CC
14+
|
15+
LL | cell.set(Some(Box::leak(Box::new(123))));
16+
| ^^^^^^^^^^^^^
17+
= note: inside `std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::try_with::<{closure@$DIR/leak_in_lib_tls.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/local.rs:LL:CC
18+
= note: inside `std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::with::<{closure@$DIR/leak_in_lib_tls.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/local.rs:LL:CC
19+
note: inside closure
20+
--> $DIR/leak_in_lib_tls.rs:LL:CC
21+
|
22+
LL | / TLS.with(|cell| {
23+
LL | | cell.set(Some(Box::leak(Box::new(123))));
24+
LL | | });
25+
| |__________^
26+
27+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
28+
29+
note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check
30+
31+
error: aborting due to previous error
32+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@error-in-other-file: memory leaked
2+
//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
3+
4+
#![feature(thread_local)]
5+
6+
use std::cell::Cell;
7+
8+
/// Ensure that leaks through `thread_local` statics *not in the main thread*
9+
/// are detected.
10+
pub fn main() {
11+
#[thread_local]
12+
static TLS: Cell<Option<&'static i32>> = Cell::new(None);
13+
14+
std::thread::spawn(|| {
15+
TLS.set(Some(Box::leak(Box::new(123))));
16+
})
17+
.join()
18+
.unwrap();
19+
20+
// Imagine the program running for a long time while the thread is gone
21+
// and this memory still sits around, unused -- leaked.
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here:
2+
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
3+
|
4+
LL | __rust_alloc(layout.size(), layout.align())
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
8+
= note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC
9+
= note: inside `<std::alloc::Global as std::alloc::Allocator>::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
10+
= note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
11+
= note: inside `std::boxed::Box::<i32>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC
12+
note: inside closure
13+
--> $DIR/leak_in_static_tls.rs:LL:CC
14+
|
15+
LL | TLS.set(Some(Box::leak(Box::new(123))));
16+
| ^^^^^^^^^^^^^
17+
18+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
19+
20+
note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check
21+
22+
error: aborting due to previous error
23+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ignore-target-windows: Windows uses a different mechanism for `thread_local!`
2+
#![feature(thread_local)]
3+
4+
use std::cell::Cell;
5+
6+
// Thread-local variables in the main thread are basically like `static` (they live
7+
// as long as the program does), so make sure we treat them the same for leak purposes.
8+
pub fn main() {
9+
thread_local! {
10+
static TLS_KEY: Cell<Option<&'static i32>> = Cell::new(None);
11+
}
12+
13+
TLS_KEY.with(|cell| {
14+
cell.set(Some(Box::leak(Box::new(123))));
15+
});
16+
17+
#[thread_local]
18+
static TLS: Cell<Option<&'static i32>> = Cell::new(None);
19+
20+
TLS.set(Some(Box::leak(Box::new(123))));
21+
}

0 commit comments

Comments
 (0)