Skip to content

Commit ffaf5b1

Browse files
committed
native-lib: support all types with Scalar layout
1 parent 7867c8e commit ffaf5b1

File tree

6 files changed

+117
-24
lines changed

6 files changed

+117
-24
lines changed

src/diagnostics.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ pub enum NonHaltingDiagnostic {
139139
NativeCallSharedMem {
140140
tracing: bool,
141141
},
142+
NativeCallFnPtr,
142143
WeakMemoryOutdatedLoad {
143144
ptr: Pointer,
144145
},
@@ -644,6 +645,11 @@ impl<'tcx> MiriMachine<'tcx> {
644645
Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
645646
NativeCallSharedMem { .. } =>
646647
("sharing memory with a native function".to_string(), DiagLevel::Warning),
648+
NativeCallFnPtr =>
649+
(
650+
"sharing a function pointer with a native function".to_string(),
651+
DiagLevel::Warning,
652+
),
647653
ExternTypeReborrow =>
648654
("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
649655
GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } =>
@@ -682,6 +688,8 @@ impl<'tcx> MiriMachine<'tcx> {
682688
Int2Ptr { .. } => format!("integer-to-pointer cast"),
683689
NativeCallSharedMem { .. } =>
684690
format!("sharing memory with a native function called via FFI"),
691+
NativeCallFnPtr =>
692+
format!("sharing a function pointer with a native function called via FFI"),
685693
WeakMemoryOutdatedLoad { ptr } =>
686694
format!("weak memory emulation: outdated value returned from load at {ptr}"),
687695
ExternTypeReborrow =>
@@ -779,6 +787,11 @@ impl<'tcx> MiriMachine<'tcx> {
779787
),
780788
]
781789
},
790+
NativeCallFnPtr => {
791+
vec![note!(
792+
"calling Rust functions from C is not supported and will, in the best case, crash the program"
793+
)]
794+
}
782795
ExternTypeReborrow => {
783796
assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
784797
matches!(

src/shims/native_lib/mod.rs

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::sync::atomic::AtomicBool;
66
use libffi::low::CodePtr;
77
use libffi::middle::Type as FfiType;
88
use rustc_abi::{HasDataLayout, Size};
9-
use rustc_middle::ty::layout::HasTypingEnv;
9+
use rustc_middle::ty::layout::TyAndLayout;
1010
use rustc_middle::ty::{self, IntTy, Ty, UintTy};
1111
use rustc_span::Symbol;
1212
use serde::{Deserialize, Serialize};
@@ -277,7 +277,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
277277

278278
// This should go first so that we emit unsupported before doing a bunch
279279
// of extra work for types that aren't supported yet.
280-
let ty = this.ty_to_ffitype(v.layout.ty)?;
280+
let ty = this.ty_to_ffitype(v.layout)?;
281281

282282
// Helper to print a warning when a pointer is shared with the native code.
283283
let expose = |prov: Provenance| -> InterpResult<'tcx> {
@@ -386,34 +386,44 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
386386
let this = self.eval_context_ref();
387387
let mut fields = vec![];
388388
for field in &adt_def.non_enum_variant().fields {
389-
fields.push(this.ty_to_ffitype(field.ty(*this.tcx, args))?);
389+
let layout = this.layout_of(field.ty(*this.tcx, args))?;
390+
fields.push(this.ty_to_ffitype(layout)?);
390391
}
391392

392393
interp_ok(FfiType::structure(fields))
393394
}
394395

395396
/// Gets the matching libffi type for a given Ty.
396-
fn ty_to_ffitype(&self, ty: Ty<'tcx>) -> InterpResult<'tcx, FfiType> {
397-
let this = self.eval_context_ref();
398-
interp_ok(match ty.kind() {
399-
ty::Int(IntTy::I8) => FfiType::i8(),
400-
ty::Int(IntTy::I16) => FfiType::i16(),
401-
ty::Int(IntTy::I32) => FfiType::i32(),
402-
ty::Int(IntTy::I64) => FfiType::i64(),
403-
ty::Int(IntTy::Isize) => FfiType::isize(),
404-
ty::Uint(UintTy::U8) => FfiType::u8(),
405-
ty::Uint(UintTy::U16) => FfiType::u16(),
406-
ty::Uint(UintTy::U32) => FfiType::u32(),
407-
ty::Uint(UintTy::U64) => FfiType::u64(),
408-
ty::Uint(UintTy::Usize) => FfiType::usize(),
409-
ty::RawPtr(pointee_ty, _mut) => {
410-
if !pointee_ty.is_sized(*this.tcx, this.typing_env()) {
411-
throw_unsup_format!("passing a pointer to an unsized type over FFI: {}", ty);
412-
}
413-
FfiType::pointer()
414-
}
415-
ty::Adt(adt_def, args) => self.adt_to_ffitype(ty, *adt_def, args)?,
416-
_ => throw_unsup_format!("unsupported argument type for native call: {}", ty),
397+
fn ty_to_ffitype(&self, layout: TyAndLayout<'tcx>) -> InterpResult<'tcx, FfiType> {
398+
use rustc_abi::{AddressSpace, BackendRepr, Integer, Primitive};
399+
400+
// `BackendRepr::Scalar` is also a signal to pass this type as a scalar in the ABI. This
401+
// matches what codegen does. This does mean that we support some types whose ABI is not
402+
// stable, but that's fine -- we are anyway quite conservative in native-lib mode.
403+
if let BackendRepr::Scalar(s) = layout.backend_repr {
404+
// Simple sanity-check: this cannot be `repr(C)`.
405+
assert!(!layout.ty.ty_adt_def().is_some_and(|adt| adt.repr().c()));
406+
return interp_ok(match s.primitive() {
407+
Primitive::Int(Integer::I8, /* signed */ true) => FfiType::i8(),
408+
Primitive::Int(Integer::I16, /* signed */ true) => FfiType::i16(),
409+
Primitive::Int(Integer::I32, /* signed */ true) => FfiType::i32(),
410+
Primitive::Int(Integer::I64, /* signed */ true) => FfiType::i64(),
411+
Primitive::Int(Integer::I8, /* signed */ false) => FfiType::u8(),
412+
Primitive::Int(Integer::I16, /* signed */ false) => FfiType::u16(),
413+
Primitive::Int(Integer::I32, /* signed */ false) => FfiType::u32(),
414+
Primitive::Int(Integer::I64, /* signed */ false) => FfiType::u64(),
415+
Primitive::Pointer(AddressSpace::ZERO) => FfiType::pointer(),
416+
_ =>
417+
throw_unsup_format!(
418+
"unsupported scalar argument type for native call: {}",
419+
layout.ty
420+
),
421+
});
422+
}
423+
interp_ok(match layout.ty.kind() {
424+
// Scalar types have already been handled above.
425+
ty::Adt(adt_def, args) => self.adt_to_ffitype(layout.ty, *adt_def, args)?,
426+
_ => throw_unsup_format!("unsupported argument type for native call: {}", layout.ty),
417427
})
418428
}
419429
}
@@ -454,6 +464,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
454464
// pointer was passed as argument). Uninitialised memory is left as-is, but any data
455465
// exposed this way is garbage anyway.
456466
this.visit_reachable_allocs(this.exposed_allocs(), |this, alloc_id, info| {
467+
if matches!(info.kind, AllocKind::Function) {
468+
static DEDUP: AtomicBool = AtomicBool::new(false);
469+
if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
470+
// Newly set, so first time we get here.
471+
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallFnPtr);
472+
}
473+
}
457474
// If there is no data behind this pointer, skip this.
458475
if !matches!(info.kind, AllocKind::LiveData) {
459476
return interp_ok(());

tests/native-lib/pass/ptr_read_access.notrace.stderr

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,18 @@ note: inside `main`
1616
LL | test_access_pointer();
1717
| ^^^^^^^^^^^^^^^^^^^^^
1818

19+
warning: sharing a function pointer with a native function called via FFI
20+
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
21+
|
22+
LL | pass_fn_ptr(Some(nop)); // this one is not
23+
| ^^^^^^^^^^^^^^^^^^^^^^ sharing a function pointer with a native function
24+
|
25+
= help: calling Rust functions from C is not supported and will, in the best case, crash the program
26+
= note: BACKTRACE:
27+
= note: inside `pass_fn_ptr` at tests/native-lib/pass/ptr_read_access.rs:LL:CC
28+
note: inside `main`
29+
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
30+
|
31+
LL | pass_fn_ptr();
32+
| ^^^^^^^^^^^^^
33+

tests/native-lib/pass/ptr_read_access.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
//@[trace] compile-flags: -Zmiri-native-lib-enable-tracing
44
//@compile-flags: -Zmiri-permissive-provenance
55

6+
use std::ptr::NonNull;
7+
68
fn main() {
79
test_access_pointer();
810
test_access_simple();
911
test_access_nested();
1012
test_access_static();
13+
pass_fn_ptr();
1114
}
1215

1316
/// Test function that dereferences an int pointer and prints its contents from C.
@@ -30,11 +33,15 @@ fn test_access_simple() {
3033

3134
extern "C" {
3235
fn access_simple(s_ptr: *const Simple) -> i32;
36+
fn access_simple2(s_ptr: NonNull<Simple>) -> i32;
37+
fn access_simple3(s_ptr: Option<NonNull<Simple>>) -> i32;
3338
}
3439

3540
let simple = Simple { field: -42 };
3641

3742
assert_eq!(unsafe { access_simple(&simple) }, -42);
43+
assert_eq!(unsafe { access_simple2(NonNull::from(&simple)) }, -42);
44+
assert_eq!(unsafe { access_simple3(Some(NonNull::from(&simple))) }, -42);
3845
}
3946

4047
/// Test function that dereferences nested struct pointers and accesses fields.
@@ -75,3 +82,16 @@ fn test_access_static() {
7582

7683
assert_eq!(unsafe { access_static(&STATIC) }, 9001);
7784
}
85+
86+
fn pass_fn_ptr() {
87+
extern "C" {
88+
fn pass_fn_ptr(s: Option<extern "C" fn()>);
89+
}
90+
91+
extern "C" fn nop() {}
92+
93+
unsafe {
94+
pass_fn_ptr(None); // this one is fine
95+
pass_fn_ptr(Some(nop)); // this one is not
96+
}
97+
}

tests/native-lib/pass/ptr_read_access.trace.stderr

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,18 @@ note: inside `main`
1717
LL | test_access_pointer();
1818
| ^^^^^^^^^^^^^^^^^^^^^
1919

20+
warning: sharing a function pointer with a native function called via FFI
21+
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
22+
|
23+
LL | pass_fn_ptr(Some(nop)); // this one is not
24+
| ^^^^^^^^^^^^^^^^^^^^^^ sharing a function pointer with a native function
25+
|
26+
= help: calling Rust functions from C is not supported and will, in the best case, crash the program
27+
= note: BACKTRACE:
28+
= note: inside `pass_fn_ptr` at tests/native-lib/pass/ptr_read_access.rs:LL:CC
29+
note: inside `main`
30+
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
31+
|
32+
LL | pass_fn_ptr();
33+
| ^^^^^^^^^^^^^
34+

tests/native-lib/ptr_read_access.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ typedef struct Simple {
1919
EXPORT int32_t access_simple(const Simple *s_ptr) {
2020
return s_ptr->field;
2121
}
22+
// Some copies so Rust can import them at different types.
23+
EXPORT int32_t access_simple2(const Simple *s_ptr) {
24+
return s_ptr->field;
25+
}
26+
EXPORT int32_t access_simple3(const Simple *s_ptr) {
27+
return s_ptr->field;
28+
}
2229

2330
/* Test: test_access_nested */
2431

@@ -55,3 +62,9 @@ EXPORT int32_t access_static(const Static *s_ptr) {
5562
EXPORT uintptr_t do_one_deref(const int32_t ***ptr) {
5663
return (uintptr_t)*ptr;
5764
}
65+
66+
/* Test: pass_fn_ptr */
67+
68+
EXPORT void pass_fn_ptr(void f(void)) {
69+
(void)f; // suppress unused warning
70+
}

0 commit comments

Comments
 (0)