use super::super::*;
use super::helpers::*;
pub(super) fn unsafe_labels(
name: &str,
files: &[(&str, &str)],
allowed: &[&str],
) -> Result<Vec<String>, String> {
let tree = TempSrcTree::new(&format!("unsafe-{name}"));
tree.write_all(files);
let allowed: Vec<String> = allowed.iter().map(|a| a.to_string()).collect();
unsafe_findings(tree.src(), &tree.root(), &allowed, "x").map(|fs| {
fs.into_iter()
.map(|(finding, _, _)| finding.to_string())
.collect()
})
}
pub(super) fn unsafe_keys(name: &str, source: &str) -> Result<Vec<StructuredFactIdentity>, String> {
let tree = TempSrcTree::new(&format!("unsafe-keys-{name}"));
tree.write_all(&[("lib.rs", "pub mod net;\n"), ("net.rs", source)]);
unsafe_findings(tree.src(), &tree.root(), &["crate::ffi".to_string()], "x").map(|findings| {
findings
.into_iter()
.map(|(fact, _, _)| fact.into_finding("app", "src/lib.rs").key().clone())
.collect()
})
}
#[test]
pub(super) fn cfg_attr_wrapped_path_on_an_inline_module_is_still_observed() {
let out = unsafe_labels(
"cfg-attr-inline",
&[(
"lib.rs",
"#[cfg_attr(windows, path = \"x.rs\")]\npub mod inner {\n pub fn f() { unsafe {} }\n}\n",
)],
&["crate::nowhere"],
)
.unwrap();
assert_eq!(out, ["unsafe block in crate::inner"]);
}
#[test]
pub(super) fn cfg_attr_wrapped_path_target_is_read_when_the_conventional_file_is_absent() {
let out = unsafe_labels(
"cfg-attr-target-only",
&[
(
"lib.rs",
"#[cfg_attr(windows, path = \"win.rs\")]\npub mod imp;\n",
),
("win.rs", "pub fn f() { unsafe {} }\n"),
],
&["crate::nowhere"],
)
.unwrap();
assert_eq!(out, ["unsafe block in crate::imp"]);
}
#[test]
pub(super) fn cfg_attr_wrapped_path_with_neither_candidate_present_fails_loud() {
let err = unsafe_labels(
"cfg-attr-both-absent",
&[(
"lib.rs",
"#[cfg_attr(windows, path = \"win.rs\")]\npub mod imp;\n",
)],
&["crate::nowhere"],
)
.unwrap_err();
assert!(
err.contains("could not be located"),
"neither candidate existing must fail loud, not silently pass: {err}"
);
}
#[test]
pub(super) fn cfg_attr_wrapped_path_conventional_file_is_read_when_the_predicate_is_always_false() {
let out = unsafe_labels(
"cfg-attr-file",
&[
(
"lib.rs",
"#[cfg_attr(any(), path = \"never.rs\")]\npub mod imp;\n",
),
("imp.rs", "pub fn f() { unsafe {} }\n"),
],
&["crate::nowhere"],
)
.unwrap();
assert_eq!(out, ["unsafe block in crate::imp"]);
}
#[test]
pub(super) fn unsafe_identity_survives_reorder_and_unrelated_insertion() {
let before = unsafe_keys(
"reorder-before",
"pub struct Api;\nunsafe impl Send for Api {}\n",
)
.unwrap();
let after = unsafe_keys(
"reorder-after",
"pub const UNRELATED: usize = 1;\npub struct Api;\nunsafe impl Send for Api {}\n",
)
.unwrap();
assert_eq!(before, after);
}
#[test]
pub(super) fn unrenderable_unsafe_owner_fails_loud_without_an_ordinal_identity() {
let error = unsafe_keys(
"unrenderable-owner",
"pub struct Arr<const N: usize>;\npub const N: usize = 1;\nunsafe impl Send for Arr<{ N + 1 }> {}\n",
)
.unwrap_err();
assert!(error.contains("without a positional fallback"), "{error}");
assert!(!error.contains("_#"), "{error}");
}
#[test]
pub(super) fn unsafe_production_violation_separates_target_rule_and_fact_roles() {
let (metadata, _fixture) = fixture_metadata(
"unsafe-identity",
&[
("lib.rs", "pub mod net;\npub mod ffi;\n"),
("net.rs", "pub unsafe fn decode() {}\n"),
("ffi.rs", ""),
],
);
let boundary = UnsafeBoundary::in_crate("x")
.only_under(["crate::raw", "crate::ffi"])
.because("unsafe stays behind the audited adapter");
let mut violations = Vec::new();
check_unsafe_boundary(&metadata, &boundary, &mut violations).unwrap();
assert_eq!(violations.len(), 1);
let id = violations[0].id();
assert_eq!(id.target(), "x");
let rule = id.rule_key();
assert_eq!(rule.rule_type(), "tianheng.rule/hunyi/unsafe-confinement");
assert_eq!(
rule.fields().collect::<Vec<_>>(),
vec![("allowed", "[\"crate::ffi\",\"crate::raw\"]")]
);
let fact = id.fact();
assert_eq!(fact.fact_type(), "tianheng.fact/hunyi/unsafe-site");
assert_eq!(fact.shape(), "unsafe-free-function");
assert_eq!(
fact.fields().collect::<Vec<_>>(),
vec![
("module", "crate::net"),
("name", "decode"),
("unit", "lib.rs"),
]
);
}
#[test]
pub(super) fn unsafe_block_outside_subtree_reacts() {
let out = unsafe_labels(
"block",
&[
("lib.rs", "pub mod ffi;\npub mod net;\n"),
(
"ffi.rs",
"pub fn ok() { unsafe { core::ptr::null::<u8>(); } }\n",
),
(
"net.rs",
"pub fn f() { unsafe { core::ptr::null::<u8>(); } }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe block in crate::net"],
"a block outside the subtree reacts; one under it is clean: {out:?}"
);
}
#[test]
pub(super) fn unsafe_fn_impl_trait_extern_outside_react() {
let out = unsafe_labels(
"kinds",
&[
("lib.rs", "pub mod ffi;\npub mod net;\n"),
("ffi.rs", "\n"),
(
"net.rs",
"pub unsafe trait Zeroable {}\npub unsafe fn decode() {}\nunsafe impl Zeroable for u8 {}\nunsafe extern \"C\" { fn c(); }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
[
"unsafe extern block in crate::net",
"unsafe fn decode in crate::net",
"unsafe impl Zeroable for u8 in crate::net",
"unsafe trait Zeroable in crate::net",
],
"every unsafe-keyword site outside the subtree reacts: {out:?}"
);
}
#[test]
pub(super) fn unsafe_under_the_subtree_is_clean() {
let out = unsafe_labels(
"clean",
&[
("lib.rs", "pub mod ffi;\n"),
("ffi.rs", "pub mod raw;\npub unsafe fn a() {}\n"),
(
"ffi/raw.rs",
"pub fn b() { unsafe { core::ptr::null::<u8>(); } }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert!(
out.is_empty(),
"unsafe at the subtree and beneath it is clean: {out:?}"
);
}
#[test]
pub(super) fn empty_allowed_set_is_a_constitution_error() {
let err = unsafe_labels("empty", &[("lib.rs", "pub fn f() { unsafe {} }\n")], &[]).unwrap_err();
assert!(
err.contains("forbid(unsafe_code)"),
"empty only_under points at #![forbid(unsafe_code)]: {err}"
);
}
#[test]
pub(super) fn crate_root_allowed_set_is_a_constitution_error() {
let err = unsafe_labels("root", &[("lib.rs", "pub fn f() {}\n")], &["crate"]).unwrap_err();
assert!(err.contains("crate root"), "{err}");
}
#[test]
pub(super) fn unsafe_confinement_rejects_a_malformed_colon_allowed_location() {
let files: &[(&str, &str)] = &[
("lib.rs", "pub mod ffi;\n"),
("ffi.rs", "pub unsafe fn a() {}\n"),
];
for bad in ["::crate::ffi", "crate::ffi::", "crate::ffi::::sub"] {
let err = unsafe_labels("malformed-allowed", files, &[bad]).unwrap_err();
assert!(
err.contains(bad),
"constitution error must name the malformed allowed entry {bad:?}: {err}"
);
}
let empty_err = unsafe_labels("malformed-allowed-empty", files, &[""]).unwrap_err();
assert!(
empty_err.contains("is empty"),
"constitution error must flag the empty allowed entry: {empty_err}"
);
let clean = unsafe_labels("malformed-allowed-control", files, &["crate::ffi"]).unwrap();
assert!(
clean.is_empty(),
"a well-formed allowed entry must still confine the genuinely-placed unsafe site: {clean:?}"
);
}
#[test]
pub(super) fn unsafe_blocks_dedup_per_module() {
let out = unsafe_labels(
"dedup",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub fn f() { unsafe {} unsafe {} }\npub fn g() { unsafe {} }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe block in crate::net"],
"N blocks in one module dedup to one stable finding: {out:?}"
);
}
#[test]
pub(super) fn two_unsafe_impls_of_different_traits_stay_distinct() {
let out = unsafe_labels(
"impls",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub struct Foo;\nunsafe impl Send for Foo {}\nunsafe impl Sync for Foo {}\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
[
"unsafe impl Send for Foo in crate::net",
"unsafe impl Sync for Foo in crate::net",
],
"the trait is in the finding, so two unsafe impls do not collapse: {out:?}"
);
}
#[test]
pub(super) fn two_unsafe_impls_of_one_trait_for_different_types_stay_distinct() {
let out = unsafe_labels(
"impls-same-trait",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub struct Foo;\npub struct Bar;\nunsafe impl Send for Foo {}\nunsafe impl Send for Bar {}\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
[
"unsafe impl Send for Bar in crate::net",
"unsafe impl Send for Foo in crate::net",
],
"the self type is in the finding, so same-trait impls for different types do not collapse: {out:?}"
);
}
#[test]
pub(super) fn two_same_named_unsafe_fns_on_different_owners_stay_distinct() {
let out = unsafe_labels(
"unsafe-fns-same-name",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub struct Foo;\npub struct Bar;\nimpl Foo { unsafe fn m(&self) {} }\nimpl Bar { unsafe fn m(&self) {} }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
[
"unsafe fn Bar::m in crate::net",
"unsafe fn Foo::m in crate::net",
],
"same-named unsafe fns on different owners must not collapse: {out:?}"
);
}
#[test]
pub(super) fn two_same_named_unsafe_trait_fns_stay_distinct() {
let out = unsafe_labels(
"unsafe-trait-fns",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub trait A { unsafe fn m(&self); }\npub trait B { unsafe fn m(&self); }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
[
"unsafe fn A::m in crate::net",
"unsafe fn B::m in crate::net"
],
"trait-declared unsafe fns must be qualified by their trait: {out:?}"
);
}
#[test]
pub(super) fn trait_impl_unsafe_fn_stays_distinct_from_inherent_and_other_traits() {
let out = unsafe_labels(
"unsafe-fns-trait-impl",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub struct Foo;\npub trait A { fn m(&self); }\npub trait B { fn m(&self); }\n\
impl Foo { unsafe fn m(&self) {} }\n\
impl A for Foo { unsafe fn m(&self) {} }\n\
impl B for Foo { unsafe fn m(&self) {} }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
[
"unsafe fn <A for Foo>::m in crate::net",
"unsafe fn <B for Foo>::m in crate::net",
"unsafe fn Foo::m in crate::net",
],
"a trait-impl unsafe fn must be qualified by <trait for self>, distinct from the inherent \
method and other trait impls on the same type: {out:?}"
);
}
#[test]
pub(super) fn unsafe_in_an_unconditional_path_remapped_module_reacts() {
let out = unsafe_labels(
"path-remap-unsafe",
&[
("lib.rs", "#[path = \"relocated.rs\"]\npub mod net;\n"),
("relocated.rs", "pub unsafe fn poke() {}\n"),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe fn poke in crate::net"],
"unsafe in an unconditional #[path] module is followed and reacts: {out:?}"
);
}
#[test]
pub(super) fn path_in_a_non_mod_rs_file_resolves_from_the_containing_files_own_dir() {
let out = unsafe_labels(
"path-nonmodrs",
&[
("lib.rs", "pub mod foo;\n"),
("foo.rs", "#[path = \"bar.rs\"]\npub mod bar;\n"),
("bar.rs", "pub unsafe fn poke() {}\n"),
("foo/bar.rs", "pub fn decoy() {}\n"),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe fn poke in crate::foo::bar"],
"a #[path] inside a non-mod.rs file resolves from that file's own dir (src/bar.rs), not \
src/foo/bar.rs: {out:?}"
);
}
#[test]
pub(super) fn path_nested_in_an_inline_block_resolves_from_the_accumulated_dir() {
let out = unsafe_labels(
"path-inline-modrs",
&[
(
"lib.rs",
"pub mod inline { #[path = \"other.rs\"] pub mod inner; }\n",
),
("inline/other.rs", "pub unsafe fn poke() {}\n"),
("other.rs", "pub fn decoy() {}\n"),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe fn poke in crate::inline::inner"],
"a #[path] nested in an inline block resolves from <file_dir>/inline (src/inline/other.rs), \
not the src/other.rs orphan: {out:?}"
);
}
#[test]
pub(super) fn path_nested_in_an_inline_block_in_a_non_mod_rs_file_accumulates_both_components() {
let out = unsafe_labels(
"path-inline-nonmodrs",
&[
("lib.rs", "pub mod bar;\n"),
(
"bar.rs",
"pub mod inline { #[path = \"p.rs\"] pub mod inner; }\n",
),
("bar/inline/p.rs", "pub unsafe fn poke() {}\n"),
("p.rs", "pub fn decoy() {}\n"),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe fn poke in crate::bar::inline::inner"],
"the #[path] base accumulates bar/ and inline/ (src/bar/inline/p.rs), not src/p.rs: {out:?}"
);
}
#[test]
pub(super) fn two_modules_sharing_one_path_target_are_not_a_false_cycle() {
let out = unsafe_labels(
"path-shared-target",
&[
(
"lib.rs",
"#[path = \"shared.rs\"]\npub mod a;\n#[path = \"shared.rs\"]\npub mod b;\n",
),
("shared.rs", "pub unsafe fn poke() {}\n"),
],
&["crate::ffi"],
)
.expect("two modules sharing one #[path] target is not a cycle (rustc compiles it)");
assert_eq!(
out,
["unsafe fn poke in crate::a", "unsafe fn poke in crate::b",],
"a file shared by two #[path] declarations reacts under both module paths, no false cycle: \
{out:?}"
);
}
#[test]
pub(super) fn a_conventional_module_and_a_path_alias_to_it_are_not_a_false_cycle() {
let out = unsafe_labels(
"path-alias-conventional",
&[
(
"lib.rs",
"pub mod foo;\n#[path = \"foo.rs\"]\npub mod bar;\n",
),
("foo.rs", "pub unsafe fn poke() {}\n"),
],
&["crate::ffi"],
)
.expect("a conventional module and a #[path] alias to the same file is not a cycle");
assert_eq!(
out,
[
"unsafe fn poke in crate::bar",
"unsafe fn poke in crate::foo",
],
"one file reached conventionally and via a #[path] alias reacts under both paths: {out:?}"
);
}
#[test]
pub(super) fn unsafe_in_a_body_nested_mod_reacts() {
let out = unsafe_labels(
"body-nested",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"pub fn f() { mod raw { pub unsafe fn poke() {} } }\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert_eq!(
out,
["unsafe fn poke in crate::net"],
"unsafe in a body-nested mod is attributed to the enclosing module, never dropped: {out:?}"
);
}
#[test]
pub(super) fn unsafe_in_a_macro_body_is_a_stated_bound() {
let out = unsafe_labels(
"macro",
&[
("lib.rs", "pub mod net;\n"),
(
"net.rs",
"macro_rules! m { () => { unsafe {} }; }\npub fn f() {}\n",
),
],
&["crate::ffi"],
)
.unwrap();
assert!(
out.is_empty(),
"unsafe in a macro body is not observed: {out:?}"
);
}