native-lib: pass structs to native code
This commit is contained in:
parent
7f6f741eba
commit
4dbadd05f8
14 changed files with 467 additions and 129 deletions
|
|
@ -39,7 +39,7 @@ features = ['unprefixed_malloc_on_supported_platforms']
|
|||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
# native-lib dependencies
|
||||
libffi = { version = "4.0.0", optional = true }
|
||||
libffi = { version = "4.1.1", optional = true }
|
||||
libloading = { version = "0.8", optional = true }
|
||||
serde = { version = "1.0.219", features = ["derive"], optional = true }
|
||||
|
||||
|
|
|
|||
54
src/tools/miri/src/shims/native_lib/ffi.rs
Normal file
54
src/tools/miri/src/shims/native_lib/ffi.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use libffi::low::CodePtr;
|
||||
use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType};
|
||||
|
||||
/// Perform the actual FFI call.
|
||||
///
|
||||
/// SAFETY: The safety invariants of the foreign function being called must be
|
||||
/// upheld (if any).
|
||||
pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &mut [OwnedArg]) -> R {
|
||||
let mut arg_tys = vec![];
|
||||
let mut arg_ptrs = vec![];
|
||||
for arg in args {
|
||||
arg_tys.push(arg.take_ty());
|
||||
arg_ptrs.push(arg.ptr())
|
||||
}
|
||||
let cif = Cif::new(arg_tys, R::reify().into_middle());
|
||||
// SAFETY: Caller upholds that the function is safe to call, and since we
|
||||
// were passed a slice reference we know the `OwnedArg`s won't have been
|
||||
// dropped by this point.
|
||||
unsafe { cif.call(fun, &arg_ptrs) }
|
||||
}
|
||||
|
||||
/// An argument for an FFI call.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OwnedArg {
|
||||
/// The type descriptor for this argument.
|
||||
ty: Option<FfiType>,
|
||||
/// Corresponding bytes for the value.
|
||||
bytes: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl OwnedArg {
|
||||
/// Instantiates an argument from a type descriptor and bytes.
|
||||
pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self {
|
||||
Self { ty: Some(ty), bytes }
|
||||
}
|
||||
|
||||
/// Gets the libffi type descriptor for this argument. Should only be
|
||||
/// called once on a given `OwnedArg`.
|
||||
fn take_ty(&mut self) -> FfiType {
|
||||
self.ty.take().unwrap()
|
||||
}
|
||||
|
||||
/// Instantiates a libffi argument pointer pointing to this argument's bytes.
|
||||
/// NB: Since `libffi::middle::Arg` ignores the lifetime of the reference
|
||||
/// it's derived from, it is up to the caller to ensure the `OwnedArg` is
|
||||
/// not dropped before unsafely calling `libffi::middle::Cif::call()`!
|
||||
fn ptr(&self) -> ArgPtr {
|
||||
// FIXME: Using `&self.bytes[0]` to reference the whole array is
|
||||
// definitely unsound under SB, but we're waiting on
|
||||
// https://github.com/libffi-rs/libffi-rs/commit/112a37b3b6ffb35bd75241fbcc580de40ba74a73
|
||||
// to land in a release so that we don't need to do this.
|
||||
ArgPtr::new(&self.bytes[0])
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,15 @@
|
|||
|
||||
use std::ops::Deref;
|
||||
|
||||
use libffi::high::call as ffi;
|
||||
use libffi::low::CodePtr;
|
||||
use rustc_abi::{BackendRepr, HasDataLayout, Size};
|
||||
use rustc_middle::mir::interpret::Pointer;
|
||||
use rustc_middle::ty::{self as ty, IntTy, UintTy};
|
||||
use libffi::middle::Type as FfiType;
|
||||
use rustc_abi::{HasDataLayout, Size};
|
||||
use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
|
||||
use rustc_span::Symbol;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod ffi;
|
||||
|
||||
#[cfg_attr(
|
||||
not(all(
|
||||
target_os = "linux",
|
||||
|
|
@ -20,6 +21,7 @@ use serde::{Deserialize, Serialize};
|
|||
)]
|
||||
pub mod trace;
|
||||
|
||||
use self::ffi::OwnedArg;
|
||||
use crate::*;
|
||||
|
||||
/// The final results of an FFI trace, containing every relevant event detected
|
||||
|
|
@ -70,12 +72,12 @@ impl AccessRange {
|
|||
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
||||
/// Call native host function and return the output as an immediate.
|
||||
fn call_native_with_args<'a>(
|
||||
fn call_native_with_args(
|
||||
&mut self,
|
||||
link_name: Symbol,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
ptr: CodePtr,
|
||||
libffi_args: Vec<libffi::high::Arg<'a>>,
|
||||
libffi_args: &mut [OwnedArg],
|
||||
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
|
||||
let this = self.eval_context_mut();
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
@ -93,55 +95,55 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Unsafe because of the call to native code.
|
||||
// Because this is calling a C function it is not necessarily sound,
|
||||
// but there is no way around this and we've checked as much as we can.
|
||||
let x = unsafe { ffi::call::<i8>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<i8>(ptr, libffi_args) };
|
||||
Scalar::from_i8(x)
|
||||
}
|
||||
ty::Int(IntTy::I16) => {
|
||||
let x = unsafe { ffi::call::<i16>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<i16>(ptr, libffi_args) };
|
||||
Scalar::from_i16(x)
|
||||
}
|
||||
ty::Int(IntTy::I32) => {
|
||||
let x = unsafe { ffi::call::<i32>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<i32>(ptr, libffi_args) };
|
||||
Scalar::from_i32(x)
|
||||
}
|
||||
ty::Int(IntTy::I64) => {
|
||||
let x = unsafe { ffi::call::<i64>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<i64>(ptr, libffi_args) };
|
||||
Scalar::from_i64(x)
|
||||
}
|
||||
ty::Int(IntTy::Isize) => {
|
||||
let x = unsafe { ffi::call::<isize>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<isize>(ptr, libffi_args) };
|
||||
Scalar::from_target_isize(x.try_into().unwrap(), this)
|
||||
}
|
||||
// uints
|
||||
ty::Uint(UintTy::U8) => {
|
||||
let x = unsafe { ffi::call::<u8>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<u8>(ptr, libffi_args) };
|
||||
Scalar::from_u8(x)
|
||||
}
|
||||
ty::Uint(UintTy::U16) => {
|
||||
let x = unsafe { ffi::call::<u16>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<u16>(ptr, libffi_args) };
|
||||
Scalar::from_u16(x)
|
||||
}
|
||||
ty::Uint(UintTy::U32) => {
|
||||
let x = unsafe { ffi::call::<u32>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<u32>(ptr, libffi_args) };
|
||||
Scalar::from_u32(x)
|
||||
}
|
||||
ty::Uint(UintTy::U64) => {
|
||||
let x = unsafe { ffi::call::<u64>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<u64>(ptr, libffi_args) };
|
||||
Scalar::from_u64(x)
|
||||
}
|
||||
ty::Uint(UintTy::Usize) => {
|
||||
let x = unsafe { ffi::call::<usize>(ptr, libffi_args.as_slice()) };
|
||||
let x = unsafe { ffi::call::<usize>(ptr, libffi_args) };
|
||||
Scalar::from_target_usize(x.try_into().unwrap(), this)
|
||||
}
|
||||
// Functions with no declared return type (i.e., the default return)
|
||||
// have the output_type `Tuple([])`.
|
||||
ty::Tuple(t_list) if (*t_list).deref().is_empty() => {
|
||||
unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) };
|
||||
unsafe { ffi::call::<()>(ptr, libffi_args) };
|
||||
return interp_ok(ImmTy::uninit(dest.layout));
|
||||
}
|
||||
ty::RawPtr(..) => {
|
||||
let x = unsafe { ffi::call::<*const ()>(ptr, libffi_args.as_slice()) };
|
||||
let ptr = Pointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
|
||||
let x = unsafe { ffi::call::<*const ()>(ptr, libffi_args) };
|
||||
let ptr = StrictPointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
|
||||
Scalar::from_pointer(ptr, this)
|
||||
}
|
||||
_ =>
|
||||
|
|
@ -267,6 +269,158 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
/// Extract the value from the result of reading an operand from the machine
|
||||
/// and convert it to a `OwnedArg`.
|
||||
fn op_to_ffi_arg(&self, v: &OpTy<'tcx>, tracing: bool) -> InterpResult<'tcx, OwnedArg> {
|
||||
let this = self.eval_context_ref();
|
||||
|
||||
// This should go first so that we emit unsupported before doing a bunch
|
||||
// of extra work for types that aren't supported yet.
|
||||
let ty = this.ty_to_ffitype(v.layout.ty)?;
|
||||
|
||||
// Now grab the bytes of the argument.
|
||||
let bytes = match v.as_mplace_or_imm() {
|
||||
either::Either::Left(mplace) => {
|
||||
// Get the alloc id corresponding to this mplace, alongside
|
||||
// a pointer that's offset to point to this particular
|
||||
// mplace (not one at the base addr of the allocation).
|
||||
let mplace_ptr = mplace.ptr();
|
||||
let sz = mplace.layout.size.bytes_usize();
|
||||
if sz == 0 {
|
||||
throw_unsup_format!("attempting to pass a ZST over FFI");
|
||||
}
|
||||
let (id, ofs, _) = this.ptr_get_alloc_id(mplace_ptr, sz.try_into().unwrap())?;
|
||||
let ofs = ofs.bytes_usize();
|
||||
// Expose all provenances in the allocation within the byte
|
||||
// range of the struct, if any.
|
||||
let alloc = this.get_alloc_raw(id)?;
|
||||
let alloc_ptr = this.get_alloc_bytes_unchecked_raw(id)?;
|
||||
for prov in alloc.provenance().get_range(this, (ofs..ofs.strict_add(sz)).into()) {
|
||||
this.expose_provenance(prov)?;
|
||||
}
|
||||
// SAFETY: We know for sure that at alloc_ptr + ofs the next layout.size
|
||||
// bytes are part of this allocation and initialised. They might be marked
|
||||
// as uninit in Miri, but all bytes returned by `MiriAllocBytes` are
|
||||
// initialised.
|
||||
unsafe {
|
||||
Box::from(std::slice::from_raw_parts(
|
||||
alloc_ptr.add(ofs),
|
||||
mplace.layout.size.bytes_usize(),
|
||||
))
|
||||
}
|
||||
}
|
||||
either::Either::Right(imm) => {
|
||||
// A little helper to write scalars to our byte array.
|
||||
let write_scalar = |this: &MiriInterpCx<'tcx>, sc: Scalar, bytes: &mut [u8]| {
|
||||
// If a scalar is a pointer, then expose its provenance.
|
||||
if let interpret::Scalar::Ptr(p, _) = sc {
|
||||
// This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
|
||||
// below to expose the actual interpreter-level allocation.
|
||||
this.expose_and_warn(Some(p.provenance), tracing)?;
|
||||
}
|
||||
// `bytes[0]` should be the first byte we want to write to.
|
||||
write_target_uint(
|
||||
this.data_layout().endian,
|
||||
&mut bytes[..sc.size().bytes_usize()],
|
||||
sc.to_scalar_int()?.to_bits_unchecked(),
|
||||
)
|
||||
.unwrap();
|
||||
interp_ok(())
|
||||
};
|
||||
|
||||
let mut bytes: Box<[u8]> =
|
||||
(0..imm.layout.size.bytes_usize()).map(|_| 0u8).collect();
|
||||
|
||||
match *imm {
|
||||
Immediate::Scalar(sc) => write_scalar(this, sc, &mut bytes)?,
|
||||
Immediate::ScalarPair(sc_first, sc_second) => {
|
||||
// The first scalar has an offset of zero.
|
||||
let ofs_second = {
|
||||
let rustc_abi::BackendRepr::ScalarPair(a, b) = imm.layout.backend_repr
|
||||
else {
|
||||
span_bug!(
|
||||
this.cur_span(),
|
||||
"op_to_ffi_arg: invalid scalar pair layout: {:#?}",
|
||||
imm.layout
|
||||
)
|
||||
};
|
||||
a.size(this).align_to(b.align(this).abi).bytes_usize()
|
||||
};
|
||||
|
||||
write_scalar(this, sc_first, &mut bytes)?;
|
||||
write_scalar(this, sc_second, &mut bytes[ofs_second..])?;
|
||||
}
|
||||
Immediate::Uninit =>
|
||||
span_bug!(this.cur_span(), "op_to_ffi_arg: argument is uninit: {:#?}", imm),
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
};
|
||||
interp_ok(OwnedArg::new(ty, bytes))
|
||||
}
|
||||
|
||||
/// Parses an ADT to construct the matching libffi type.
|
||||
fn adt_to_ffitype(
|
||||
&self,
|
||||
orig_ty: Ty<'_>,
|
||||
adt_def: ty::AdtDef<'tcx>,
|
||||
args: &'tcx ty::List<ty::GenericArg<'tcx>>,
|
||||
) -> InterpResult<'tcx, FfiType> {
|
||||
// TODO: Certain non-C reprs should be okay also.
|
||||
if !adt_def.repr().c() {
|
||||
throw_unsup_format!("passing a non-#[repr(C)] struct over FFI: {orig_ty}")
|
||||
}
|
||||
// TODO: unions, etc.
|
||||
if !adt_def.is_struct() {
|
||||
throw_unsup_format!(
|
||||
"unsupported argument type for native call: {orig_ty} is an enum or union"
|
||||
);
|
||||
}
|
||||
|
||||
let this = self.eval_context_ref();
|
||||
let mut fields = vec![];
|
||||
for field in adt_def.all_fields() {
|
||||
fields.push(this.ty_to_ffitype(field.ty(*this.tcx, args))?);
|
||||
}
|
||||
|
||||
interp_ok(FfiType::structure(fields))
|
||||
}
|
||||
|
||||
/// Gets the matching libffi type for a given Ty.
|
||||
fn ty_to_ffitype(&self, ty: Ty<'tcx>) -> InterpResult<'tcx, FfiType> {
|
||||
interp_ok(match ty.kind() {
|
||||
ty::Int(IntTy::I8) => FfiType::i8(),
|
||||
ty::Int(IntTy::I16) => FfiType::i16(),
|
||||
ty::Int(IntTy::I32) => FfiType::i32(),
|
||||
ty::Int(IntTy::I64) => FfiType::i64(),
|
||||
ty::Int(IntTy::Isize) => FfiType::isize(),
|
||||
// the uints
|
||||
ty::Uint(UintTy::U8) => FfiType::u8(),
|
||||
ty::Uint(UintTy::U16) => FfiType::u16(),
|
||||
ty::Uint(UintTy::U32) => FfiType::u32(),
|
||||
ty::Uint(UintTy::U64) => FfiType::u64(),
|
||||
ty::Uint(UintTy::Usize) => FfiType::usize(),
|
||||
ty::RawPtr(..) => FfiType::pointer(),
|
||||
ty::Adt(adt_def, args) => self.adt_to_ffitype(ty, *adt_def, args)?,
|
||||
_ => throw_unsup_format!("unsupported argument type for native call: {}", ty),
|
||||
})
|
||||
}
|
||||
|
||||
fn expose_and_warn(&self, prov: Option<Provenance>, tracing: bool) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_ref();
|
||||
if let Some(prov) = prov {
|
||||
// The first time this happens, print a warning.
|
||||
if !this.machine.native_call_mem_warned.replace(true) {
|
||||
// Newly set, so first time we get here.
|
||||
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
|
||||
}
|
||||
|
||||
this.expose_provenance(prov)?;
|
||||
};
|
||||
interp_ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
|
|
@ -295,36 +449,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
// Do we have ptrace?
|
||||
let tracing = trace::Supervisor::is_enabled();
|
||||
|
||||
// Get the function arguments, and convert them to `libffi`-compatible form.
|
||||
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
|
||||
// Get the function arguments, copy them, and prepare the type descriptions.
|
||||
let mut libffi_args = Vec::<OwnedArg>::with_capacity(args.len());
|
||||
for arg in args.iter() {
|
||||
if !matches!(arg.layout.backend_repr, BackendRepr::Scalar(_)) {
|
||||
throw_unsup_format!("only scalar argument types are supported for native calls")
|
||||
}
|
||||
let imm = this.read_immediate(arg)?;
|
||||
libffi_args.push(imm_to_carg(&imm, this)?);
|
||||
// If we are passing a pointer, expose its provenance. Below, all exposed memory
|
||||
// (previously exposed and new exposed) will then be properly prepared.
|
||||
if matches!(arg.layout.ty.kind(), ty::RawPtr(..)) {
|
||||
let ptr = imm.to_scalar().to_pointer(this)?;
|
||||
let Some(prov) = ptr.provenance else {
|
||||
// Pointer without provenance may not access any memory anyway, skip.
|
||||
continue;
|
||||
};
|
||||
// The first time this happens, print a warning.
|
||||
if !this.machine.native_call_mem_warned.replace(true) {
|
||||
// Newly set, so first time we get here.
|
||||
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
|
||||
}
|
||||
|
||||
this.expose_provenance(prov)?;
|
||||
}
|
||||
libffi_args.push(this.op_to_ffi_arg(arg, tracing)?);
|
||||
}
|
||||
// Convert arguments to `libffi::high::Arg` type.
|
||||
let libffi_args = libffi_args
|
||||
.iter()
|
||||
.map(|arg| arg.arg_downcast())
|
||||
.collect::<Vec<libffi::high::Arg<'_>>>();
|
||||
|
||||
// Prepare all exposed memory (both previously exposed, and just newly exposed since a
|
||||
// pointer was passed as argument). Uninitialised memory is left as-is, but any data
|
||||
|
|
@ -367,7 +496,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// Call the function and store output, depending on return type in the function signature.
|
||||
let (ret, maybe_memevents) =
|
||||
this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
|
||||
this.call_native_with_args(link_name, dest, code_ptr, &mut libffi_args)?;
|
||||
|
||||
if tracing {
|
||||
this.tracing_apply_accesses(maybe_memevents.unwrap())?;
|
||||
|
|
@ -377,83 +506,3 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
interp_ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Enum of supported arguments to external C functions.
|
||||
// We introduce this enum instead of just calling `ffi::arg` and storing a list
|
||||
// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
|
||||
// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
|
||||
// and we need to store a copy of the value, and pass a reference to this copy to C instead.
|
||||
enum CArg {
|
||||
/// 8-bit signed integer.
|
||||
Int8(i8),
|
||||
/// 16-bit signed integer.
|
||||
Int16(i16),
|
||||
/// 32-bit signed integer.
|
||||
Int32(i32),
|
||||
/// 64-bit signed integer.
|
||||
Int64(i64),
|
||||
/// isize.
|
||||
ISize(isize),
|
||||
/// 8-bit unsigned integer.
|
||||
UInt8(u8),
|
||||
/// 16-bit unsigned integer.
|
||||
UInt16(u16),
|
||||
/// 32-bit unsigned integer.
|
||||
UInt32(u32),
|
||||
/// 64-bit unsigned integer.
|
||||
UInt64(u64),
|
||||
/// usize.
|
||||
USize(usize),
|
||||
/// Raw pointer, stored as C's `void*`.
|
||||
RawPtr(*mut std::ffi::c_void),
|
||||
}
|
||||
|
||||
impl<'a> CArg {
|
||||
/// Convert a `CArg` to a `libffi` argument type.
|
||||
fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
|
||||
match self {
|
||||
CArg::Int8(i) => ffi::arg(i),
|
||||
CArg::Int16(i) => ffi::arg(i),
|
||||
CArg::Int32(i) => ffi::arg(i),
|
||||
CArg::Int64(i) => ffi::arg(i),
|
||||
CArg::ISize(i) => ffi::arg(i),
|
||||
CArg::UInt8(i) => ffi::arg(i),
|
||||
CArg::UInt16(i) => ffi::arg(i),
|
||||
CArg::UInt32(i) => ffi::arg(i),
|
||||
CArg::UInt64(i) => ffi::arg(i),
|
||||
CArg::USize(i) => ffi::arg(i),
|
||||
CArg::RawPtr(i) => ffi::arg(i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the scalar value from the result of reading a scalar from the machine,
|
||||
/// and convert it to a `CArg`.
|
||||
fn imm_to_carg<'tcx>(v: &ImmTy<'tcx>, cx: &impl HasDataLayout) -> InterpResult<'tcx, CArg> {
|
||||
interp_ok(match v.layout.ty.kind() {
|
||||
// If the primitive provided can be converted to a type matching the type pattern
|
||||
// then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
|
||||
// the ints
|
||||
ty::Int(IntTy::I8) => CArg::Int8(v.to_scalar().to_i8()?),
|
||||
ty::Int(IntTy::I16) => CArg::Int16(v.to_scalar().to_i16()?),
|
||||
ty::Int(IntTy::I32) => CArg::Int32(v.to_scalar().to_i32()?),
|
||||
ty::Int(IntTy::I64) => CArg::Int64(v.to_scalar().to_i64()?),
|
||||
ty::Int(IntTy::Isize) =>
|
||||
CArg::ISize(v.to_scalar().to_target_isize(cx)?.try_into().unwrap()),
|
||||
// the uints
|
||||
ty::Uint(UintTy::U8) => CArg::UInt8(v.to_scalar().to_u8()?),
|
||||
ty::Uint(UintTy::U16) => CArg::UInt16(v.to_scalar().to_u16()?),
|
||||
ty::Uint(UintTy::U32) => CArg::UInt32(v.to_scalar().to_u32()?),
|
||||
ty::Uint(UintTy::U64) => CArg::UInt64(v.to_scalar().to_u64()?),
|
||||
ty::Uint(UintTy::Usize) =>
|
||||
CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()),
|
||||
ty::RawPtr(..) => {
|
||||
let s = v.to_scalar().to_pointer(cx)?.addr();
|
||||
// This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
|
||||
// above.
|
||||
CArg::RawPtr(std::ptr::with_exposed_provenance_mut(s.bytes_usize()))
|
||||
}
|
||||
_ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
42
src/tools/miri/tests/native-lib/aggregate_arguments.c
Normal file
42
src/tools/miri/tests/native-lib/aggregate_arguments.c
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#include <stdint.h>
|
||||
|
||||
// See comments in build_native_lib()
|
||||
#define EXPORT __attribute__((visibility("default")))
|
||||
|
||||
/* Test: test_pass_struct */
|
||||
|
||||
typedef struct PassMe {
|
||||
int32_t value;
|
||||
int64_t other_value;
|
||||
} PassMe;
|
||||
|
||||
EXPORT int64_t pass_struct(const PassMe pass_me) {
|
||||
return pass_me.value + pass_me.other_value;
|
||||
}
|
||||
|
||||
/* Test: test_pass_struct_complex */
|
||||
|
||||
typedef struct Part1 {
|
||||
uint16_t high;
|
||||
uint16_t low;
|
||||
} Part1;
|
||||
|
||||
typedef struct Part2 {
|
||||
uint32_t bits;
|
||||
} Part2;
|
||||
|
||||
typedef struct ComplexStruct {
|
||||
Part1 part_1;
|
||||
Part2 part_2;
|
||||
uint32_t part_3;
|
||||
} ComplexStruct;
|
||||
|
||||
EXPORT int32_t pass_struct_complex(const ComplexStruct complex, uint16_t high, uint16_t low, uint32_t bits) {
|
||||
if (complex.part_1.high == high && complex.part_1.low == low
|
||||
&& complex.part_2.bits == bits
|
||||
&& complex.part_3 == bits)
|
||||
return 0;
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
21
src/tools/miri/tests/native-lib/fail/multi_struct_alloc.rs
Normal file
21
src/tools/miri/tests/native-lib/fail/multi_struct_alloc.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct HasPointer {
|
||||
ptr: *const u8,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn access_struct_ptr(s: HasPointer) -> u8;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let vals = [10u8, 20u8];
|
||||
let structs =
|
||||
vec![HasPointer { ptr: &raw const vals[0] }, HasPointer { ptr: &raw const vals[1] }];
|
||||
unsafe {
|
||||
access_struct_ptr(structs[1]);
|
||||
let _val = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr()); //~ ERROR: Undefined Behavior: attempting a read access using <wildcard>
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
error: Undefined Behavior: attempting a read access using <wildcard> at ALLOC[0x0], but no exposed tags have suitable permission in the borrow stack for this location
|
||||
--> tests/native-lib/fail/multi_struct_alloc.rs:LL:CC
|
||||
|
|
||||
LL | ...al = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x1]
|
||||
|
|
||||
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
|
||||
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
|
||||
= note: BACKTRACE:
|
||||
= note: inside `main` at tests/native-lib/fail/multi_struct_alloc.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
19
src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs
Normal file
19
src/tools/miri/tests/native-lib/fail/struct_not_extern_c.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Only works on Unix targets
|
||||
//@ignore-target: windows wasm
|
||||
//@only-on-host
|
||||
|
||||
#![allow(improper_ctypes)]
|
||||
|
||||
pub struct PassMe {
|
||||
pub value: i32,
|
||||
pub other_value: i64,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn pass_struct(s: PassMe) -> i64;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let pass_me = PassMe { value: 42, other_value: 1337 };
|
||||
unsafe { pass_struct(pass_me) }; //~ ERROR: unsupported operation: passing a non-#[repr(C)] struct over FFI
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
error: unsupported operation: passing a non-#[repr(C)] struct over FFI: PassMe
|
||||
--> tests/native-lib/fail/struct_not_extern_c.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { pass_struct(pass_me) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
|
||||
|
|
||||
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
|
||||
= note: BACKTRACE:
|
||||
= note: inside `main` at tests/native-lib/fail/struct_not_extern_c.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
27
src/tools/miri/tests/native-lib/fail/uninit_struct.rs
Normal file
27
src/tools/miri/tests/native-lib/fail/uninit_struct.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct ComplexStruct {
|
||||
part_1: Part1,
|
||||
part_2: Part2,
|
||||
part_3: u32,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct Part1 {
|
||||
high: u16,
|
||||
low: u16,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct Part2 {
|
||||
bits: u32,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn pass_struct_complex(s: ComplexStruct, high: u16, low: u16, bits: u32) -> i32;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arg = std::mem::MaybeUninit::<ComplexStruct>::uninit();
|
||||
unsafe { pass_struct_complex(*arg.as_ptr(), 0, 0, 0) }; //~ ERROR: Undefined Behavior: constructing invalid value
|
||||
}
|
||||
15
src/tools/miri/tests/native-lib/fail/uninit_struct.stderr
Normal file
15
src/tools/miri/tests/native-lib/fail/uninit_struct.stderr
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
error: Undefined Behavior: constructing invalid value at .part_1.high: encountered uninitialized memory, but expected an integer
|
||||
--> tests/native-lib/fail/uninit_struct.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { pass_struct_complex(*arg.as_ptr(), 0, 0, 0) };
|
||||
| ^^^^^^^^^^^^^ Undefined Behavior occurred here
|
||||
|
|
||||
= 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: BACKTRACE:
|
||||
= note: inside `main` at tests/native-lib/fail/uninit_struct.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
51
src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs
Normal file
51
src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
fn main() {
|
||||
test_pass_struct();
|
||||
test_pass_struct_complex();
|
||||
}
|
||||
|
||||
/// Test passing a basic struct as an argument.
|
||||
fn test_pass_struct() {
|
||||
#[repr(C)]
|
||||
struct PassMe {
|
||||
value: i32,
|
||||
other_value: i64,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn pass_struct(s: PassMe) -> i64;
|
||||
}
|
||||
|
||||
let pass_me = PassMe { value: 42, other_value: 1337 };
|
||||
assert_eq!(unsafe { pass_struct(pass_me) }, 42 + 1337);
|
||||
}
|
||||
|
||||
/// Test passing a more complex struct as an argument.
|
||||
fn test_pass_struct_complex() {
|
||||
#[repr(C)]
|
||||
struct ComplexStruct {
|
||||
part_1: Part1,
|
||||
part_2: Part2,
|
||||
part_3: u32,
|
||||
}
|
||||
#[repr(C)]
|
||||
struct Part1 {
|
||||
high: u16,
|
||||
low: u16,
|
||||
}
|
||||
#[repr(C)]
|
||||
struct Part2 {
|
||||
bits: u32,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn pass_struct_complex(s: ComplexStruct, high: u16, low: u16, bits: u32) -> i32;
|
||||
}
|
||||
|
||||
let high = 0xabcd;
|
||||
let low = 0xef01;
|
||||
let bits = 0xabcdef01;
|
||||
|
||||
let complex =
|
||||
ComplexStruct { part_1: Part1 { high, low }, part_2: Part2 { bits }, part_3: bits };
|
||||
assert_eq!(unsafe { pass_struct_complex(complex, high, low, bits) }, 0);
|
||||
}
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
//@revisions: trace notrace
|
||||
//@[trace] only-target: x86_64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||
//@[trace] compile-flags: -Zmiri-native-lib-enable-tracing
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
fn main() {
|
||||
test_access_pointer();
|
||||
test_access_simple();
|
||||
test_access_nested();
|
||||
test_access_static();
|
||||
test_access_struct_ptr();
|
||||
}
|
||||
|
||||
/// Test function that dereferences an int pointer and prints its contents from C.
|
||||
|
|
@ -74,3 +76,21 @@ fn test_access_static() {
|
|||
|
||||
assert_eq!(unsafe { access_static(&STATIC) }, 9001);
|
||||
}
|
||||
|
||||
/// Test exposing provenance from a field within a struct.
|
||||
fn test_access_struct_ptr() {
|
||||
#[repr(C)]
|
||||
struct HasPointer {
|
||||
ptr: *const u8,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// Return value exists only so the access isn't optimised away.
|
||||
fn access_struct_ptr(s: HasPointer) -> u8;
|
||||
}
|
||||
|
||||
let some_val = 42u8;
|
||||
let ptr = &raw const some_val;
|
||||
unsafe { access_struct_ptr(HasPointer { ptr }) };
|
||||
assert_eq!(some_val, unsafe { *std::ptr::with_exposed_provenance::<u8>(ptr.addr()) })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,3 +55,13 @@ EXPORT int32_t access_static(const Static *s_ptr) {
|
|||
EXPORT uintptr_t do_one_deref(const int32_t ***ptr) {
|
||||
return (uintptr_t)*ptr;
|
||||
}
|
||||
|
||||
/* Test: test_access_struct_ptr */
|
||||
|
||||
typedef struct HasPointer {
|
||||
uint8_t *ptr;
|
||||
} HasPointer;
|
||||
|
||||
EXPORT uint8_t access_struct_ptr(const HasPointer s) {
|
||||
return *s.ptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ fn build_native_lib(target: &str) -> PathBuf {
|
|||
native_lib_path.to_str().unwrap(),
|
||||
// FIXME: Automate gathering of all relevant C source files in the directory.
|
||||
"tests/native-lib/scalar_arguments.c",
|
||||
"tests/native-lib/aggregate_arguments.c",
|
||||
"tests/native-lib/ptr_read_access.c",
|
||||
"tests/native-lib/ptr_write_access.c",
|
||||
// Ensure we notice serious problems in the C code.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue