diff --git a/src/eval.rs b/src/eval.rs index 8274b52bd0..7f07fc1a7d 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -186,8 +186,8 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) -> Option { // FIXME: We always ignore leaks on some OSs where we do not // correctly implement TLS destructors. - let target_os = tcx.sess.target.target.target_os.as_str(); - let ignore_leaks = config.ignore_leaks || target_os == "windows" || target_os == "macos"; + let target_os = &tcx.sess.target.target.target_os; + let ignore_leaks = config.ignore_leaks || target_os == "windows"; let (mut ecx, ret_place) = match create_ecx(tcx, main_id, config) { Ok(v) => v, diff --git a/src/shims/foreign_items/posix/macos.rs b/src/shims/foreign_items/posix/macos.rs index ee02effecd..325be877d0 100644 --- a/src/shims/foreign_items/posix/macos.rs +++ b/src/shims/foreign_items/posix/macos.rs @@ -87,7 +87,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } "_tlv_atexit" => { - // FIXME: register the destructor. + let dtor = this.read_scalar(args[0])?.not_undef()?; + let dtor = this.memory.get_fn(dtor)?.as_instance()?; + let data = this.read_scalar(args[1])?.not_undef()?; + this.machine.tls.set_global_dtor(dtor, data)?; } "_NSGetArgc" => { diff --git a/src/shims/tls.rs b/src/shims/tls.rs index 094b58d99a..6635978cb2 100644 --- a/src/shims/tls.rs +++ b/src/shims/tls.rs @@ -25,6 +25,12 @@ pub struct TlsData<'tcx> { /// pthreads-style thread-local storage. keys: BTreeMap>, + + /// A single global dtor (that's how things work on macOS) with a data argument. + global_dtor: Option<(ty::Instance<'tcx>, Scalar)>, + + /// Whether we are in the "destruct" phase, during which some operations are UB. + dtors_running: bool, } impl<'tcx> Default for TlsData<'tcx> { @@ -32,6 +38,8 @@ impl<'tcx> Default for TlsData<'tcx> { TlsData { next_key: 1, // start with 1 as we must not use 0 on Windows keys: Default::default(), + global_dtor: None, + dtors_running: false, } } } @@ -86,6 +94,19 @@ impl<'tcx> TlsData<'tcx> { } } + pub fn set_global_dtor(&mut self, dtor: ty::Instance<'tcx>, data: Scalar) -> InterpResult<'tcx> { + if self.dtors_running { + // UB, according to libstd docs. + throw_ub_format!("setting global destructor while destructors are already running"); + } + if self.global_dtor.is_some() { + throw_unsup_format!("setting more than one global destructor is not supported"); + } + + self.global_dtor = Some((dtor, data)); + Ok(()) + } + /// Returns a dtor, its argument and its index, if one is supposed to run /// /// An optional destructor function may be associated with each key value. @@ -134,11 +155,30 @@ impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tc pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> { fn run_tls_dtors(&mut self) -> InterpResult<'tcx> { let this = self.eval_context_mut(); + assert!(!this.machine.tls.dtors_running, "running TLS dtors twice"); + this.machine.tls.dtors_running = true; + + // The macOS global dtor runs "before any TLS slots get freed", so do that first. + if let Some((instance, data)) = this.machine.tls.global_dtor { + trace!("Running global dtor {:?} on {:?}", instance, data); + + let ret_place = MPlaceTy::dangling(this.layout_of(this.tcx.mk_unit())?, this).into(); + this.call_function( + instance, + &[data.into()], + Some(ret_place), + StackPopCleanup::None { cleanup: true }, + )?; + + // step until out of stackframes + this.run()?; + } + + // Now run the "keyed" destructors. let mut dtor = this.machine.tls.fetch_tls_dtor(None); - // FIXME: replace loop by some structure that works with stepping while let Some((instance, ptr, key)) = dtor { trace!("Running TLS dtor {:?} on {:?}", instance, ptr); - assert!(!this.is_null(ptr).unwrap(), "Data can't be NULL when dtor is called!"); + assert!(!this.is_null(ptr).unwrap(), "data can't be NULL when dtor is called!"); let ret_place = MPlaceTy::dangling(this.layout_of(this.tcx.mk_unit())?, this).into(); this.call_function( diff --git a/tests/compile-fail/memleak.rs b/tests/compile-fail/memleak.rs index 06e01d7aea..c3b27abcdb 100644 --- a/tests/compile-fail/memleak.rs +++ b/tests/compile-fail/memleak.rs @@ -1,5 +1,4 @@ // ignore-windows: We do not check leaks on Windows -// ignore-macos: We do not check leaks on macOS //error-pattern: the evaluated program leaked memory diff --git a/tests/compile-fail/memleak_rc.rs b/tests/compile-fail/memleak_rc.rs index 14a85ecd89..446d28681b 100644 --- a/tests/compile-fail/memleak_rc.rs +++ b/tests/compile-fail/memleak_rc.rs @@ -1,5 +1,4 @@ // ignore-windows: We do not check leaks on Windows -// ignore-macos: We do not check leaks on macOS //error-pattern: the evaluated program leaked memory