use brick::Building; use brick::mcp::Catalog; use brick::scan; use serde_json::{Value, json}; use std::collections::BTreeMap; use std::fs; fn scan_files(files: &[(&str, &str)]) -> Building { let root = tempfile::tempdir().unwrap(); for (path, content) in files { let file = root.path().join(path); fs::create_dir_all(file.parent().unwrap()).unwrap(); fs::write(file, content).unwrap(); } scan::scan(root.path()).unwrap() } /// function name -> (status, reason kinds) for every function in the model. fn statuses(model: Building, closed_world: bool) -> BTreeMap)> { let catalog = Catalog::new(model).unwrap(); let result = catalog .call( "brick_uncalled", json!({"assume_closed_world": closed_world, "include_unknown": true, "limit": 1000}), ) .unwrap(); let mut found = BTreeMap::new(); for (status, list) in [("unreferenced", "unreferenced"), ("unknown", "unknown")] { for report in result[list].as_array().unwrap() { let reasons = report["reasons"] .as_array() .unwrap() .iter() .map(|r| r["kind"].as_str().unwrap().to_owned()) .collect(); found.insert( report["node"]["name"].as_str().unwrap().to_owned(), (status.to_owned(), reasons), ); } } found } fn cpp_model() -> Building { scan_files(&[ ( "src/app.h", "#include \nstruct Base { virtual void hook(); };\nstruct Widget : Base {\n void hook() override;\n int size() const;\n Widget();\n};\nint shared();\n", ), ( "src/app.cpp", "#include \"app.h\"\n#include \nWidget::Widget() {}\nvoid Base::hook() {}\nvoid Widget::hook() {}\nint Widget::size() const { return 0; }\nstatic std::mutex& lockObject() { static std::mutex m; return m; }\nstatic int unusedHelper() { return 1; }\nstatic int callback(int x) { return x; }\nint shared() { return 2; }\nWidget make();\nint main() {\n std::lock_guard guard(lockObject());\n std::string text;\n text.size();\n make().size();\n int (*fp)(int) = callback;\n return fp(1) + shared();\n}\n", ), ]) } #[test] fn cpp_statuses_separate_dead_code_from_unresolvable_uses() { let found = statuses(cpp_model(), false); // Only resolved callers are omitted: shared() and lockObject() (via the vexing parse). assert!(!found.contains_key("shared")); assert!(!found.contains_key("lockObject")); assert_eq!(found["unusedHelper"], ("unreferenced".into(), vec![])); let reasons = |name: &str| found[name].1.clone(); assert_eq!(found["size"].0, "unknown"); // make().size() may reach Widget::size; std::string::size cannot. assert!(reasons("size").contains(&"unresolved-call-sites".to_owned())); assert!(reasons("callback").contains(&"referenced-by-name".to_owned())); assert!(reasons("hook").contains(&"virtual".to_owned())); assert!(reasons("Widget").contains(&"implicit-invocation".to_owned())); assert!(reasons("main").contains(&"entry-point".to_owned())); } #[test] fn closed_world_drops_linkage_but_keeps_evidence_of_indirect_use() { let open = statuses(cpp_model(), false); let closed = statuses(cpp_model(), true); assert!(open["size"].1.contains(&"external-linkage".to_owned())); assert!(!closed["size"].1.contains(&"external-linkage".to_owned())); assert_eq!(closed["size"].0, "unknown"); assert_eq!(closed["unusedHelper"].0, "unreferenced"); } #[test] fn rust_attributes_attribute_strings_and_trait_impls_are_uses() { let model = scan_files(&[( "src/lib.rs", "use std::fmt;\nstruct Point;\nimpl fmt::Display for Point {\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Ok(()) }\n}\n#[derive(serde::Deserialize)]\nstruct Config {\n #[serde(deserialize_with = \"crate::parse_flag\")]\n flag: bool,\n}\nfn parse_flag() {}\nfn orphan() {}\n#[test]\nfn checks() {}\n#[cfg(test)]\nmod tests {\n fn helper() {}\n}\n", )]); let found = statuses(model, true); assert_eq!(found["orphan"], ("unreferenced".into(), vec![])); assert!(found["fmt"].1.contains(&"trait-impl".to_owned())); assert!( found["parse_flag"] .1 .contains(&"referenced-by-name".to_owned()) ); assert!( found["checks"] .1 .contains(&"framework-attribute".to_owned()) ); assert!(found["helper"].1.contains(&"test-code".to_owned())); } #[test] fn callers_query_reports_resolved_callers_basis_and_rejects_non_functions() { let model = cpp_model(); let shared = model .nodes .iter() .find(|n| n.name == "shared" && n.path.as_deref() == Some("src/app.cpp")) .unwrap() .id .clone(); let file = model .nodes .iter() .find(|n| { n.path.as_deref() == Some("src/app.cpp") && n.kind == brick::model::NodeKind::File }) .unwrap() .id .clone(); let catalog = Catalog::new(model).unwrap(); let result = catalog .call("brick_callers", json!({"node_id": shared})) .unwrap(); let report = &result["report"]; assert_eq!(report["status"], "resolved"); assert_eq!(report["basis"], "heuristic"); assert_eq!(report["callers"].as_array().unwrap().len(), 1); assert!( result["coverage"]["cpp"]["call_site_count"] .as_u64() .unwrap() > 0 ); assert!(result["caveat"].as_str().unwrap().contains("not proof")); assert!( catalog .call("brick_callers", json!({"node_id": file})) .is_err() ); assert!( catalog .call( "brick_callers", json!({"node_id": shared, "extra": Value::Null}) ) .is_err() ); } #[test] fn macro_bodies_and_syntax_errors_make_functions_unknown_not_dead() { let model = scan_files(&[ ( "src/plugin.cpp", "static void* open_impl(const char* name) { return nullptr; }\n#define open_library open_impl\nstatic void* unused_impl() { return nullptr; }\nvoid* load() { return open_library(\"x\"); }\n", ), ( "src/broken.cpp", "namespace {\n#if defined(A)\n}\n#endif\nstatic int lost() { return 1; }\n", ), ]); let found = statuses(model, true); assert!( found["open_impl"] .1 .contains(&"referenced-by-name".to_owned()) ); assert_eq!(found["unused_impl"], ("unreferenced".into(), vec![])); assert!( found["lost"].1.contains(&"parse-incomplete".to_owned()), "{found:?}" ); } #[test] fn template_parameters_aliases_and_protocol_methods_are_not_dead() { let model = scan_files(&[ ( "src/repair.h", "#include \nnamespace app {\nstruct Repair { void enqueue(); void drain(); void unused(); };\nusing RepairPtr = std::shared_ptr;\nstruct Awaiter { bool await_ready() const noexcept; };\ntemplate struct Box { Box(); };\ntemplate <> struct Box { Box() {} };\n}\n", ), ( "src/repair.cpp", "#include \"repair.h\"\n#include \nnamespace app {\nvoid Repair::enqueue() {}\nvoid Repair::drain() {}\nvoid Repair::unused() {}\nbool Awaiter::await_ready() const noexcept { return true; }\n}\ntemplate <> void lib::Fsm::start() {}\n", ), ( "src/use.cpp", "#include \"repair.h\"\nnamespace app {\ntemplate \nvoid poke(Ptr service) { service->enqueue(); }\nvoid run(RepairPtr repair) { repair->drain(); }\n}\n", ), ]); let found = statuses(model, true); // Alias through shared_ptr resolves; the templated receiver keeps enqueue possible. assert!(!found.contains_key("drain"), "{found:?}"); assert_eq!(found["enqueue"].0, "unknown"); assert!( found["enqueue"] .1 .contains(&"unresolved-call-sites".to_owned()) ); assert!( found["await_ready"] .1 .contains(&"implicit-invocation".to_owned()) ); assert!( found["start"] .1 .contains(&"outside-class-member".to_owned()) ); assert!(found["Box"].1.contains(&"implicit-invocation".to_owned())); // Clean negative: nothing can reach unused(). assert_eq!(found["unused"], ("unreferenced".into(), vec![])); }