#!/usr/bin/env python3 """One definition of what counts as a name, and one of what counts as readable. Four separate metric-definition errors in this work came from re-writing these predicates at each use site. Each time the pattern was slightly too strict, the number came out too good and the work it should have exposed stayed hidden: * the field share globbed fw/decomp/*.c, which also matches the .anon.c files holding the bodies of functions that have no name, so unnamed bodies were counted against the named artifacts (67.5% reported, 75.6% actual); * the per-function dereference residue summed each function across all 17 builds, making a flat distribution look concentrated (158 reported for the top holder, 14 actual); * the named count accepted IDA's address defaults, so 243 sub_401234 entries counted as names (67.1% reported, 66.3% actual); * the same count, and the repair pass written to fix it, both anchored on $ right after the hex digits and so missed IDA's collision suffix, leaving 1,852 j_sub_D07364_12 entries counted as names and 713 thunks fixable but untouched (66.6% reported, 60.2% actual). The last of those was introduced in the commit that described the previous one. So these live here, imported, with the regex written once. """ import json import re from pathlib import Path HERE = Path(__file__).resolve().parent DECOMP = HERE / "decomp" # IDA's address-derived defaults, including the collision suffix it appends when the same name # is assigned twice. sub_401234, j_sub_401234, j_sub_D07364_12 are all equally uninformative. ADDR_NAME = re.compile(r"^(j_)?sub_[0-9A-Fa-f]+(_\d+)?$") # A placeholder derived from a body hash: a stable identity, but not a name. HASH_NAME = re.compile(r"lib_[0-9a-f]{8}_\d+") FIELD_ACCESS = (re.compile(r"\b\w+->\w+"), re.compile(r"\bv\d+\.[a-z_]\w*")) RAW_DEREF = re.compile(r"\*\(\s*_?\w+\s*\*\s*\)\s*\(\s*\w+\s*\+") def is_addr_name(name: str | None) -> bool: """True if this is IDA's address default rather than a name.""" return bool(name) and bool(ADDR_NAME.match(name)) def is_meaningful(name: str | None) -> bool: """True if the name tells a reader something. Address defaults do not count.""" return bool(name) and not ADDR_NAME.match(name) def measure() -> dict: """Corpus metrics, from the artifacts, with every denominator stated.""" total = meaningful = addr = placeholder = 0 for p in sorted(DECOMP.glob("*.index.json")): for fn in json.loads(p.read_text())["functions"]: nm = fn.get("name") or "" total += 1 if not nm: continue if ADDR_NAME.match(nm): addr += 1 else: meaningful += 1 if HASH_NAME.search(nm): placeholder += 1 # Named artifacts only. The .anon.c files hold functions with no name; counting their # untyped bodies against the typing work measures the corpus, not the readable output. fields = raw = 0 for c in sorted(DECOMP.glob("*.c")): if c.name.endswith(".anon.c"): continue txt = c.read_text() for pat in FIELD_ACCESS: fields += len(pat.findall(txt)) raw += len(RAW_DEREF.findall(txt)) return { "functions": total, "meaningful_names": meaningful, "meaningful_pct": round(100.0 * meaningful / total, 1) if total else 0.0, "address_derived": addr, "hash_placeholders": placeholder, "typed_field_accesses": fields, "raw_dereferences": raw, "field_share_pct": round(100.0 * fields / (fields + raw), 1) if fields + raw else 0.0, } if __name__ == "__main__": m = measure() w = max(len(k) for k in m) for k, v in m.items(): print("%-*s %s" % (w, k, v))