use super::*;
#[test]
fn opaque_owned_transmute_by_value() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
pub struct Payload {
pub inner: Vec<u8>,
}
);
let out_fn: syn::ItemFn = syn::parse_quote!(
pub fn z_payload_make() -> Payload {
unimplemented!()
}
);
let in_fn: syn::ItemFn = syn::parse_quote!(
pub fn z_payload_take(p: Payload) {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(out_fn), loc.clone()),
(syn::Item::Fn(in_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.opaque_owned_struct(syn::parse_quote!(Payload), syn::parse_quote!(OpaquePayload))
.base_name("z_payload_t")
.function(syn::parse_quote!(z_payload_make))
.function(syn::parse_quote!(z_payload_take))
.panic();
let src = write(cbindgen, registry, "value_opaque");
let compact: String = src.split_whitespace().collect();
assert!(
compact
.contains("size_of::<zenoh_flat::Payload>()==::core::mem::size_of::<OpaquePayload>()"),
"{src}"
);
assert!(
compact.contains(
"align_of::<zenoh_flat::Payload>()==::core::mem::align_of::<OpaquePayload>()"
),
"{src}"
);
assert!(
compact.contains("impl::prebindgen_c_runtime::TransmuteforOpaquePayload"),
"{src}"
);
assert!(compact.contains("typeRust=zenoh_flat::Payload;"), "{src}");
assert!(!compact.contains("Box::into_raw"), "{src}");
assert!(
compact.contains("<OpaquePayloadas::prebindgen_c_runtime::Transmute>::from_rust(v)"),
"{src}"
);
assert!(compact.contains("v:*mutOpaquePayload"), "{src}");
assert!(
compact.contains(
"<OpaquePayloadas::prebindgen_c_runtime::Transmute>::into_rust(::core::ptr::read(v),)"
),
"{src}"
);
assert!(
compact.contains(
"ptr::write(v,<OpaquePayloadas::prebindgen_c_runtime::Gravestone>::gravestone(),)"
),
"{src}"
);
assert!(
compact.contains("fnz_payload_t_drop(this_:*mutOpaquePayload)"),
"{src}"
);
assert!(
!compact.contains("is_gravestone"),
"drop must be unconditional: {src}"
);
assert!(
compact.contains("Transmute>::as_rust_mut(&mut*this_,)"),
"{src}"
);
}
#[test]
fn opaque_data_no_gravestone_writeback() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
pub struct Stamp {
pub ntp64: u64,
pub id: u64,
}
);
let out_fn: syn::ItemFn = syn::parse_quote!(
pub fn z_stamp_make() -> Stamp {
unimplemented!()
}
);
let in_fn: syn::ItemFn = syn::parse_quote!(
pub fn z_stamp_take(s: Stamp) {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(out_fn), loc.clone()),
(syn::Item::Fn(in_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.opaque_data_struct(syn::parse_quote!(Stamp), syn::parse_quote!(z_stamp_t))
.base_name("z_stamp_t")
.function(syn::parse_quote!(z_stamp_make))
.function(syn::parse_quote!(z_stamp_take))
.panic();
let src = write(cbindgen, registry, "opaque_data_struct");
let compact: String = src.split_whitespace().collect();
assert!(
compact.contains("impl::prebindgen_c_runtime::Transmuteforz_stamp_t"),
"{src}"
);
assert!(
compact.contains("align_of::<zenoh_flat::Stamp>()==::core::mem::align_of::<z_stamp_t>()"),
"{src}"
);
assert!(
compact.contains(
"<z_stamp_tas::prebindgen_c_runtime::Transmute>::into_rust(::core::ptr::read(v)"
),
"{src}"
);
assert!(
!compact.contains("Gravestone"),
"opaque_data_struct must not reference Gravestone: {src}"
);
assert!(
compact.contains("fnz_stamp_t_drop(this_:*mutz_stamp_t)"),
"{src}"
);
}
#[test]
fn repr_c_struct_visible_mirror_and_zero_copy_borrow() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
pub struct Foo {
pub a: u64,
pub s: Option<Box<String>>,
}
);
let make_fn: syn::ItemFn = syn::parse_quote!(
pub fn foo_make() -> Foo {
unimplemented!()
}
);
let put_fn: syn::ItemFn = syn::parse_quote!(
pub fn foo_put(p: &Foo) {
unimplemented!()
}
);
let cb_fn: syn::ItemFn = syn::parse_quote!(
pub fn foo_cb(f: impl Fn(&Foo) + Send + Sync + 'static) {
unimplemented!()
}
);
let string_fn: syn::ItemFn = syn::parse_quote!(
pub fn string_make() -> String {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(make_fn), loc.clone()),
(syn::Item::Fn(put_fn), loc.clone()),
(syn::Item::Fn(cb_fn), loc.clone()),
(syn::Item::Fn(string_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_callback(|bases| format!("closure_{}_t", bases.join("_")))
.mangle_function(|n| n.to_string())
.opaque_ptr(syn::parse_quote!(String))
.repr_c_struct(syn::parse_quote!(Foo))
.callback(syn::parse_quote!(impl Fn(&Foo) + Send + Sync + 'static))
.function(syn::parse_quote!(foo_make))
.function(syn::parse_quote!(foo_put))
.panic()
.function(syn::parse_quote!(foo_cb))
.function(syn::parse_quote!(string_make));
let src = write(cbindgen, registry, "repr_c_struct");
let compact: String = src.split_whitespace().collect();
assert!(compact.contains("pubstructfoo_t"), "{src}");
assert!(compact.contains("puba:u64"), "{src}");
assert!(compact.contains("pubs:*mutstring_t"), "{src}");
// The `String` opaque handle and its drop are emitted.
assert!(compact.contains("pubstructstring_t"), "{src}");
assert!(
compact.contains("fnstring_drop(this_:*mutstring_t)"),
"{src}"
);
// Value-opaque transmute glue + fail-closed size/align assert prove the
// whole-struct reinterpret sound.
assert!(
compact.contains("impl::prebindgen_c_runtime::Transmuteforfoo_t"),
"{src}"
);
assert!(
compact.contains("size_of::<zenoh_flat::Foo>()==::core::mem::size_of::<foo_t>()"),
"{src}"
);
// `&Foo` input is a zero-copy `*const foo_t` pointer cast (no field copy).
assert!(compact.contains("p:*constfoo_t"), "{src}");
assert!(compact.contains("&*(vas*constzenoh_flat::Foo)"), "{src}");
// `impl Fn(&Foo)` callback: the closure `call` takes a `const foo_t*`.
assert!(
compact.contains("*constfoo_t,*mut::core::ffi::c_void"),
"{src}"
);
}
/// A `repr_c_struct` with an opaque-pointer field is **inferred owned** (no `.owned()`):
/// a by-value consume reads the live value out through `*mut foo_t` and cleans the
/// moved-from slot so a later `_drop` is a no-op. Because the field is nullable
/// (`Option<Box<String>>`), the write-back nulls just that field (`(*v).s = null`) — so
/// **no** `Gravestone` impl is emitted and the source type needs **no** `Default`
/// (asserted via a `Foo` that does not derive `Default`).
#[test]
fn repr_c_struct_owned_inferred_field_nulls_without_default() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
pub struct Foo {
pub a: u64,
pub s: Option<Box<String>>,
}
);
// Consumes `Foo` by value.
let put_fn: syn::ItemFn = syn::parse_quote!(
pub fn foo_put(p: Foo) {
unimplemented!()
}
);
let string_fn: syn::ItemFn = syn::parse_quote!(
pub fn string_make() -> String {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(put_fn), loc.clone()),
(syn::Item::Fn(string_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.opaque_ptr(syn::parse_quote!(String))
.repr_c_struct(syn::parse_quote!(Foo))
.function(syn::parse_quote!(foo_put))
.panic()
.function(syn::parse_quote!(string_make));
let src = write(cbindgen, registry, "owned_inferred");
let compact: String = src.split_whitespace().collect();
// By-value consume: takes `*mut foo_t`, moves the value out, and nulls only the
// nullable owned-pointer field (`s`).
assert!(compact.contains("v:*mutfoo_t"), "{src}");
assert!(compact.contains("(*v).s=::core::ptr::null_mut();"), "{src}");
// No `Gravestone` impl and no `Default` requirement for a nullable mirror.
assert!(
!compact.contains("impl::prebindgen_c_runtime::Gravestoneforfoo_t"),
"nullable mirror should emit no Gravestone impl: {src}"
);
assert!(
!compact.contains("Default>::default()"),
"nullable mirror should not require Default: {src}"
);
}
/// A `repr_c_struct` with only scalar/enum fields owns nothing — a by-value consume is a
/// bitwise move with **no** write-back and **no** `Gravestone` impl.
#[test]
fn repr_c_struct_plain_data_has_no_writeback() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
pub struct Pt {
pub x: u64,
pub y: f64,
}
);
let take_fn: syn::ItemFn = syn::parse_quote!(
pub fn pt_sum(p: Pt) -> f64 {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(take_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.repr_c_struct(syn::parse_quote!(Pt))
.function(syn::parse_quote!(pt_sum))
.panic();
let src = write(cbindgen, registry, "plain_data");
let compact: String = src.split_whitespace().collect();
// The consume moves the value out with no clean-up of the moved-from slot.
assert!(compact.contains("v:*mutpt_t"), "{src}");
assert!(!compact.contains("null_mut()"), "{src}");
assert!(!compact.contains("Gravestoneforpt_t"), "{src}");
}
/// A bare `Box<T>` owned-pointer field (NOT `Option<Box<T>>`) can't be nulled (a null
/// `Box` is invalid), so the inferred-owned struct keeps the full `gravestone()`
/// write-back and gets the auto-`Gravestone` impl (requiring `Default`).
#[test]
fn repr_c_struct_bare_box_field_keeps_full_gravestone() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
#[derive(Default)]
pub struct Bar {
pub a: u64,
pub s: Box<String>,
}
);
let put_fn: syn::ItemFn = syn::parse_quote!(
pub fn bar_put(p: Bar) {
unimplemented!()
}
);
let string_fn: syn::ItemFn = syn::parse_quote!(
pub fn string_make() -> String {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(put_fn), loc.clone()),
(syn::Item::Fn(string_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.opaque_ptr(syn::parse_quote!(String))
.repr_c_struct(syn::parse_quote!(Bar))
.function(syn::parse_quote!(bar_put))
.panic()
.function(syn::parse_quote!(string_make));
let src = write(cbindgen, registry, "bare_box");
let compact: String = src.split_whitespace().collect();
// A bare `Box<String>` field falls back to the full gravestone write + impl.
assert!(
compact.contains("impl::prebindgen_c_runtime::Gravestoneforbar_t"),
"{src}"
);
assert!(
compact.contains(
"::core::ptr::write(v,<bar_tas::prebindgen_c_runtime::Gravestone>::gravestone())"
),
"{src}"
);
}
/// A `repr_c_struct` crossed by **mutable** reference: `&mut Foo` (read/write borrow,
/// or an out-param that reassigns) and `&mut MaybeUninit<Foo>` (out-param into
/// uninitialized memory) both lower to a `*mut foo_t` wire — the C memory is the Rust
/// value (layout-identical mirror) — and reinterpret it as the matching `&mut` Rust
/// reference. No gravestone (a borrow, not a by-value consume).
#[test]
fn repr_c_struct_mut_ref_and_maybe_uninit_out_param() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
#[derive(Default)]
pub struct Foo {
pub a: u64,
pub s: Option<Box<String>>,
}
);
// `&mut Foo` read/write borrow.
let upd_fn: syn::ItemFn = syn::parse_quote!(
pub fn foo_update(p: &mut Foo) {
unimplemented!()
}
);
// `&mut MaybeUninit<Foo>` out-param into uninitialized memory.
let into_fn: syn::ItemFn = syn::parse_quote!(
pub fn foo_into_uninit(p: &mut ::core::mem::MaybeUninit<Foo>) {
unimplemented!()
}
);
let string_fn: syn::ItemFn = syn::parse_quote!(
pub fn string_make() -> String {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(upd_fn), loc.clone()),
(syn::Item::Fn(into_fn), loc.clone()),
(syn::Item::Fn(string_fn), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.opaque_ptr(syn::parse_quote!(String))
.repr_c_struct(syn::parse_quote!(Foo))
.function(syn::parse_quote!(foo_update))
.panic()
.function(syn::parse_quote!(foo_into_uninit))
.panic()
.function(syn::parse_quote!(string_make));
let src = write(cbindgen, registry, "repr_c_struct_mut");
let compact: String = src.split_whitespace().collect();
// Both lower to a `*mut foo_t` wire.
assert!(compact.contains("v:*mutfoo_t"), "{src}");
// `&mut Foo` reinterprets the pointer as a mutable Rust reference.
assert!(compact.contains("&mut*(vas*mutzenoh_flat::Foo)"), "{src}");
// `&mut MaybeUninit<Foo>` reinterprets it as a mutable `MaybeUninit` reference
// (write without dropping the uninitialized slot).
assert!(
compact.contains("&mut*(vas*mut::core::mem::MaybeUninit<zenoh_flat::Foo>)"),
"{src}"
);
}
/// The one position with no per-value hook (#170 instance 3, #158 instance 3):
/// a `repr_c_struct` mirror is reinterpreted **wholesale** by one `Transmute`,
/// so a field whose Rust type accepts only some bit patterns is undefined
/// behaviour the moment C writes another one and hands the struct back. Every
/// other position — a parameter, a `data_struct` field, a union payload — goes
/// through a per-value converter and has somewhere to normalise or validate.
///
/// Rejected at declaration time, with the offending field named.
#[test]
fn repr_c_struct_restricted_validity_field_is_rejected() {
for (field, want) in [
(quote::quote!(pub flag: bool), "`flag`: `bool`"),
(
quote::quote!(pub op: Operation),
"`op`: a declared `enum_type`",
),
] {
let msg = catch_msg(|| {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
pub struct Rec {
pub id: u64,
#field,
}
);
let take: syn::ItemFn = syn::parse_quote!(
pub fn rec_take(r: Rec) -> u64 {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(
syn::Item::Enum(syn::parse_quote!(
pub enum Operation {
Add = 0,
Sub = 1,
}
)),
loc.clone(),
),
(syn::Item::Fn(take), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.enum_type(syn::parse_quote!(Operation))
.repr_c_struct(syn::parse_quote!(Rec))
.function(syn::parse_quote!(rec_take))
.panic();
let _ = write(cbindgen, registry, "repr_c_struct_restricted");
});
assert!(msg.contains(want), "{msg}");
assert!(msg.contains("assume_c_field_validity"), "{msg}");
}
}
/// The escape hatch: `.assume_c_field_validity()` says this binding's C side is
/// trusted to write only in-domain bytes. It is an acknowledgement, not a fix —
/// the mirror still holds the field verbatim — and it exists so the audit can
/// reject silently-unsound **new** declarations without removing bindings that
/// already ship.
#[test]
fn repr_c_struct_restricted_validity_field_accepted_when_acknowledged() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
pub struct Rec {
pub id: u64,
pub flag: bool,
}
);
let take: syn::ItemFn = syn::parse_quote!(
pub fn rec_take(r: Rec) -> u64 {
unimplemented!()
}
);
let registry = crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st), loc.clone()),
(syn::Item::Fn(take), loc.clone()),
]))
.expect("index items");
let cbindgen = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.repr_c_struct(syn::parse_quote!(Rec))
.assume_c_field_validity()
.function(syn::parse_quote!(rec_take))
.panic();
let src = write(cbindgen, registry, "repr_c_struct_acknowledged");
let compact: String = src.split_whitespace().collect();
assert!(compact.contains("pubflag:bool"), "{src}");
}
/// The audit does **not** narrow itself to mirrors C hands back, even though
/// only those are reachable: a declared type resolves both directions whether
/// or not either is called, so "does it cross in" has no truthful answer at
/// this point in the pipeline (the reachability accounting #194/#196 replace).
/// Over-reporting is the safe direction — this pins that an output-only mirror
/// is rejected too, and takes the acknowledgement like any other.
#[test]
fn repr_c_struct_restricted_validity_field_audited_even_when_output_only() {
let loc = SourceLocation::default();
let st: syn::ItemStruct = syn::parse_quote!(
#[repr(C)]
pub struct Rec {
pub id: u64,
pub flag: bool,
}
);
let make: syn::ItemFn = syn::parse_quote!(
pub fn rec_make() -> Rec {
unimplemented!()
}
);
let registry = || {
crate::test_util::reg_from_items(declare_referenced([
(syn::Item::Struct(st.clone()), loc.clone()),
(syn::Item::Fn(make.clone()), loc.clone()),
]))
.expect("index items")
};
let declare = |acknowledged: bool| {
let mut c = CbindgenBuilder::new()
.source_module(syn::parse_quote!(zenoh_flat))
.mangle_type_name(|base| format!("{base}_t"))
.mangle_destructor(|base| format!("{base}_drop"))
.mangle_function(|n| n.to_string())
.repr_c_struct(syn::parse_quote!(Rec));
if acknowledged {
c = c.assume_c_field_validity();
}
c.function(syn::parse_quote!(rec_make))
};
let msg = catch_msg(|| {
let _ = write(declare(false), registry(), "repr_c_struct_out_only");
});
assert!(msg.contains("`flag`: `bool`"), "{msg}");
let src = write(declare(true), registry(), "repr_c_struct_out_only_ack");
let compact: String = src.split_whitespace().collect();
assert!(compact.contains("pubflag:bool"), "{src}");
}