Skip to content

Commit 9ccb197

Browse files
committed
Address review comments
This moves the creation of the closure to a more meaningful location. Additionally it adds a test + aborts the execution as soon as the closure is called as we currently cannot return a reasonable value there.
1 parent 6699145 commit 9ccb197

File tree

4 files changed

+109
-154
lines changed

4 files changed

+109
-154
lines changed

src/alloc_addresses/mod.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::cell::RefCell;
88

99
use rustc_abi::{Align, Size};
1010
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
11-
use rustc_middle::ty::TyCtxt;
11+
use rustc_middle::ty::{InstanceKind, TyCtxt};
1212

1313
pub use self::address_generator::AddressGenerator;
1414
use self::reuse_pool::ReusePool;
@@ -169,9 +169,39 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
169169
Align::from_bytes(1).unwrap(),
170170
params,
171171
);
172-
let ptr = alloc_bytes.as_ptr();
172+
let mut ptr = alloc_bytes.as_ptr();
173173
// Leak the underlying memory to ensure it remains unique.
174174
std::mem::forget(alloc_bytes);
175+
if let Some(GlobalAlloc::Function { instance, .. }) =
176+
this.tcx.try_get_global_alloc(alloc_id)
177+
{
178+
#[cfg(all(unix, feature = "native-lib"))]
179+
if let InstanceKind::Item(def_id) = instance.def {
180+
let sig = this.tcx.fn_sig(def_id).skip_binder().skip_binder();
181+
let closure =
182+
crate::shims::native_lib::build_libffi_closure(this, sig)?;
183+
if let Some(closure) = closure {
184+
let closure = Box::leak(Box::new(closure));
185+
// Libffi returns a **reference** to a function ptr here
186+
// (The actual argument type doesn't matter)
187+
let fn_ptr = unsafe {
188+
closure.instantiate_code_ptr::<unsafe extern "C" fn(*const std::ffi::c_void)>()
189+
};
190+
// Therefore we need to dereference the reference to get the actual function pointer
191+
let fn_ptr = *fn_ptr;
192+
#[expect(
193+
clippy::as_conversions,
194+
reason = "No better way to cast a function ptr to a ptr"
195+
)]
196+
{
197+
// After that we need to cast the function pointer to the
198+
// expected pointer type. At this point we don't actually care about the
199+
// type of this pointer
200+
ptr = (fn_ptr as *const std::ffi::c_void).cast();
201+
}
202+
}
203+
}
204+
}
175205
ptr
176206
}
177207
AllocKind::TypeId | AllocKind::Dead => unreachable!(),

src/shims/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod backtrace;
66
mod files;
77
mod math;
88
#[cfg(all(unix, feature = "native-lib"))]
9-
mod native_lib;
9+
pub mod native_lib;
1010
mod unix;
1111
mod windows;
1212
mod x86;

src/shims/native_lib/mod.rs

Lines changed: 69 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! Implements calling functions from a native library.
22
3-
use std::borrow::Cow;
4-
use std::cell::RefCell;
53
use std::ops::Deref;
64
use std::os::raw::c_void;
75
use std::sync::atomic::AtomicBool;
@@ -19,14 +17,6 @@ use self::helpers::ToSoft;
1917

2018
mod ffi;
2119

22-
struct CallbackError {
23-
message: Cow<'static, str>,
24-
}
25-
26-
thread_local! {
27-
pub static CALLBACK_MESSAGES: RefCell<Vec<CallbackError>> = RefCell::new(Vec::new());
28-
}
29-
3020
#[cfg_attr(
3121
not(all(
3222
target_os = "linux",
@@ -103,8 +93,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
10393
let alloc = ();
10494

10595
trace::Supervisor::do_ffi(alloc, || {
106-
// clear the callback error buffer
107-
CALLBACK_MESSAGES.with_borrow_mut(|c| c.clear());
10896
// Call the function (`ptr`) with arguments `libffi_args`, and obtain the return value
10997
// as the specified primitive integer type
11098
let scalar = match dest.layout.ty.kind() {
@@ -179,11 +167,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
179167
))
180168
.into(),
181169
};
182-
let callback_error_messages = CALLBACK_MESSAGES.take();
183-
if !callback_error_messages.is_empty() {
184-
let first = callback_error_messages.first().unwrap();
185-
return Err(err_unsup_format!("{}", first.message)).into();
186-
}
187170
interp_ok(ImmTy::from_scalar(scalar, dest.layout))
188171
})
189172
}
@@ -303,12 +286,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
303286

