Skip to content

also ignore 'thread leaks' with -Zmiri-ignore-leaks #1858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ environment variable:
the host so that it cannot be accessed by the program. Can be used multiple
times to exclude several variables. On Windows, the `TERM` environment
variable is excluded by default.
* `-Zmiri-ignore-leaks` disables the memory leak checker.
* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some
remaining threads to exist when the main thread exits.
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
This can be used to find which parts of your program are executing slowly under Miri.
The profile is written out to a file with the prefix `<name>`, and can be processed
Expand Down
10 changes: 10 additions & 0 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,20 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
match res {
Ok(return_code) => {
if !ignore_leaks {
// Check for thread leaks.
if !ecx.have_all_terminated() {
tcx.sess.err(
"the main thread terminated without waiting for all remaining threads",
);
tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
return None;
}
// Check for memory leaks.
info!("Additonal static roots: {:?}", ecx.machine.static_roots);
let leaks = ecx.memory.leak_report(&ecx.machine.static_roots);
if leaks != 0 {
tcx.sess.err("the evaluated program leaked memory");
tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
// Ignore the provided return code - let the reported error
// determine the return code.
return None;
Expand Down
21 changes: 12 additions & 9 deletions src/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
self.threads[thread_id].state == ThreadState::Terminated
}

/// Have all threads terminated?
fn have_all_terminated(&self) -> bool {
self.threads.iter().all(|thread| thread.state == ThreadState::Terminated)
}

/// Enable the thread for execution. The thread must be terminated.
fn enable_thread(&mut self, thread_id: ThreadId) {
assert!(self.has_terminated(thread_id));
Expand Down Expand Up @@ -491,15 +496,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
// If we get here again and the thread is *still* terminated, there are no more dtors to run.
if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
// The main thread terminated; stop the program.
if self.threads.iter().any(|thread| thread.state != ThreadState::Terminated) {
// FIXME: This check should be either configurable or just emit
// a warning. For example, it seems normal for a program to
// terminate without waiting for its detached threads to
// terminate. However, this case is not trivial to support
// because we also probably do not want to consider the memory
// owned by these threads as leaked.
throw_unsup_format!("the main thread terminated without waiting for other threads");
}
// We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior.
return Ok(SchedulingAction::Stop);
}
// This thread and the program can keep going.
Expand Down Expand Up @@ -645,6 +642,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.machine.threads.has_terminated(thread_id)
}

#[inline]
fn have_all_terminated(&self) -> bool {
let this = self.eval_context_ref();
this.machine.threads.have_all_terminated()
}

#[inline]
fn enable_thread(&mut self, thread_id: ThreadId) {
let this = self.eval_context_mut();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ignore-windows: No libc on Windows
// error-pattern: unsupported operation: the main thread terminated without waiting for other threads
// error-pattern: the main thread terminated without waiting for all remaining threads

// Check that we terminate the program when the main thread terminates.

Expand All @@ -10,7 +10,7 @@ extern crate libc;
use std::{mem, ptr};

extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
ptr::null_mut()
loop {}
}

fn main() {
Expand Down
25 changes: 25 additions & 0 deletions tests/run-pass/threadleak_ignored.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ignore-windows: Concurrency on Windows is not supported yet.
// compile-flags: -Zmiri-ignore-leaks

//! Test that leaking threads works, and that their destructors are not executed.

use std::cell::RefCell;

struct LoudDrop(i32);
impl Drop for LoudDrop {
fn drop(&mut self) {
eprintln!("Dropping {}", self.0);
}
}

thread_local! {
static X: RefCell<Option<LoudDrop>> = RefCell::new(None);
}

fn main() {
X.with(|x| *x.borrow_mut() = Some(LoudDrop(0)));

let _detached = std::thread::spawn(|| {
X.with(|x| *x.borrow_mut() = Some(LoudDrop(1)));
});
}
3 changes: 3 additions & 0 deletions tests/run-pass/threadleak_ignored.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
warning: thread support is experimental and incomplete: weak memory effects are not emulated.

Dropping 0