Skip to content

Commit 4b82474

Browse files
nia-eRalfJung
authored andcommitted
native-lib: pass structs to native code
1 parent 3358569 commit 4b82474

14 files changed

+467
-129
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ features = ['unprefixed_malloc_on_supported_platforms']
3939
[target.'cfg(unix)'.dependencies]
4040
libc = "0.2"
4141
# native-lib dependencies
42-
libffi = { version = "4.0.0", optional = true }
42+
libffi = { version = "4.1.1", optional = true }
4343
libloading = { version = "0.8", optional = true }
4444
serde = { version = "1.0.219", features = ["derive"], optional = true }
4545

src/shims/native_lib/ffi.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use libffi::low::CodePtr;
2+
use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType};
3+
4+
/// Perform the actual FFI call.
5+
///
6+
/// SAFETY: The safety invariants of the foreign function being called must be
7+
/// upheld (if any).
8+
pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &mut [OwnedArg]) -> R {
9+
let mut arg_tys = vec![];
10+
let mut arg_ptrs = vec![];
11+
for arg in args {
12+
arg_tys.push(arg.take_ty());
13+
arg_ptrs.push(arg.ptr())
14+
}
15+
let cif = Cif::new(arg_tys, R::reify().into_middle());
16+
// SAFETY: Caller upholds that the function is safe to call, and since we
17+
// were passed a slice reference we know the `OwnedArg`s won't have been
18+
// dropped by this point.
19+
unsafe { cif.call(fun, &arg_ptrs) }
20+
}
21+
22+
/// An argument for an FFI call.
23+
#[derive(Debug, Clone)]
24+
pub struct OwnedArg {
25+
/// The type descriptor for this argument.
26+
ty: Option<FfiType>,
27+
/// Corresponding bytes for the value.
28+
bytes: Box<[u8]>,
29+
}
30+
31+
impl OwnedArg {
32+
/// Instantiates an argument from a type descriptor and bytes.
33+
pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self {
34+
Self { ty: Some(ty), bytes }
35+
}
36+
37+
/// Gets the libffi type descriptor for this argument. Should only be
38+
/// called once on a given `OwnedArg`.
39+
fn take_ty(&mut self) -> FfiType {
40+
self.ty.take().unwrap()
41+
}
42+
43+
/// Instantiates a libffi argument pointer pointing to this argument's bytes.
44+
/// NB: Since `libffi::middle::Arg` ignores the lifetime of the reference
45+
/// it's derived from, it is up to the caller to ensure the `OwnedArg` is
46+
/// not dropped before unsafely calling `libffi::middle::Cif::call()`!
47+
fn ptr(&self) -> ArgPtr {
48+
// FIXME: Using `&self.bytes[0]` to reference the whole array is
49+
// definitely unsound under SB, but we're waiting on
50+
// https://github.com/libffi-rs/libffi-rs/commit/112a37b3b6ffb35bd75241fbcc580de40ba74a73
51+
// to land in a release so that we don't need to do this.
52+
ArgPtr::new(&self.bytes[0])
53+
}
54+
}

src/shims/native_lib/mod.rs