304287
/// Extract the value from the result of reading an operand from the machine
305288
/// and convert it to a `OwnedArg`.
306-
fn op_to_ffi_arg(
307-
&self,
308-
v: &OpTy<'tcx>,
309-
tracing: bool,
310-
link_name: &Symbol,
311-
) -> InterpResult<'tcx, OwnedArg> {
289+
fn op_to_ffi_arg(&self, v: &OpTy<'tcx>, tracing: bool) -> InterpResult<'tcx, OwnedArg> {
312290
let this = self.eval_context_ref();
313291

314292
// This should go first so that we emit unsupported before doing a bunch
@@ -333,44 +311,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
333311
// casting the integer in `byte` to a pointer and using that.
334312
let bytes = match v.as_mplace_or_imm() {
335313
either::Either::Left(mplace) => {
336-
let ptr_overwrite = match v.layout.ty.kind() {
337-
ty::Adt(_adt_def, args) =>
338-
if let ty::FnPtr(fn_ptr, _header) = args.type_at(0).kind() {
339-
let args = fn_ptr
340-
.skip_binder()
341-
.inputs()
342-
.into_iter()
343-
.map(|i| {
344-
let layout = this.layout_of(i.clone())?;
345-
this.ty_to_ffitype(layout)
346-
})
347-
.collect::<InterpResult<'_, Vec<_>>>()?;
348-
let res_type = fn_ptr.skip_binder().output();
349-
let res_type = {
350-
let layout = this.layout_of(res_type)?;
351-
this.ty_to_ffitype(layout)?
352-
};
353-
let closure_builder = libffi::middle::Builder::new()
354-
.args(args)
355-
.res(res_type)
356-
.abi(libffi::raw::ffi_abi_FFI_UNIX64);
357-
let data = CallbackData {
358-
args: fn_ptr.skip_binder().inputs().to_vec(),
359-
result: fn_ptr.skip_binder().output(),
360-
this,
361-
link_name: link_name.clone(),
362-
ty: v.layout.ty,
363-
};
364-
// todo: leaking is likely not optimal here
365-
let data = Box::leak(Box::new(data));
366-
367-
let closure = closure_builder.into_closure(callback_callback, data);
368-
Some(closure)
369-
} else {
370-
None
371-
},
372-
_ => None,
373-
};
374314
// Get the alloc id corresponding to this mplace, alongside
375315
// a pointer that's offset to point to this particular
376316
// mplace (not one at the base addr of the allocation).
@@ -393,33 +333,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
393333
// is kind of outside the interpreter, after all...
394334
let ret: Box<[u8]> =
395335
Box::from(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range));
396-
if ret.iter().any(|b| *b != 0)
397-
&& let Some(ptr_overwrite) = ptr_overwrite
398-
{
399-
// we need to leak the closure here as we don't know when it's actually called
400-
// I'm not sure if it's possible to have a better solution for that
401-
let ptr_overwrite = Box::leak(Box::new(ptr_overwrite));
402-
403-
// we get a **reference** to a function ptr here
404-
// (The actual argument type doesn't matter)
405-
let ptr = unsafe {
406-
ptr_overwrite.instantiate_code_ptr::<unsafe extern "C" fn(*const c_void)>()
407-
};
408-
// so deref away the reference
409-
let ptr = *ptr;
410-
// cast it to void as the actual function type doesn't matter
411-
let ptr = ptr as *const c_void;
412-
// get a the address as usize to write it into the
413-
// right memory location
414-
let bytes = ptr.addr();
415-
// bytes are in native endian, as that's literally
416-
// the definition of native endian
417-
let bytes = usize::to_ne_bytes(bytes);
418-
// return the bytes of the ptr
419-
Box::from(bytes)
420-
} else {
421-
ret
422-
}
336+
ret
423337
}
424338
either::Either::Right(imm) => {
425339
let mut bytes: Box<[u8]> = vec![0; imm.layout.size.bytes_usize()].into();
@@ -538,6 +452,57 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
538452
}
539453
}
540454

455+
pub fn build_libffi_closure<'tcx, 'this>(
456+
this: &'this MiriInterpCx<'tcx>,
457+
fn_ptr: rustc_middle::ty::FnSig<'tcx>,
458+
) -> InterpResult<'tcx, Option<libffi::middle::Closure<'this>>> {
459+
let mut args = Vec::new();
460+
for input in fn_ptr.inputs().iter() {
461+
let layout = match this.layout_of(*input) {
462+
Ok(layout) => layout,
463+
Err(e) => {
464+
tracing::info!(?e, "Skip closure");
465+
return interp_ok(None);
466+
}
467+
};
468+
let ty = match this.ty_to_ffitype(layout).report_err() {
469+
Ok(ty) => ty,
470+
Err(e) => {
471+
tracing::info!(?e, "Skip closure");
472+
return interp_ok(None);
473+
}
474+
};
475+
args.push(ty);
476+
}
477+
let res_type = fn_ptr.output();
478+
let res_type = {
479+
let layout = match this.layout_of(res_type) {
480+
Ok(layout) => layout,
481+
Err(e) => {
482+
tracing::info!(?e, "Skip closure");
483+
return interp_ok(None);
484+
}
485+
};
486+
match this.ty_to_ffitype(layout).report_err() {
487+
Ok(ty) => ty,
488+
Err(e) => {
489+
tracing::info!(?e, "Skip closure");
490+
return interp_ok(None);
491+
}
492+
}
493+
};
494+
let closure_builder = libffi::middle::Builder::new().args(args).res(res_type);
495+
let data = CallbackData {
496+
args: fn_ptr.inputs().to_vec(),
497+
result: fn_ptr.output(),
498+
this,
499+
//link_name: *link_name,
500+
};
501+
let data = Box::leak(Box::new(data));
502+
let closure = closure_builder.into_closure(callback_callback, data);
503+
interp_ok(Some(closure))
504+
}
505+
541506
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
542507
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
543508
/// Call the native host function, with supplied arguments.
@@ -567,7 +532,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
567532
// Get the function arguments, copy them, and prepare the type descriptions.
568533
let mut libffi_args = Vec::<OwnedArg>::with_capacity(args.len());
569534
for arg in args.iter() {
570-
libffi_args.push(this.op_to_ffi_arg(arg, tracing, &link_name)?);
535+
libffi_args.push(this.op_to_ffi_arg(arg, tracing)?);
571536
}
572537

573538
// Prepare all exposed memory (both previously exposed, and just newly exposed since a
@@ -633,26 +598,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
633598

634599
struct CallbackData<'a, 'tcx> {
635600
args: Vec<Ty<'tcx>>,
601+
#[expect(dead_code, reason = "It's there for later")]
636602
result: Ty<'tcx>,
637603
this: &'a MiriInterpCx<'tcx>,
638-
link_name: Symbol,
639-
ty: Ty<'tcx>,
640604
}
641605

642606
unsafe extern "C" fn callback_callback(
643607
cif: &libffi::low::ffi_cif,
644-
result: &mut c_void,
608+
_result: &mut c_void,
645609
args: *const *const c_void,
646610
infos: &CallbackData<'_, '_>,
647611
) {
648-
debug_assert_eq!(cif.nargs as usize, infos.args.len());
612+
debug_assert_eq!(cif.nargs.try_into(), Ok(infos.args.len()));
649613
let mut rust_args = Vec::with_capacity(infos.args.len());
650-
// cast away the pointer to pointer
651-
let mut args = args as *const c_void;
614+
// We cast away the pointer to pointer to get a pointer to the actual argument
615+
let mut args = args.cast::<c_void>();
652616
for arg in &infos.args {
653617
let scalar = match arg.kind() {
654618
ty::RawPtr(..) => {
655619
let ptr = StrictPointer::new(Provenance::Wildcard, Size::from_bytes(args.addr()));
620+
// This offset moves the pointer to the next argument
656621
args = unsafe { args.offset(1) };
657622
Scalar::from_pointer(ptr, infos.this)
658623
}
@@ -662,60 +627,13 @@ unsafe extern "C" fn callback_callback(
662627
rust_args.push(scalar);
663628
}
664629

665-
CALLBACK_MESSAGES.with_borrow_mut(|msgs| {
666-
msgs.push(CallbackError {
667-
message: format!("Tried to call a function pointer via FFI boundary. \
668-
That's not supported yet by miri\n This function pointer was registered by a call to `{}` \
669-
using an argument of the type `{}`", infos.link_name, infos.ty)
670-
.into(),
671-
});
672-
});
673-
674-
// write here the output
675-
// For now we just try to write some dummy output
676-
// by using some "reasonable" default values
677-
// to prevent crashing
678-
match infos.result.kind() {
679-
ty::RawPtr(..) => {
680-
write_helper::<*mut c_void>(result, std::ptr::null_mut());
681-
}
682-
ty::Int(IntTy::I8) => {
683-
write_helper::<i8>(result, 0);
684-
}
685-
ty::Int(IntTy::I16) => {
686-
write_helper::<i32>(result, 0);
687-
}
688-
ty::Int(IntTy::I32) => {
689-
write_helper::<i32>(result, 0);
690-
}
691-
ty::Int(IntTy::I64) => {
692-
write_helper::<i64>(result, 0);
693-
}
694-
ty::Int(IntTy::Isize) => {
695-
write_helper::<isize>(result, 0);
696-
}
697-
ty::Uint(UintTy::U8) => {
698-
write_helper::<u8>(result, 0);
699-
}
700-
ty::Uint(UintTy::U16) => {
701-
write_helper::<u16>(result, 0);
702-
}
703-
ty::Uint(UintTy::U32) => {
704-
write_helper::<u32>(result, 0);
705-
}
706-
ty::Uint(UintTy::U64) => {
707-
write_helper::<u64>(result, 0);
708-
}
709-
ty::Uint(UintTy::Usize) => {
710-
write_helper::<usize>(result, 0);
711-
}
712-
// unsure how to handle that at allow
713-
// Just do nothing for now?
714-
_ => {}
715-
};
716-
}
717-
718-
fn write_helper<T>(ptr: &mut c_void, value: T) {
719-
let ptr = (ptr as *mut c_void) as *mut T;
720-
unsafe { std::ptr::write(ptr, value) };
630+
// We abort the execution at this point as we cannot return the
631+
// expected value here.
632+
eprintln!(
633+
"Tried to call a function pointer via FFI boundary. \
634+
That's not supported yet by miri\nThis function pointer was registered by a call to `{}` \
635+
using an argument of the type `{}`",
636+
"todo: fill in name", "todo: fill in type"
637+
);
638+
std::process::exit(1);
721639
}

tests/native-lib/ptr_read_access.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,10 @@ EXPORT uintptr_t do_one_deref(const int32_t ***ptr) {
6868
EXPORT void pass_fn_ptr(void f(void)) {
6969
(void)f; // suppress unused warning
7070
}
71+
72+
/* Test: function_ptrs */
73+
EXPORT void call_fn_ptr(void f(void)) {
74+
if (f != NULL) {
75+
f();
76+
}
77+
}

0 commit comments

Comments
 (0)