use std::process::Command;
const MARKER: &str = "RUSTY_ALLOC_FOREIGN_FREE_CHILD";
fn child_foreign_free() -> ! {
static NOT_OURS: [u64; 64] = [0; 64];
let foreign = (&raw const NOT_OURS).cast::<u8>().cast_mut();
let real = rusty_alloc::alloc::malloc(128);
assert!(!real.is_null(), "child: malloc failed");
unsafe { rusty_alloc::alloc::free(foreign) };
eprintln!("child: the foreign-pointer guard did not fire");
std::process::exit(0);
}
#[test]
#[cfg_attr(miri, ignore)] fn foreign_pointer_is_rejected_in_debug_builds() {
if std::env::var(MARKER).is_ok() {
child_foreign_free();
}
if !cfg!(any(debug_assertions, feature = "debug_checks")) {
eprintln!("skipped: guard is debug/debug_checks-only, and this is a release build");
return;
}
let exe = std::env::current_exe().expect("current_exe");
let out = Command::new(exe)
.env(MARKER, "1")
.arg("--test-threads=1")
.output()
.expect("spawn child");
let combined = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
!out.status.success(),
"the child exited successfully: free() ACCEPTED a foreign pointer. \
The guard is not protecting the free path.\n{combined}"
);
assert!(
combined.contains("never returned"),
"child died, but not on the foreign-pointer assertion — the test may be \
measuring an unrelated failure.\n{combined}"
);
}