Lines changed: 177 additions & 128 deletions
Large diffs are not rendered by default.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <stdint.h>
2+
3+
// See comments in build_native_lib()
4+
#define EXPORT __attribute__((visibility("default")))
5+
6+
/* Test: test_pass_struct */
7+
8+
typedef struct PassMe {
9+
int32_t value;
10+
int64_t other_value;
11+
} PassMe;
12+
13+
EXPORT int64_t pass_struct(const PassMe pass_me) {
14+
return pass_me.value + pass_me.other_value;
15+
}
16+
17+
/* Test: test_pass_struct_complex */
18+
19+
typedef struct Part1 {
20+
uint16_t high;
21+
uint16_t low;
22+
} Part1;
23+
24+
typedef struct Part2 {
25+
uint32_t bits;
26+
} Part2;
27+
28+
typedef struct ComplexStruct {
29+
Part1 part_1;
30+
Part2 part_2;
31+
uint32_t part_3;
32+
} ComplexStruct;
33+
34+
EXPORT int32_t pass_struct_complex(const ComplexStruct complex, uint16_t high, uint16_t low, uint32_t bits) {
35+
if (complex.part_1.high == high && complex.part_1.low == low
36+
&& complex.part_2.bits == bits
37+
&& complex.part_3 == bits)
38+
return 0;
39+
else {
40+
return 1;
41+
}
42+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@compile-flags: -Zmiri-permissive-provenance
2+
3+
#[repr(C)]
4+
#[derive(Copy, Clone)]
5+
struct HasPointer {
6+
ptr: *const u8,
7+
}
8+
9+
extern "C" {
10+
fn access_struct_ptr(s: HasPointer) -> u8;
11+
}
12+
13+
fn main() {
14+
let vals = [10u8, 20u8];
15+
let structs =
16+
vec![HasPointer { ptr: &raw const vals[0] }, HasPointer { ptr: &raw const vals[1] }];
17+
unsafe {
18+
access_struct_ptr(structs[1]);
19+
let _val = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr()); //~ ERROR: Undefined Behavior: attempting a read access using <wildcard>
20+
};
21+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
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
2+
--> tests/native-lib/fail/multi_struct_alloc.rs:LL:CC
3+
|
4+
LL | ...al = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this error occurs as part of an access at ALLOC[0x0..0x1]
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
8+
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at tests/native-lib/fail/multi_struct_alloc.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Only works on Unix targets
2+
//@ignore-target: windows wasm
3+
//@only-on-host
4+
5+
#![allow(improper_ctypes)]
6+
7+
pub struct PassMe {
8+
pub value: i32,
9+
pub other_value: i64,
10+
}
11+
12+
extern "C" {
13+
fn pass_struct(s: PassMe) -> i64;
14+
}
15+
16+
fn main() {
17+
let pass_me = PassMe { value: 42, other_value: 1337 };
18+
unsafe { pass_struct(pass_me) }; //~ ERROR: unsupported operation: passing a non-#[repr(C)] struct over FFI
19+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unsupported operation: passing a non-#[repr(C)] struct over FFI: PassMe
2+
--> tests/native-lib/fail/struct_not_extern_c.rs:LL:CC
3+
|
4+
LL | unsafe { pass_struct(pass_me) };
5+
| ^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
6+
|
7+
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8+
= note: BACKTRACE:
9+
= note: inside `main` at tests/native-lib/fail/struct_not_extern_c.rs:LL:CC
10+
11+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12+
13+
error: aborting due to 1 previous error
14+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#[repr(C)]
2+
#[derive(Copy, Clone)]
3+
struct ComplexStruct {
4+
part_1: Part1,
5+
part_2: Part2,
6+
part_3: u32,
7+
}
8+
#[repr(C)]
9+
#[derive(Copy, Clone)]
10+
struct Part1 {
11+
high: u16,
12+
low: u16,
13+
}
14+
#[repr(C)]
15+
#[derive(Copy, Clone)]
16+
struct Part2 {
17+
bits: u32,
18+
}
19+
20+
extern "C" {
21+
fn pass_struct_complex(s: ComplexStruct, high: u16, low: u16, bits: u32) -> i32;
22+
}
23+
24+
fn main() {
25+
let arg = std::mem::MaybeUninit::<ComplexStruct>::uninit();
26+
unsafe { pass_struct_complex(*arg.as_ptr(), 0, 0, 0) }; //~ ERROR: Undefined Behavior: constructing invalid value
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: constructing invalid value at .part_1.high: encountered uninitialized memory, but expected an integer
2+
--> tests/native-lib/fail/uninit_struct.rs:LL:CC
3+
|
4+
LL | unsafe { pass_struct_complex(*arg.as_ptr(), 0, 0, 0) };
5+
| ^^^^^^^^^^^^^ Undefined Behavior occurred here
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at tests/native-lib/fail/uninit_struct.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+

0 commit comments

Comments
 (0)