use callgraph_verify::{
analyze, cfg_fingerprint, diff_body_calls, diff_body_calls_ext, diff_calls,
has_direct_branch_target, reg_params, resolve_thunk, tail_referenced, ArgKind, Pe, Spec,
};
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
struct Fixture {
label: &'static str,
exe: PathBuf,
pe: Pe,
addrs: BTreeMap<String, u64>,
toolchain: String,
}
fn fixtures_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("target")
.join("fixtures")
}
fn parse_manifest(json: &std::path::Path) -> (BTreeMap<String, u64>, String) {
let text = std::fs::read_to_string(json).expect("read addrs manifest");
let v: serde_json::Value =
serde_json::from_str(&text).expect("parse addrs manifest");
let toolchain = v
.get("toolchain")
.and_then(|t| t.as_str())
.unwrap_or("unknown")
.to_string();
let raw = v
.get("symbols")
.and_then(|s| s.as_object())
.unwrap_or_else(|| panic!("manifest {} has no symbols map", json.display()));
let mut addrs = BTreeMap::new();
for (k, val) in raw {
let s = val.as_str().unwrap_or_else(|| {
panic!("bad VA {val:?} for {k} in {}", json.display())
});
let n = if let Some(h) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
u64::from_str_radix(h, 16)
} else {
s.parse()
}
.unwrap_or_else(|_| panic!("bad VA {s:?} for {k} in {}", json.display()));
addrs.insert(k.clone(), n);
}
(addrs, toolchain)
}
fn load_fixture(
label: &'static str,
stem: &str,
exe_env: &str,
json_env: &str,
) -> Option<Fixture> {
let exe = std::env::var(exe_env)
.map(PathBuf::from)
.unwrap_or_else(|_| fixtures_dir().join(format!("{stem}.exe")));
let json = std::env::var(json_env)
.map(PathBuf::from)
.unwrap_or_else(|_| fixtures_dir().join(format!("{stem}_addrs.json")));
if !exe.exists() || !json.exists() {
if std::env::var("CALLGRAPH_REQUIRE_FIXTURES")
.map(|v| v == "1")
.unwrap_or(false)
{
panic!(
"{label} fixture missing (REQUIRE=1): exe={} (exists={}), addrs={} (exists={}). \
Run test-fixtures/build_fixtures.py first.",
exe.display(),
exe.exists(),
json.display(),
json.exists()
);
}
eprintln!(
"skipping {label} fixture tests: {exe} or {json} not present (run test-fixtures/build_fixtures.py, or set {exe_env}/{json_env})",
exe = exe.display(),
json = json.display(),
exe_env = exe_env,
json_env = json_env,
);
return None;
}
let (addrs, toolchain) = parse_manifest(&json);
let pe = Pe::load(exe.to_str().unwrap()).expect("load fixture PE");
Some(Fixture {
label,
exe,
pe,
addrs,
toolchain,
})
}
fn c_fixture() -> Option<Fixture> {
load_fixture("C", "c_callgraph", "CALLGRAPH_C_EXE", "CALLGRAPH_C_ADDRS")
}
fn cpp_fixture() -> Option<Fixture> {
load_fixture(
"C++",
"cpp_callgraph",
"CALLGRAPH_CPP_EXE",
"CALLGRAPH_CPP_ADDRS",
)
}
fn c_od_fixture() -> Option<Fixture> {
load_fixture(
"C-od",
"c_callgraph_od",
"CALLGRAPH_C_OD_EXE",
"CALLGRAPH_C_OD_ADDRS",
)
}
fn c_dbg_fixture() -> Option<Fixture> {
let exe = std::env::var("CALLGRAPH_C_DBG_EXE")
.map(PathBuf::from)
.unwrap_or_else(|_| fixtures_dir().join("c_callgraph_dbg.exe"));
let json = std::env::var("CALLGRAPH_C_DBG_ADDRS")
.map(PathBuf::from)
.unwrap_or_else(|_| fixtures_dir().join("c_callgraph_dbg_addrs.json"));
if !exe.exists() || !json.exists() {
eprintln!(
"skipping ILT-thunk tests: {exe} not present (MSVC-only artifact; build with cl to run locally)",
exe = exe.display(),
);
let _ = json;
return None;
}
let (addrs, toolchain) = parse_manifest(&json);
let pe = Pe::load(exe.to_str().unwrap()).expect("load dbg fixture PE");
Some(Fixture {
label: "C-dbg",
exe,
pe,
addrs,
toolchain,
})
}
fn addr(fx: &Fixture, name: &str) -> u64 {
*fx
.addrs
.get(name)
.unwrap_or_else(|| panic!("{} manifest has no {name:?}", fx.label))
}
fn call_edges(fx: &Fixture, entry: u64) -> BTreeSet<u64> {
let cg = analyze(&fx.pe, entry).expect("analyze fixture fn");
let mut s = cg.call_targets();
s.extend(&cg.tail_calls);
s
}
fn sweep_functions(pe: &Pe, cap: usize) -> Vec<u64> {
let mut addrs = Vec::new();
let text = pe.sections().iter().find(|s| s.name == ".text").unwrap();
let text_va = pe.image_base + text.virtual_address as u64;
let end = text_va + text.virtual_size as u64;
let mut va = text_va;
while va < end {
if let Ok(cg) = analyze(pe, va) {
addrs.push(va);
va = va.saturating_add(cg.max_end.saturating_sub(va).max(1));
} else {
va += 1;
}
if addrs.len() >= cap {
break;
}
}
addrs
}
#[test]
fn c_fixture_is_pe32_with_text() {
let Some(fx) = c_fixture() else {
return;
};
assert!(
fx.pe.sections().iter().any(|s| s.name == ".text"),
"{} has no .text",
fx.exe.display()
);
for name in ["branch_caller", "leaf_add"] {
let va = addr(&fx, name);
assert!(
fx.pe.section_of(va).is_some(),
"{name} VA {va:#x} is outside every section"
);
}
}
#[test]
fn cpp_fixture_is_pe32_with_text() {
let Some(fx) = cpp_fixture() else {
return;
};
assert!(
fx.pe.sections().iter().any(|s| s.name == ".text"),
"{} has no .text",
fx.exe.display()
);
let va = addr(&fx, "cpp_caller_fn");
assert!(
fx.pe.section_of(va).is_some(),
"cpp_caller_fn VA {va:#x} is outside every section"
);
}
#[test]
fn c_branch_caller_calls_both_leaves() { let Some(fx) = c_fixture() else {
return;
};
let edges = call_edges(&fx, addr(&fx, "branch_caller"));
for leaf in [addr(&fx, "leaf_add"), addr(&fx, "leaf_const")] {
assert!(
edges.contains(&leaf),
"branch_caller edges {edges:#x?} miss {leaf:#x} — the optimizer \
removed a call the tests pin (check NOINLINE / main reachability)"
);
}
}
#[test]
fn c_parent_calls_branch() {
let Some(fx) = c_fixture() else {
return;
};
let edges = call_edges(&fx, addr(&fx, "parent_fn"));
assert!(
edges.contains(&addr(&fx, "branch_caller")),
"parent_fn edges {edges:#x?} miss branch_caller"
);
}
#[test]
fn c_leaf_const_is_a_leaf() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "leaf_const")).expect("analyze leaf_const");
assert!(
cg.direct_calls.is_empty() && cg.indirect_calls == 0,
"leaf_const should be a leaf: {cg:?}"
);
}
#[test]
fn c_use_apply_reaches_apply_twice_indirectly() {
let Some(fx) = c_fixture() else {
return;
};
let edges = call_edges(&fx, addr(&fx, "use_apply"));
assert!(
edges.contains(&addr(&fx, "apply_twice")),
"use_apply edges {edges:#x?} miss apply_twice"
);
let cg = analyze(&fx.pe, addr(&fx, "apply_twice")).expect("analyze apply_twice");
assert!(
cg.indirect_calls >= 1,
"apply_twice calls through a function pointer — indirect_calls must be >= 1: {cg:?}"
);
}
#[test]
fn c_indirect_target_accepted_on_real_output() {
for fx in [c_fixture(), c_od_fixture()].into_iter().flatten() {
let caller = addr(&fx, "apply_twice");
let target = addr(&fx, "inc_fn");
let cg = analyze(&fx.pe, caller).expect("analyze apply_twice");
assert!(
cg.indirect_calls >= 1,
"[{}] premise: apply_twice must contain an indirect call: {cg:?}",
fx.label
);
let corpus: BTreeSet<u64> = [caller, target].into_iter().collect();
let idents = vec![("inc_fn".to_string(), vec![target])];
let none = BTreeSet::new();
let d = diff_body_calls(
&fx.pe,
&[caller],
&corpus,
&idents,
&none,
&none,
)
.expect("diff_body_calls");
assert!(
d.hallucinated
.iter()
.any(|(n, addrs)| n == "inc_fn" && addrs.contains(&target)),
"[{}] baseline must flag the direct callback call: {:?}",
fx.label, d.hallucinated
);
let declared: BTreeSet<u64> = [target].into_iter().collect();
let d = diff_body_calls_ext(
&fx.pe,
&[caller],
&corpus,
&idents,
&none,
&none,
&declared,
)
.expect("diff_body_calls_ext");
assert!(
d.hallucinated.is_empty(),
"[{}] declared indirect target must be accepted: {:?}",
fx.label,
d.hallucinated
);
assert!(
!d.missing.contains(&target),
"[{}] an indirect target is acceptance-only, never MISSING: {:?}",
fx.label,
d.missing
);
Spec::new(caller)
.indirect_targets(&[target])
.check(&fx.pe)
.unwrap_or_else(|e| panic!("[{}] Spec indirect target: {e}", fx.label));
}
}
#[test]
fn c_spec_superset_ok_and_hallucinated_fails() {
let Some(fx) = c_fixture() else {
return;
};
let entry = addr(&fx, "branch_caller");
let real = [addr(&fx, "leaf_add"), addr(&fx, "leaf_const")];
Spec::new(entry)
.calls_superset(&real)
.check(&fx.pe)
.expect("superset of the real call set must pass");
let err = Spec::new(entry)
.calls_superset(&[0xDEADBEEF])
.check(&fx.pe)
.expect_err("invented call must fail");
assert!(err.contains("HALLUCINATED"), "error: {err}");
}
#[test]
fn c_spec_exact_missing_detected() {
let Some(fx) = c_fixture() else {
return;
};
let err = Spec::new(addr(&fx, "branch_caller"))
.calls_exact(&[addr(&fx, "leaf_add")])
.check(&fx.pe)
.expect_err("omitted real call must fail");
assert!(err.contains("MISSING"), "error: {err}");
assert!(
err.contains(&format!("{:#010x}", addr(&fx, "leaf_const"))),
"missing report should name leaf_const: {err}"
);
}
#[test]
fn c_diff_body_calls_hallucinated_and_missing() {
let Some(fx) = c_fixture() else {
return;
};
let caller = addr(&fx, "parent_fn");
let callee = addr(&fx, "branch_caller");
let fake = 0xDEADBEEFu64;
let corpus: BTreeSet<u64> = [callee, fake].into_iter().collect();
let d = diff_body_calls(
&fx.pe,
&[caller],
&corpus,
&[("invented_fn".to_string(), vec![fake])],
&BTreeSet::new(),
&BTreeSet::new(),
)
.expect("diff_body_calls");
assert!(
d.hallucinated.iter().any(|(n, _)| n == "invented_fn"),
"hallucinated: {:?}",
d.hallucinated
);
let d = diff_body_calls(
&fx.pe,
&[caller],
&corpus,
&[],
&BTreeSet::new(),
&BTreeSet::new(),
)
.expect("diff_body_calls");
assert!(
d.missing.contains(&callee),
"missing: {:?} (want {callee:#x})",
d.missing
);
}
#[test]
fn c_global_referenced() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "read_global")).expect("analyze read_global");
assert!(
cg.globals.contains(&addr(&fx, "g_counter")),
"read_global must reference g_counter {:#x}: globals={:?}",
addr(&fx, "g_counter"),
cg.globals
);
}
#[test]
fn c_tail_caller_global_covered() {
for fx in [c_fixture(), c_od_fixture()].into_iter().flatten() {
let (Some(&caller), Some(&target), Some(&g)) = (
fx.addrs.get("tail_caller"),
fx.addrs.get("tail_target"),
fx.addrs.get("g_tail_tag"),
) else {
eprintln!(
"[{}] skipping: manifest lacks tail_caller/tail_target/g_tail_tag (stale fixtures?)",
fx.label
);
continue;
};
let cg = analyze(&fx.pe, caller).expect("analyze tail_caller");
let is_tail = cg.tail_calls.contains(&target);
let is_call = cg.direct_calls.contains_key(&target);
assert!(
is_tail || is_call,
"[{}] tail_caller must transfer control to tail_target (JMP or CALL): calls={:?} tails={:?}",
fx.label, cg.direct_calls, cg.tail_calls
);
if is_tail {
assert!(
!cg.globals.contains(&g),
"[{}] tail path: caller must not reference the global directly: {:?}",
fx.label, cg.globals
);
assert!(
tail_referenced(&fx.pe, &cg).contains(&g),
"[{}] tail path: fold must cover g_tail_tag {:#x}",
fx.label, g
);
}
let cg_t = analyze(&fx.pe, target).expect("analyze tail_target");
assert!(
cg_t.globals.contains(&g),
"[{}] tail_target must reference g_tail_tag {:#x}: {:?}",
fx.label, g, cg_t.globals
);
}
}
#[test]
fn c_field_offsets() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "field_read")).expect("analyze field_read");
assert!(
cg.field_offsets.contains(&4),
"field_read (p->y) must touch offset 4: {:?}",
cg.field_offsets
);
let cg = analyze(&fx.pe, addr(&fx, "field_write")).expect("analyze field_write");
for off in [4u64, 8] {
assert!(
cg.field_offsets.contains(&off),
"field_write (p->y, p->z) must touch offset {off}: {:?}",
cg.field_offsets
);
}
}
#[test]
fn c_recurse_sum_calls_itself() {
for fx in [c_fixture(), c_od_fixture()].into_iter().flatten() {
let me = addr(&fx, "recurse_sum");
let cg = analyze(&fx.pe, me).expect("analyze recurse_sum");
if fx.toolchain == "msvc" {
assert!(
cg.call_targets().contains(&me),
"[{}] recurse_sum must directly call itself: {:?}",
fx.label,
cg.direct_calls
);
continue;
}
let mut edges = cg.call_targets();
edges.extend(&cg.tail_calls);
if edges.contains(&me) {
continue;
}
let fp = cfg_fingerprint(&fx.pe, me).expect("cfg recurse_sum");
assert!(
fp.loops >= 1,
"[{}] recurse_sum must self-call or lower to a loop: {:?} {fp:?}",
fx.label,
cg
);
}
}
#[test]
fn c_nested_sum_has_signed_branches() {
for fx in [c_fixture(), c_od_fixture()].into_iter().flatten() {
let cg = analyze(&fx.pe, addr(&fx, "nested_sum")).expect("analyze");
eprintln!(
"[{}] nested_sum polarity: signed={} unsigned={} equality={}",
fx.label, cg.signed_branches, cg.unsigned_branches, cg.equality_branches
);
assert!(
cg.signed_branches >= 2,
"[{}] nested_sum's loop bounds must survive as signed JCCs",
fx.label
);
}
}
#[test]
fn c_loop_has_back_edge() {
let Some(fx) = c_fixture() else {
return;
};
let fp = cfg_fingerprint(&fx.pe, addr(&fx, "loop_sum")).expect("cfg loop_sum");
assert!(
fp.loops >= 1,
"loop_sum's for-loop must leave a back-edge: {fp:?}"
);
}
#[test]
fn c_nested_loops_have_two_back_edges() {
let Some(fx) = c_fixture() else {
return;
};
let fp = cfg_fingerprint(&fx.pe, addr(&fx, "nested_sum")).expect("cfg nested_sum");
assert!(
fp.loops >= 2,
"nested_sum's two for-loops must leave two back-edges: {fp:?}"
);
}
#[test]
fn c_switch_compiles_to_control_flow() {
let Some(fx) = c_fixture() else {
return;
};
let fp = cfg_fingerprint(&fx.pe, addr(&fx, "classify")).expect("cfg classify");
eprintln!("classify fingerprint: {fp:?}");
assert!(
fp.switch_cases >= 3 || fp.conditionals >= 5,
"classify's 8-case switch must survive as control flow: {fp:?}"
);
}
#[test]
fn c_decision_chain_has_twelve_conditionals() {
let Some(fx) = c_fixture() else {
return;
};
let fp = cfg_fingerprint(&fx.pe, addr(&fx, "decision_chain")).expect("cfg");
assert!(
fp.conditionals >= 12,
"decision_chain's 12 ifs must yield 12 conditionals: {fp:?}"
);
}
#[test]
fn c_branch_caller_uses_signed_branch() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "branch_caller")).expect("analyze");
eprintln!(
"branch_caller polarity: signed={} unsigned={} equality={}",
cg.signed_branches, cg.unsigned_branches, cg.equality_branches
);
assert!(
cg.signed_branches >= 1,
"branch_caller's signed `x > 100` must survive as a signed Jcc: signed={} unsigned={} equality={}",
cg.signed_branches,
cg.unsigned_branches,
cg.equality_branches
);
}
#[test]
fn c_od_branch_caller_uses_signed_branch() {
let Some(od) = c_od_fixture() else {
return;
};
let cg = analyze(&od.pe, addr(&od, "branch_caller")).expect("analyze");
assert!(
cg.signed_branches >= 1,
"branch_caller -O0 signed `x > 100` must survive as a signed Jcc: signed={} unsigned={} equality={}",
cg.signed_branches,
cg.unsigned_branches,
cg.equality_branches
);
}
#[test]
fn c_loop_sum_uses_signed_branch() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "loop_sum")).expect("analyze loop_sum");
assert!(
cg.signed_branches >= 1,
"loop_sum's signed `i <= n` must survive as a signed Jcc: signed={} unsigned={} equality={}",
cg.signed_branches,
cg.unsigned_branches,
cg.equality_branches
);
}
#[test]
fn c_decision_chain_uses_equality_branches() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "decision_chain")).expect("analyze");
eprintln!(
"decision_chain polarity: signed={} unsigned={} equality={}",
cg.signed_branches, cg.unsigned_branches, cg.equality_branches
);
assert!(
cg.equality_branches >= 12,
"decision_chain's `==` chain must survive as equality Jcc: signed={} unsigned={} equality={}",
cg.signed_branches,
cg.unsigned_branches,
cg.equality_branches
);
}
#[test]
fn c_diff_body_calls_reports_unported() {
let Some(fx) = c_fixture() else {
return;
};
let leaf_add = addr(&fx, "leaf_add");
let leaf_const = addr(&fx, "leaf_const");
let corpus: BTreeSet<u64> = fx
.addrs
.values()
.copied()
.filter(|&a| a != leaf_const)
.collect();
let rust_idents = vec![("leaf_add".to_string(), vec![leaf_add])];
let empty = BTreeSet::new();
let d = diff_body_calls(
&fx.pe,
&[addr(&fx, "branch_caller")],
&corpus,
&rust_idents,
&empty,
&BTreeSet::from([leaf_add]),
)
.expect("diff branch_caller");
assert!(
d.missing.is_empty(),
"leaf_const has no origin, so it must not fire MISSING: {:?}",
d.missing
);
assert!(
d.unported.contains(&leaf_const),
"branch_caller's real leaf_const edge must be inventoried as unported: {:?}",
d.unported
);
}
#[test]
fn c_call_site_targets_leaf_add_once() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "branch_caller")).expect("analyze");
let leaf = addr(&fx, "leaf_add");
let sites: Vec<_> = cg.call_sites.iter().filter(|s| s.target == leaf).collect();
assert_eq!(
sites.len(),
1,
"branch_caller must call leaf_add exactly once: {:?}",
cg.call_sites
);
}
#[test]
fn c_switch_case_calls_recovered() {
let Some(fx) = c_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "classify")).expect("analyze classify");
let leaf = addr(&fx, "leaf_add");
let sites: Vec<_> = cg
.call_sites
.iter()
.filter(|s| s.target == leaf)
.collect();
assert!(
sites.len() >= 7,
"classify must call leaf_add in (nearly) every case: {:?}",
cg.direct_calls
);
if fx.toolchain == "msvc" {
assert_eq!(
sites.len(),
8,
"MSVC lowers the 8-case switch to a jump table with one call per case: {:?}",
cg.direct_calls
);
assert!(
cg.indirect_jumps >= 1,
"the switch dispatch itself must be recognized: {:?}",
cg.direct_calls
);
let mut first_args: Vec<u64> = sites
.iter()
.filter_map(|s| match s.stack.as_slice() {
[ArgKind::Imm(a), ..] => Some(*a),
_ => None,
})
.collect();
first_args.sort_unstable();
assert_eq!(
first_args,
(100u64..108).collect::<Vec<_>>(),
"each case's pushed first-arg constant must be recovered: {:?}",
sites
.iter()
.map(|s| &s.stack)
.collect::<Vec<_>>()
);
} else {
eprintln!(
"note: skipping exact switch shape asserts on {} (compare-chain lowering unpinned)",
fx.toolchain
);
}
}
#[test]
fn c_od_switch_case_calls_recovered() {
let Some(od) = c_od_fixture() else {
return;
};
let cg = analyze(&od.pe, addr(&od, "classify")).expect("analyze classify");
let leaf = addr(&od, "leaf_add");
let sites: Vec<_> = cg
.call_sites
.iter()
.filter(|s| s.target == leaf)
.collect();
assert!(
sites.len() >= 7,
"od classify must call leaf_add in (nearly) every case: toolchain={} direct={:?} tails={:?} ind_jumps={} fp={:?}",
od.toolchain,
cg.direct_calls,
cg.tail_calls,
cg.indirect_jumps,
cfg_fingerprint(&od.pe, addr(&od, "classify")).ok(),
);
if od.toolchain == "msvc" {
assert_eq!(
sites.len(),
8,
"MSVC -Od keeps one call per case: {:?}",
cg.direct_calls
);
}
}
#[test]
fn c_od_call_site_recovers_pushed_imm_arg() {
let Some(od) = c_od_fixture() else {
return;
};
let cg = analyze(&od.pe, addr(&od, "branch_caller")).expect("analyze");
let leaf = addr(&od, "leaf_add");
let sites: Vec<_> = cg.call_sites.iter().filter(|s| s.target == leaf).collect();
assert_eq!(
sites.len(),
1,
"branch_caller must call leaf_add exactly once: {:?}",
cg.call_sites
);
if od.toolchain == "msvc" {
assert!(
sites[0].stack.iter().any(|a| matches!(a, ArgKind::Imm(1))),
"pushed literal 1 must be recovered as Imm(1): {:?}",
sites[0].stack
);
} else {
eprintln!(
"note: skipping Imm(1) recovery assert on {} (MOV stack-arg idiom unharvested)",
od.toolchain
);
}
}
#[test]
fn c_main_reaches_iat_imports() {
let Some(fx) = c_fixture() else {
return;
};
let mut seen = std::collections::BTreeSet::new();
let mut frontier = vec![addr(&fx, "main")];
let mut reached = Vec::new();
for _ in 0..4 {
let mut next = Vec::new();
for va in frontier {
if !seen.insert(va) {
continue;
}
let Ok(cg) = analyze(&fx.pe, va) else {
continue;
};
if !cg.iat_calls.is_empty() {
reached.push(va);
}
if seen.len() > 256 {
break;
}
next.extend(cg.call_targets());
}
if !reached.is_empty() || next.is_empty() {
break;
}
frontier = next;
}
assert!(
!reached.is_empty(),
"main must reach imports within a few hops: {seen:#x?}"
);
}
#[test]
fn c_manifest_functions_are_referenced() {
let Some(fx) = c_fixture() else {
return;
};
let refs = fx.pe.referenced_addrs();
for name in fx.addrs.keys() {
if name == "g_counter" {
continue; }
let va = fx.addrs[name];
assert!(
refs.contains(&(va as u32)),
"{name} {va:#x} is referenced nowhere — stale manifest?"
);
}
}
#[test]
fn c_analyze_and_byte_scan_agree() {
let Some(fx) = c_fixture() else {
return;
};
let addrs = sweep_functions(&fx.pe, 512);
let mut checked = 0u32;
let mut disagreements = Vec::new();
for caller in &addrs {
if let Ok(cg) = analyze(&fx.pe, *caller) {
for &t in cg.direct_calls.keys() {
checked += 1;
if !has_direct_branch_target(&fx.pe, t) {
disagreements.push((*caller, t));
}
}
}
}
assert!(checked > 0, "sweep found no analyzable functions");
assert!(
disagreements.is_empty(),
"{} of {checked} CALL targets disagree with the byte scan: {disagreements:?}",
disagreements.len()
);
}
#[test]
fn c_diff_calls_clean_when_declared_equals_real() {
let Some(fx) = c_fixture() else {
return;
};
let addrs = sweep_functions(&fx.pe, 512);
let mut total = 0u32;
for caller in &addrs {
if let Ok(cg) = analyze(&fx.pe, *caller) {
let real: Vec<u64> = cg.call_targets().into_iter().collect();
let d = diff_calls(&fx.pe, *caller, &real, true)
.unwrap_or_else(|e| panic!("diff_calls at {caller:#x} failed: {e}"));
total += 1;
assert!(
d.hallucinated.is_empty() && d.missing.is_empty(),
"{caller:#x} not clean: {d:?}"
);
}
}
assert!(total > 0, "sweep found no analyzable functions");
}
#[test]
fn c_image_uses_iat_imports() {
let Some(fx) = c_fixture() else {
return;
};
let addrs = sweep_functions(&fx.pe, 512);
let with_iat = addrs
.iter()
.filter_map(|a| analyze(&fx.pe, *a).ok())
.filter(|cg| !cg.iat_calls.is_empty())
.count();
assert!(
with_iat > 0,
"no IAT imports found in a hosted C binary — unexpected"
);
}
#[test]
fn cpp_caller_calls_leaf_and_counter() {
let Some(fx) = cpp_fixture() else {
return;
};
let edges = call_edges(&fx, addr(&fx, "cpp_caller_fn"));
for want in [addr(&fx, "cpp_leaf_fn"), addr(&fx, "cpp_counter_fn")] {
assert!(
edges.contains(&want),
"cpp_caller_fn edges {edges:#x?} miss {want:#x}"
);
}
}
#[test]
fn cpp_switch_dispatch_recognized() {
let Some(fx) = cpp_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "cpp_classify")).expect("analyze");
if fx.toolchain == "msvc" {
assert!(
cg.indirect_jumps >= 1,
"cpp_classify's switch dispatch must be recognized: {:?}",
cg.direct_calls
);
let fp = cfg_fingerprint(&fx.pe, addr(&fx, "cpp_classify")).expect("cfg");
eprintln!("cpp_classify fingerprint: {fp:?}");
assert!(
fp.switch_cases >= 8,
"all 8 MSVC jump-table cases must resolve to code: {fp:?}"
);
} else {
let fp = cfg_fingerprint(&fx.pe, addr(&fx, "cpp_classify")).expect("cfg");
assert!(
fp.switch_cases >= 3 || fp.conditionals >= 5,
"cpp_classify's switch must survive as control flow: {fp:?}"
);
}
}
#[test]
fn cpp_field_fn_touches_value_field() {
let Some(fx) = cpp_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "cpp_field_fn")).expect("analyze");
assert!(
cg.field_offsets.contains(&4),
"cpp_field_fn must touch Counter::value at offset 4: {:?}",
cg.field_offsets
);
}
#[test]
fn cpp_virtual_dispatch_is_indirect_only() {
let Some(fx) = cpp_fixture() else {
return;
};
let cg = analyze(&fx.pe, addr(&fx, "cpp_use_virtual_fn")).expect("analyze");
assert!(
cg.indirect_calls + cg.indirect_jumps >= 1,
"virtual call must be indirect: {cg:?}"
);
assert!(
cg.direct_calls.is_empty(),
"virtual call must not be a direct call: {:?}",
cg.direct_calls
);
assert!(
cg.vcall_slots.contains(&0),
"virtual call must dispatch through vtable slot 0 (kind): {:?}",
cg.vcall_slots
);
}
#[test]
fn cpp_spec_superset_ok_and_hallucinated_fails() {
let Some(fx) = cpp_fixture() else {
return;
};
let entry = addr(&fx, "cpp_caller_fn");
Spec::new(entry)
.calls_superset(&[addr(&fx, "cpp_leaf_fn"), addr(&fx, "cpp_counter_fn")])
.check(&fx.pe)
.expect("superset of the real call set must pass");
let err = Spec::new(entry)
.calls_superset(&[0xDEADBEEF])
.check(&fx.pe)
.expect_err("invented call must fail");
assert!(err.contains("HALLUCINATED"), "error: {err}");
}
#[test]
fn cpp_analyze_and_byte_scan_agree() {
let Some(fx) = cpp_fixture() else {
return;
};
let addrs = sweep_functions(&fx.pe, 512);
let mut checked = 0u32;
for caller in &addrs {
if let Ok(cg) = analyze(&fx.pe, *caller) {
for &t in cg.direct_calls.keys() {
checked += 1;
assert!(
has_direct_branch_target(&fx.pe, t),
"CALL target {t:#x} from {caller:#x} missing in byte scan"
);
}
}
}
assert!(checked > 0, "sweep found no analyzable functions");
}
#[test]
fn c_od_branch_caller_calls_both_leaves() {
let Some(fx) = c_od_fixture() else {
return;
};
let edges = call_edges(&fx, addr(&fx, "branch_caller"));
for leaf in [addr(&fx, "leaf_add"), addr(&fx, "leaf_const")] {
assert!(
edges.contains(&leaf),
"od branch_caller edges {edges:#x?} miss {leaf:#x}"
);
}
Spec::new(addr(&fx, "branch_caller"))
.calls_superset(&[addr(&fx, "leaf_add"), addr(&fx, "leaf_const")])
.check(&fx.pe)
.expect("superset of the real call set must pass on /Od too");
let err = Spec::new(addr(&fx, "branch_caller"))
.calls_superset(&[0xDEADBEEF])
.check(&fx.pe)
.expect_err("invented call must fail on /Od too");
assert!(err.contains("HALLUCINATED"), "error: {err}");
}
#[test]
fn c_od_sweeps_agree_and_diff_clean() {
let Some(fx) = c_od_fixture() else {
return;
};
let addrs = sweep_functions(&fx.pe, 512);
let mut checked = 0u32;
for caller in &addrs {
if let Ok(cg) = analyze(&fx.pe, *caller) {
let real: Vec<u64> = cg.call_targets().into_iter().collect();
let d = diff_calls(&fx.pe, *caller, &real, true)
.unwrap_or_else(|e| panic!("diff_calls at {caller:#x} failed: {e}"));
assert!(
d.hallucinated.is_empty() && d.missing.is_empty(),
"{caller:#x} not clean: {d:?}"
);
for &t in cg.direct_calls.keys() {
checked += 1;
assert!(
has_direct_branch_target(&fx.pe, t),
"CALL target {t:#x} from {caller:#x} missing in byte scan"
);
}
}
}
assert!(checked > 0, "od sweep found no direct calls");
}
#[test]
fn c_o2_vs_od_inlining_differential() {
let Some(fx) = c_fixture() else {
return;
};
let Some(od) = c_od_fixture() else {
return;
};
let od_names: std::collections::HashMap<u64, &str> = od
.addrs
.iter()
.map(|(k, &v)| (v, k.as_str()))
.collect();
let mut events: Vec<(String, String)> = Vec::new();
let mut compared = 0u32;
for (name, &o2_va) in fx.addrs.iter() {
let Some(&od_va) = od.addrs.get(name) else {
continue;
};
if !fx.pe.is_executable_section_va(o2_va)
|| !od.pe.is_executable_section_va(od_va)
{
continue;
}
let (Ok(_cg_o2), Ok(_cg_od)) =
(analyze(&fx.pe, o2_va), analyze(&od.pe, od_va))
else {
continue;
};
compared += 1;
let o2_edges = call_edges(&fx, o2_va);
let od_edges = call_edges(&od, od_va);
for t_od in &od_edges {
let Some(callee_name) = od_names.get(t_od).copied() else {
continue; };
let Some(&o2_target) = fx.addrs.get(callee_name) else {
continue;
};
if !o2_edges.contains(&o2_target) {
events.push((name.clone(), callee_name.to_string()));
}
}
}
assert!(
compared > 0,
"no manifest functions analyzable in both builds"
);
eprintln!("O2-vs-Od: compared {compared} functions, inlining events: {events:?}");
assert!(
events
.iter()
.any(|(f, c)| f == "uses_tiny" && c == "tiny_sum"),
"expected uses_tiny->tiny_sum inlining event; got {events:?}"
);
for (caller_name, callee_name) in &events {
let f_o2 = addr(&fx, caller_name);
let c_o2 = addr(&fx, callee_name);
let corpus: BTreeSet<u64> = [c_o2].into_iter().collect();
let d = diff_body_calls(
&fx.pe,
&[f_o2],
&corpus,
&[(callee_name.clone(), vec![c_o2])],
&BTreeSet::new(),
&BTreeSet::new(),
)
.unwrap_or_else(|e| panic!("diff for {caller_name}->{callee_name}: {e}"));
let flagship = caller_name == "uses_tiny" && callee_name == "tiny_sum";
eprintln!(
"event {caller_name}->{callee_name} (O2 {f_o2:#x}->{c_o2:#x}): \
inlined_ok={:?} hallucinated={:?} missing={:?}",
d.inlined_ok, d.hallucinated, d.missing
);
if flagship {
assert!(
d.inlined_ok.iter().any(|n| n == callee_name),
"{caller_name}->{callee_name} (O2 {f_o2:#x}->{c_o2:#x}) must be \
inlined_ok, got hallucinated={:?} missing={:?}",
d.hallucinated,
d.missing
);
assert!(
d.hallucinated.is_empty(),
"{caller_name}->{callee_name}: unexpected hallucinations: {:?}",
d.hallucinated
);
}
}
}
#[test]
fn c_dbg_calls_route_through_thunks() {
let Some(fx) = c_dbg_fixture() else {
return;
};
let addrs = sweep_functions(&fx.pe, 512);
let mut resolved = Vec::new();
for caller in &addrs {
if let Ok(cg) = analyze(&fx.pe, *caller) {
for &t in cg.direct_calls.keys() {
if let Some(u) = resolve_thunk(&fx.pe, t) {
if u != t && fx.pe.is_executable_section_va(u) {
resolved.push((*caller, t, u));
}
}
}
}
}
assert!(
!resolved.is_empty(),
"no ILT thunk edges in the dbg build — is incremental linking on?"
);
}
#[test]
fn c_dbg_diff_accepts_real_callee_through_thunk() {
let Some(fx) = c_dbg_fixture() else {
return;
};
let caller = addr(&fx, "branch_caller");
let bin: Vec<u64> = analyze(&fx.pe, caller)
.expect("analyze dbg branch_caller")
.call_targets()
.into_iter()
.collect();
assert!(
!bin.is_empty(),
"dbg branch_caller has no direct calls — incremental linking off?"
);
let corpus: BTreeSet<u64> = [addr(&fx, "leaf_add"), addr(&fx, "leaf_const")]
.into_iter()
.collect();
let rust_idents = vec![
("leaf_add".to_string(), vec![addr(&fx, "leaf_add")]),
("leaf_const".to_string(), vec![addr(&fx, "leaf_const")]),
];
let d = diff_body_calls(
&fx.pe,
&[caller],
&corpus,
&rust_idents,
&BTreeSet::new(),
&BTreeSet::new(),
)
.expect("diff_body_calls");
assert!(
d.hallucinated.is_empty(),
"real callees reached through ILT stubs must be accepted: {:?}",
d.hallucinated
);
for t in &bin {
assert!(
!corpus.contains(t),
"dbg build calls {t:#x} directly (no thunk?) — test premise broken"
);
}
}
fn vtable_slots(fx: &Fixture, vt_name: &str) -> Vec<u64> {
let vt = addr(fx, vt_name);
(0..8u64)
.filter_map(|slot| {
let b = fx.pe.read(vt + slot * 4, 4)?;
if b.len() < 4 {
return None;
}
let va = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as u64;
(va != 0 && fx.pe.is_executable_section_va(va)).then_some(va)
})
.collect()
}
#[test]
fn cpp_vtable_slots_are_executable_methods() {
let Some(fx) = cpp_fixture() else {
return;
};
for vt in ["circle_vt", "square_vt"] {
let slots = vtable_slots(&fx, vt);
assert!(
slots.len() >= 2,
"{vt} must have at least 2 method slots: {slots:#x?}"
);
}
}
#[test]
fn cpp_kind_slot_is_vtable_only_and_thiscall() {
let Some(fx) = cpp_fixture() else {
return;
};
let slots = vtable_slots(&fx, "circle_vt");
let mut kind_va = None;
for va in &slots {
if let Ok(cg) = analyze(&fx.pe, *va) {
if cg.field_offsets.contains(&4) {
kind_va = Some(*va);
break;
}
}
}
let kind_va = kind_va.expect("no circle_vt slot reads field offset 4 (want kind)");
assert!(
!has_direct_branch_target(&fx.pe, kind_va),
"kind {kind_va:#x} must be reached only via its vtable slot"
);
assert!(
fx.pe.referenced_addrs().contains(&(kind_va as u32)),
"kind {kind_va:#x} must be referenced (stored in the vtable)"
);
assert_eq!(
reg_params(&fx.pe, kind_va),
(true, false),
"kind must be detected as thiscall (reads ECX, never EDX)"
);
}