use std::fs;
use std::path::{Path, PathBuf};
const CRATES_REQUIRING_FORBID: &[&str] = &[
"security_core",
"secure_errors",
"security_events",
"secure_boundary",
"secure_authz",
"secure_data",
"secure_output",
"secure_identity",
"secure_device_trust",
"secure_reference_service",
"secure_smoke_service",
"secure_network",
"secure_resilience",
"secure_privacy",
];
fn workspace_root() -> PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
Path::new(manifest_dir)
.parent()
.and_then(Path::parent)
.expect("CARGO_MANIFEST_DIR must be two levels below workspace root")
.to_path_buf()
}
fn crate_root_file(workspace: &Path, krate: &str) -> Option<PathBuf> {
let lib = workspace.join("crates").join(krate).join("src/lib.rs");
if lib.exists() {
return Some(lib);
}
let main = workspace.join("crates").join(krate).join("src/main.rs");
if main.exists() {
return Some(main);
}
None
}
#[test]
fn every_workspace_crate_forbids_unsafe_code() {
let workspace = workspace_root();
let mut failures: Vec<String> = Vec::new();
for krate in CRATES_REQUIRING_FORBID {
let Some(file) = crate_root_file(&workspace, krate) else {
failures.push(format!(
"crate `{krate}`: no lib.rs or main.rs found under crates/{krate}/src/",
));
continue;
};
let contents = match fs::read_to_string(&file) {
Ok(s) => s,
Err(err) => {
failures.push(format!(
"crate `{krate}`: cannot read {}: {err}",
file.display()
));
continue;
}
};
let head: String = contents.lines().take(20).collect::<Vec<_>>().join("\n");
if !head.contains("#![forbid(unsafe_code)]") {
failures.push(format!(
"crate `{krate}`: expected `#![forbid(unsafe_code)]` in the first 20 lines of {}; not found.\n\
Add the attribute at the top of the file (before any item declaration).\n\
If the crate genuinely needs unsafe, document the exception in \
docs/dev-guide/unsafe-budget.md and update CRATES_REQUIRING_FORBID.",
file.display()
));
}
}
assert!(
failures.is_empty(),
"forbid(unsafe_code) regression test failed for {} crate(s):\n - {}",
failures.len(),
failures.join("\n - ")
);
}
#[test]
fn no_unsafe_keyword_in_workspace_sources() {
let workspace = workspace_root();
let mut found: Vec<String> = Vec::new();
for krate in CRATES_REQUIRING_FORBID {
let src_dir = workspace.join("crates").join(krate).join("src");
if !src_dir.exists() {
continue;
}
scan_for_unsafe(&src_dir, krate, &mut found);
}
assert!(
found.is_empty(),
"found `unsafe` keyword in workspace sources (must be empty under forbid(unsafe_code)):\n - {}",
found.join("\n - ")
);
}
fn scan_for_unsafe(dir: &Path, krate: &str, found: &mut Vec<String>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
scan_for_unsafe(&path, krate, found);
continue;
}
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let Ok(contents) = fs::read_to_string(&path) else {
continue;
};
for (lineno, line) in contents.lines().enumerate() {
let trimmed = line.trim_start();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with("*") {
continue;
}
if has_unsafe_keyword(line) {
found.push(format!(
"crate `{krate}`: {}:{} — {}",
path.display(),
lineno + 1,
line.trim()
));
}
}
}
}
fn has_unsafe_keyword(line: &str) -> bool {
const FORMS: &[&str] = &["unsafe {", "unsafe fn", "unsafe impl", "unsafe trait"];
FORMS.iter().any(|f| line.contains(f))
}