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

This commit is contained in:
Ralf Jung 2021-07-25 14:08:12 +02:00
parent e2872a3f2a
commit 71efd950d1
6 changed files with 49 additions and 12 deletions

View File

@ -230,7 +230,8 @@ environment variable:
the host so that it cannot be accessed by the program. Can be used multiple 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 times to exclude several variables. On Windows, the `TERM` environment
variable is excluded by default. 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. * `-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. 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 The profile is written out to a file with the prefix `<name>`, and can be processed

View File

@ -300,6 +300,12 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
match res { match res {
Ok(return_code) => { Ok(return_code) => {
if !ignore_leaks { 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");
return None;
}
// Check for memory leaks.
info!("Additonal static roots: {:?}", ecx.machine.static_roots); info!("Additonal static roots: {:?}", ecx.machine.static_roots);
let leaks = ecx.memory.leak_report(&ecx.machine.static_roots); let leaks = ecx.memory.leak_report(&ecx.machine.static_roots);
if leaks != 0 { if leaks != 0 {

View File

@ -302,6 +302,11 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
self.threads[thread_id].state == ThreadState::Terminated 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. /// Enable the thread for execution. The thread must be terminated.
fn enable_thread(&mut self, thread_id: ThreadId) { fn enable_thread(&mut self, thread_id: ThreadId) {
assert!(self.has_terminated(thread_id)); assert!(self.has_terminated(thread_id));
@ -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 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 { if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
// The main thread terminated; stop the program. // The main thread terminated; stop the program.
if self.threads.iter().any(|thread| thread.state != ThreadState::Terminated) { // We do *not* run TLS dtors of remaining threads, which seems to match rustc behavior.
// 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");
}
return Ok(SchedulingAction::Stop); return Ok(SchedulingAction::Stop);
} }
// This thread and the program can keep going. // This thread and the program can keep going.
@ -645,6 +642,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.machine.threads.has_terminated(thread_id) 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] #[inline]
fn enable_thread(&mut self, thread_id: ThreadId) { fn enable_thread(&mut self, thread_id: ThreadId) {
let this = self.eval_context_mut(); let this = self.eval_context_mut();

View File

@ -1,5 +1,5 @@
// ignore-windows: No libc on Windows // 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. // Check that we terminate the program when the main thread terminates.
@ -10,7 +10,7 @@ extern crate libc;
use std::{mem, ptr}; use std::{mem, ptr};
extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void { extern "C" fn thread_start(_null: *mut libc::c_void) -> *mut libc::c_void {
ptr::null_mut() loop {}
} }
fn main() { fn main() {

View File

@ -0,0 +1,24 @@
// 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)));
});
}

View File

@ -0,0 +1,3 @@
warning: thread support is experimental and incomplete: weak memory effects are not emulated.
Dropping 0