show span when there is an error invoking a global ctor/dtor or the thread main fn

This commit is contained in:
Ralf Jung
2025-12-06 13:57:59 +01:00
parent ccefb744e3
commit 7ad3301e03
19 changed files with 188 additions and 49 deletions
+1
View File
@@ -624,6 +624,7 @@ Definite bugs found:
* [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite)
* [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248)
* [Rare Deadlock in the thread (un)parking example code](https://github.com/rust-lang/rust/issues/145816)
* [`winit` registering a global constructor with the wrong ABI on Windows](https://github.com/rust-windowing/winit/issues/4435)
Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):
+17 -6
View File
@@ -14,7 +14,7 @@
use rustc_index::{Idx, IndexVec};
use rustc_middle::mir::Mutability;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_span::Span;
use rustc_span::{DUMMY_SP, Span};
use rustc_target::spec::Os;
use crate::concurrency::GlobalDataRaceHandler;
@@ -174,6 +174,10 @@ pub struct Thread<'tcx> {
/// The virtual call stack.
stack: Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
/// A span that explains where the thread (or more specifically, its current root
/// frame) "comes from".
pub(crate) origin_span: Span,
/// The function to call when the stack ran empty, to figure out what to do next.
/// Conceptually, this is the interpreter implementation of the things that happen 'after' the
/// Rust language entry point for this thread returns (usually implemented by the C or OS runtime).
@@ -303,6 +307,7 @@ fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'tcx>>) ->
state: ThreadState::Enabled,
thread_name: name.map(|name| Vec::from(name.as_bytes())),
stack: Vec::new(),
origin_span: DUMMY_SP,
top_user_relevant_frame: None,
join_status: ThreadJoinStatus::Joinable,
unwind_payloads: Vec::new(),
@@ -318,6 +323,7 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
unwind_payloads: panic_payload,
last_error,
stack,
origin_span: _,
top_user_relevant_frame: _,
state: _,
thread_name: _,
@@ -584,6 +590,10 @@ pub fn active_thread_ref(&self) -> &Thread<'tcx> {
&self.threads[self.active_thread]
}
pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> {
&self.threads[thread_id]
}
/// Mark the thread as detached, which means that no other thread will try
/// to join it and the thread is responsible for cleaning up.
///
@@ -704,8 +714,9 @@ fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
#[inline]
fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> {
let this = self.eval_context_mut();
let mut callback = this
.active_thread_mut()
let active_thread = this.active_thread_mut();
active_thread.origin_span = DUMMY_SP; // reset, the old value no longer applied
let mut callback = active_thread
.on_stack_empty
.take()
.expect("`on_stack_empty` not set up, or already running");
@@ -891,11 +902,11 @@ fn start_regular_thread(
let this = self.eval_context_mut();
// Create the new thread
let current_span = this.machine.current_user_relevant_span();
let new_thread_id = this.machine.threads.create_thread({
let mut state = tls::TlsDtorsState::default();
Box::new(move |m| state.on_stack_empty(m))
});
let current_span = this.machine.current_user_relevant_span();
match &mut this.machine.data_race {
GlobalDataRaceHandler::None => {}
GlobalDataRaceHandler::Vclocks(data_race) =>
@@ -934,12 +945,12 @@ fn start_regular_thread(
// it.
let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
this.call_function(
this.call_thread_root_function(
instance,
start_abi,
&[func_arg],
Some(&ret_place),
ReturnContinuation::Stop { cleanup: true },
current_span,
)?;
// Restore the old active thread frame.
+19 -2
View File
@@ -444,7 +444,11 @@ pub fn report_result<'tcx>(
write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), res)).unwrap();
if labels.is_empty() {
labels.push(format!("{} occurred here", title.unwrap_or("error")));
labels.push(format!(
"{} occurred {}",
title.unwrap_or("error"),
if stacktrace.is_empty() { "due to this code" } else { "here" }
));
}
report_msg(
@@ -552,7 +556,14 @@ pub fn report_msg<'tcx>(
thread: Option<ThreadId>,
machine: &MiriMachine<'tcx>,
) {
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
let span = match stacktrace.first() {
Some(fi) => fi.span,
None =>
match thread {
Some(thread_id) => machine.threads.thread_ref(thread_id).origin_span,
None => DUMMY_SP,
},
};
let sess = machine.tcx.sess;
let level = match diag_level {
DiagLevel::Error => Level::Error,
@@ -620,6 +631,12 @@ pub fn report_msg<'tcx>(
err.note(format!("{frame_info} at {span}"));
}
}
} else if stacktrace.len() == 0 && !span.is_dummy() {
err.note(format!(
"this {} occurred while pushing a call frame onto an empty stack",
level.to_str()
));
err.note("the span indicates which code caused the function to be called, but may not be the literal call site");
}
err.emit();
+22 -4
View File
@@ -472,6 +472,22 @@ fn call_function(
)
}
/// Call a function in an "empty" thread.
fn call_thread_root_function(
&mut self,
f: ty::Instance<'tcx>,
caller_abi: ExternAbi,
args: &[ImmTy<'tcx>],
dest: Option<&MPlaceTy<'tcx>>,
span: Span,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
assert!(this.active_thread_stack().is_empty());
assert!(this.active_thread_ref().origin_span.is_dummy());
this.active_thread_mut().origin_span = span;
this.call_function(f, caller_abi, args, dest, ReturnContinuation::Stop { cleanup: true })
}
/// Visits the memory covered by `place`, sensitive to freezing: the 2nd parameter
/// of `action` will be true if this is frozen, false if this is in an `UnsafeCell`.
/// The range is relative to `place`.
@@ -995,11 +1011,12 @@ fn expect_target_feature_for_intrinsic(
interp_ok(())
}
/// Lookup an array of immediates from any linker sections matching the provided predicate.
/// Lookup an array of immediates from any linker sections matching the provided predicate,
/// with the spans of where they were found.
fn lookup_link_section(
&mut self,
include_name: impl Fn(&str) -> bool,
) -> InterpResult<'tcx, Vec<ImmTy<'tcx>>> {
) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> {
let this = self.eval_context_mut();
let tcx = this.tcx.tcx;
@@ -1012,6 +1029,7 @@ fn lookup_link_section(
};
if include_name(link_section.as_str()) {
let instance = ty::Instance::mono(tcx, def_id);
let span = tcx.def_span(def_id);
let const_val = this.eval_global(instance).unwrap_or_else(|err| {
panic!(
"failed to evaluate static in required link_section: {def_id:?}\n{err:?}"
@@ -1019,12 +1037,12 @@ fn lookup_link_section(
});
match const_val.layout.ty.kind() {
ty::FnPtr(..) => {
array.push(this.read_immediate(&const_val)?);
array.push((this.read_immediate(&const_val)?, span));
}
ty::Array(elem_ty, _) if matches!(elem_ty.kind(), ty::FnPtr(..)) => {
let mut elems = this.project_array_fields(&const_val)?;
while let Some((_idx, elem)) = elems.next(this)? {
array.push(this.read_immediate(&elem)?);
array.push((this.read_immediate(&elem)?, span));
}
}
_ =>
+1
View File
@@ -39,6 +39,7 @@
clippy::needless_question_mark,
clippy::needless_lifetimes,
clippy::too_long_first_doc_paragraph,
clippy::len_zero,
// We don't use translatable diagnostics
rustc::diagnostic_outside_of_impl,
// We are not implementing queries here so it's fine
+5 -4
View File
@@ -3,6 +3,7 @@
use std::task::Poll;
use rustc_abi::ExternAbi;
use rustc_span::Span;
use rustc_target::spec::BinaryFormat;
use crate::*;
@@ -15,7 +16,7 @@ enum GlobalCtorStatePriv<'tcx> {
#[default]
Init,
/// The list of constructor functions that we still have to call.
Ctors(Vec<ImmTy<'tcx>>),
Ctors(Vec<(ImmTy<'tcx>, Span)>),
Done,
}
@@ -67,19 +68,19 @@ pub fn on_stack_empty(
break 'new_state Ctors(ctors);
}
Ctors(ctors) => {
if let Some(ctor) = ctors.pop() {
if let Some((ctor, span)) = ctors.pop() {
let this = this.eval_context_mut();
let ctor = ctor.to_scalar().to_pointer(this)?;
let thread_callback = this.get_ptr_fn(ctor)?.as_instance()?;
// The signature of this function is `unsafe extern "C" fn()`.
this.call_function(
this.call_thread_root_function(
thread_callback,
ExternAbi::C { unwind: false },
&[],
None,
ReturnContinuation::Stop { cleanup: true },
span,
)?;
return interp_ok(Poll::Pending); // we stay in this state (but `ctors` got shorter)
+23 -22
View File
@@ -6,6 +6,7 @@
use rustc_abi::{ExternAbi, HasDataLayout, Size};
use rustc_middle::ty;
use rustc_span::Span;
use rustc_target::spec::Os;
use crate::*;
@@ -17,7 +18,7 @@ pub struct TlsEntry<'tcx> {
/// The data for this key. None is used to represent NULL.
/// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
data: BTreeMap<ThreadId, Scalar>,
dtor: Option<ty::Instance<'tcx>>,
dtor: Option<(ty::Instance<'tcx>, Span)>,
}
#[derive(Default, Debug)]
@@ -38,7 +39,7 @@ pub struct TlsData<'tcx> {
/// On macOS, each thread holds a list of destructor functions with their
/// respective data arguments.
macos_thread_dtors: BTreeMap<ThreadId, Vec<(ty::Instance<'tcx>, Scalar)>>,
macos_thread_dtors: BTreeMap<ThreadId, Vec<(ty::Instance<'tcx>, Scalar, Span)>>,
}
impl<'tcx> Default for TlsData<'tcx> {
@@ -57,7 +58,7 @@ impl<'tcx> TlsData<'tcx> {
#[expect(clippy::arithmetic_side_effects)]
pub fn create_tls_key(
&mut self,
dtor: Option<ty::Instance<'tcx>>,
dtor: Option<(ty::Instance<'tcx>, Span)>,
max_size: Size,
) -> InterpResult<'tcx, TlsKey> {
let new_key = self.next_key;
@@ -126,8 +127,9 @@ pub fn add_macos_thread_dtor(
thread: ThreadId,
dtor: ty::Instance<'tcx>,
data: Scalar,
span: Span,
) -> InterpResult<'tcx> {
self.macos_thread_dtors.entry(thread).or_default().push((dtor, data));
self.macos_thread_dtors.entry(thread).or_default().push((dtor, data, span));
interp_ok(())
}
@@ -154,7 +156,7 @@ fn fetch_tls_dtor(
&mut self,
key: Option<TlsKey>,
thread_id: ThreadId,
) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey)> {
) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey, Span)> {
use std::ops::Bound::*;
let thread_local = &mut self.keys;
@@ -172,11 +174,10 @@ fn fetch_tls_dtor(
for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
match data.entry(thread_id) {
BTreeEntry::Occupied(entry) => {
if let Some(dtor) = dtor {
if let Some((dtor, span)) = dtor {
// Set TLS data to NULL, and call dtor with old value.
let data_scalar = entry.remove();
let ret = Some((*dtor, data_scalar, key));
return ret;
return Some((*dtor, data_scalar, key, *span));
}
}
BTreeEntry::Vacant(_) => {}
@@ -205,7 +206,7 @@ fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
for scalar in keys.values().flat_map(|v| v.data.values()) {
scalar.visit_provenance(visit);
}
for (_, scalar) in macos_thread_dtors.values().flatten() {
for (_, scalar, _) in macos_thread_dtors.values().flatten() {
scalar.visit_provenance(visit);
}
}
@@ -222,7 +223,7 @@ enum TlsDtorsStatePriv<'tcx> {
PthreadDtors(RunningDtorState),
/// For Windows Dtors, we store the list of functions that we still have to call.
/// These are functions from the magic `.CRT$XLB` linker section.
WindowsDtors(Vec<ImmTy<'tcx>>),
WindowsDtors(Vec<(ImmTy<'tcx>, Span)>),
Done,
}
@@ -273,8 +274,8 @@ pub fn on_stack_empty(
}
}
WindowsDtors(dtors) => {
if let Some(dtor) = dtors.pop() {
this.schedule_windows_tls_dtor(dtor)?;
if let Some((dtor, span)) = dtors.pop() {
this.schedule_windows_tls_dtor(dtor, span)?;
return interp_ok(Poll::Pending); // we stay in this state (but `dtors` got shorter)
} else {
// No more destructors to run.
@@ -297,7 +298,7 @@ impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Schedule TLS destructors for Windows.
/// On windows, TLS destructors are managed by std.
fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<ImmTy<'tcx>>> {
fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> {
let this = self.eval_context_mut();
// Windows has a special magic linker section that is run on certain events.
@@ -305,7 +306,7 @@ fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<ImmTy<'tcx>>> {
interp_ok(this.lookup_link_section(|section| section == ".CRT$XLB")?)
}
fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>) -> InterpResult<'tcx> {
fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>, span: Span) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let dtor = dtor.to_scalar().to_pointer(this)?;
@@ -320,12 +321,12 @@ fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>) -> InterpResult<'tcx>
// The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
// FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
// but both are ignored by std.
this.call_function(
this.call_thread_root_function(
thread_callback,
ExternAbi::System { unwind: false },
&[null_ptr.clone(), ImmTy::from_scalar(reason, this.machine.layouts.u32), null_ptr],
None,
ReturnContinuation::Stop { cleanup: true },
span,
)?;
interp_ok(())
}
@@ -338,15 +339,15 @@ fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, Poll<()>> {
// registers another destructor, it will be run next.
// See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277
let dtor = this.machine.tls.macos_thread_dtors.get_mut(&thread_id).and_then(Vec::pop);
if let Some((instance, data)) = dtor {
if let Some((instance, data, span)) = dtor {
trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
this.call_function(
this.call_thread_root_function(
instance,
ExternAbi::C { unwind: false },
&[ImmTy::from_scalar(data, this.machine.layouts.mut_raw_ptr)],
None,
ReturnContinuation::Stop { cleanup: true },
span,
)?;
return interp_ok(Poll::Pending);
@@ -370,7 +371,7 @@ fn schedule_next_pthread_tls_dtor(
// We ran each dtor once, start over from the beginning.
None => this.machine.tls.fetch_tls_dtor(None, active_thread),
};
if let Some((instance, ptr, key)) = dtor {
if let Some((instance, ptr, key, span)) = dtor {
state.last_key = Some(key);
trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread);
assert!(
@@ -378,12 +379,12 @@ fn schedule_next_pthread_tls_dtor(
"data can't be NULL when dtor is called!"
);
this.call_function(
this.call_thread_root_function(
instance,
ExternAbi::C { unwind: false },
&[ImmTy::from_scalar(ptr, this.machine.layouts.mut_raw_ptr)],
None,
ReturnContinuation::Stop { cleanup: true },
span,
)?;
return interp_ok(Poll::Pending);
@@ -634,7 +634,10 @@ fn emulate_foreign_item_inner(
// Extract the function type out of the signature (that seems easier than constructing it ourselves).
let dtor = if !this.ptr_is_null(dtor)? {
Some(this.get_ptr_fn(dtor)?.as_instance()?)
Some((
this.get_ptr_fn(dtor)?.as_instance()?,
this.machine.current_user_relevant_span(),
))
} else {
None
};
@@ -158,7 +158,12 @@ fn emulate_foreign_item_inner(
let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
let data = this.read_scalar(data)?;
let active_thread = this.active_thread();
this.machine.tls.add_macos_thread_dtor(active_thread, dtor, data)?;
this.machine.tls.add_macos_thread_dtor(
active_thread,
dtor,
data,
this.machine.current_user_relevant_span(),
)?;
}
// Querying system information
@@ -1,5 +1,4 @@
//@ignore-target: windows # No pthreads on Windows
//~^ERROR: calling a function with more arguments than it expected
//! The thread function must have exactly one argument.
@@ -17,6 +16,7 @@ fn main() {
mem::transmute(thread_start);
assert_eq!(
libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()),
//~^ERROR: calling a function with more arguments than it expected
0
);
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
@@ -1,9 +1,13 @@
error: Undefined Behavior: calling a function with more arguments than it expected
--> tests/fail-dep/concurrency/libc_pthread_create_too_few_args.rs:LL:CC
|
LL | libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code
|
= note: Undefined Behavior occurred here
= note: (no span available)
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: this error occurred while pushing a call frame onto an empty stack
= note: the span indicates which code caused the function to be called, but may not be the literal call site
error: aborting due to 1 previous error
@@ -1,5 +1,4 @@
//@ignore-target: windows # No pthreads on Windows
//~^ERROR: calling a function with fewer arguments than it requires
//! The thread function must have exactly one argument.
@@ -17,6 +16,7 @@ fn main() {
mem::transmute(thread_start);
assert_eq!(
libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()),
//~^ERROR: calling a function with fewer arguments than it requires
0
);
assert_eq!(libc::pthread_join(native, ptr::null_mut()), 0);
@@ -1,9 +1,13 @@
error: Undefined Behavior: calling a function with fewer arguments than it requires
--> tests/fail-dep/concurrency/libc_pthread_create_too_many_args.rs:LL:CC
|
LL | libc::pthread_create(&mut native, ptr::null(), thread_start, ptr::null_mut()),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code
|
= note: Undefined Behavior occurred here
= note: (no span available)
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: this error occurred while pushing a call frame onto an empty stack
= note: the span indicates which code caused the function to be called, but may not be the literal call site
error: aborting due to 1 previous error
@@ -0,0 +1,21 @@
//@ignore-target: windows # No pthreads on Windows
use std::{mem, ptr};
pub type Key = libc::pthread_key_t;
pub unsafe fn create(dtor: unsafe fn(*mut u8)) -> Key {
let mut key = 0;
assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0);
//~^ERROR: calling a function with calling convention "Rust"
key
}
unsafe fn dtor(_ptr: *mut u8) {}
fn main() {
unsafe {
let key = create(dtor);
libc::pthread_setspecific(key, ptr::without_provenance(1));
}
}
@@ -0,0 +1,13 @@
error: Undefined Behavior: calling a function with calling convention "Rust" using calling convention "C"
--> tests/fail-dep/concurrency/tls_pthread_dtor_wrong_abi.rs:LL:CC
|
LL | assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: this error occurred while pushing a call frame onto an empty stack
= note: the span indicates which code caused the function to be called, but may not be the literal call site
error: aborting due to 1 previous error
@@ -1,5 +1,4 @@
unsafe extern "C" fn ctor() -> i32 {
//~^ERROR: calling a function with return type i32 passing return place of type ()
0
}
@@ -31,6 +30,7 @@ macro_rules! ctor {
)]
#[used]
static $ident: unsafe extern "C" fn() -> i32 = $ctor;
//~^ERROR: calling a function with return type i32 passing return place of type ()
};
}
@@ -1,11 +1,19 @@
error: Undefined Behavior: calling a function with return type i32 passing return place of type ()
--> tests/fail/shims/ctor_wrong_ret_type.rs:LL:CC
|
LL | static $ident: unsafe extern "C" fn() -> i32 = $ctor;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code
...
LL | ctor! { CTOR = ctor }
| --------------------- in this macro invocation
|
= note: Undefined Behavior occurred here
= note: (no span available)
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
= help: if you think this code should be accepted anyway, please report an issue with Miri
= note: this error occurred while pushing a call frame onto an empty stack
= note: the span indicates which code caused the function to be called, but may not be the literal call site
= note: this error originates in the macro `ctor` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
@@ -0,0 +1,18 @@
//@only-target: darwin
use std::{mem, ptr};
extern "C" {
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
}
fn register(dtor: unsafe fn(*mut u8)) {
unsafe {
_tlv_atexit(mem::transmute(dtor), ptr::null_mut());
//~^ERROR: calling a function with calling convention "Rust"
}
}
fn main() {
register(|_| ());
}
@@ -0,0 +1,13 @@
error: Undefined Behavior: calling a function with calling convention "Rust" using calling convention "C"
--> tests/fail/shims/macos_tlv_atexit_wrong_abi.rs:LL:CC
|
LL | _tlv_atexit(mem::transmute(dtor), ptr::null_mut());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred due to this code
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: this error occurred while pushing a call frame onto an empty stack
= note: the span indicates which code caused the function to be called, but may not be the literal call site
error: aborting due to 1 previous error