code wiki / (root) / nx_mmapbal.nx

nx_mmapbal.nx source

↩ module page · 1328 lines · 73257 B

1// nx_mmapbal.nx -- STATIC mmap/munmap BALANCE SCANNER over the .nx corpus. 2// 3// WHY THIS EXISTS (measured 2026-07-31, debt 1785515861): the ecosystem's ONLY leak instrument, 4// nx_leak_check, is DYNAMIC and daemon-only -- its `fleet` verb certifies organs that are RUNNING and 5// a LEAK verdict requires LK_MIN_AGE_S=120s of process age. One-shot organs (5365 gates + every CLI 6// organ) live for milliseconds and exit, so they can NEVER appear in a fleet scan and can NEVER reach 7// the age floor. That hole is why nx_gate_verdict.nx -- the D001 base class 150+ organs import -- 8// leaked on EVERY call for ~13 days while every instrument reported healthy. Dynamic and static 9// detection are not redundant here; they cover disjoint halves of the fleet. 10// 11// THE RULE, stated once so it cannot drift: within ONE function, every sys_mmap must be matched by a 12// sys_munmap, UNLESS the signature RETURNS A POINTER -- that is ownership TRANSFER to the caller, not 13// a leak (gv_ctr is the correct exemplar: it returns the counter, which must outlive the call). A 14// function that allocates and returns i64 has no way to hand the memory back, so an unmatched mmap 15// there is a leak BY CONSTRUCTION, provable from the source alone with no runtime sampling. 16// 17// main() IS REPORTED SEPARATELY, NOT CONVICTED: it runs once per process and the kernel reclaims at 18// exit, so an unbalanced mmap in main is a style note, not a defect. Convicting it would bury the 19// real signal (helpers called in loops) under noise -- the ranked-wrong-thing failure. 20// 21// COMMENT LINES ARE SKIPPED so a header that merely MENTIONS sys_mmap cannot fabricate a finding. 22// 23// ⚠ KNOWN LIMITATION -- OWNERSHIP VIA STRUCT FIELD IS INVISIBLE (measured 2026-07-31). 24// mb_owns detects ownership transfer ONLY through a `-> *` RETURN TYPE. A function that hands its 25// allocation to the caller by STORING IT IN AN OUT-PARAM STRUCT FIELD is reported as a leak even 26// though it is correct. PROVEN CASE: nx_opaque_login.olg_ctx_setup_ttl mmaps 5 key buffers and 27// passes 3 to nx_auth_context_init, which does `ctx.opaque_skS_32 = ...` / `ctx.server_ed25519_priv_32 28// = ...` -- storing the POINTERS, not copying the bytes. Those live for the whole daemon lifetime; 29// freeing them would be a use-after-free on every login and would corrupt the server signing key. 30// CONSEQUENCE: *_init / *_ctx_setup / *_new functions are the FALSE-POSITIVE class of this scanner. 31// A headline count that does not exclude them OVERSTATES the defect. Treat any finding in an 32// initializer as UNPROVEN until the callee is read. Fixing this properly needs callee-aware 33// escape analysis (does the callee store the pointer?), which is a real rung, not a tweak. 34// 35// FAIL-LOUD ON COVERAGE (law L011): prints files_scanned / files_skipped / coverage_complete. A corpus 36// scanner that hides partial coverage presents partial-as-complete, which is the self-ceiling defect. 37// 38// WHY `deep` EXISTS AND `scan` CANNOT SUBSTITUTE FOR IT (measured 2026-08-14): `scan` streams every 39// finding and its summary block sits BELOW that stream, so BOTH transports truncate before the totals 40// are reached -- the MCP capture cap at 163,840 B and the async job capture cap at 1 MiB, both hit on 41// this organ on the same day. A caller using `scan` to answer a whole-corpus question silently 42// receives an ALPHABETICAL PREFIX that looks like a complete answer: the 163,840 B run stopped inside 43// nx_c* and never reached nx_docportal, nx_gallery or nx_hub_gw. Use `deep` for any POPULATION 44// question and `file` to attribute a single fix on a shared, concurrently-edited tree. 45// THESE THREE LINES ARE THE PUBLISHED CONTRACT: nx_toolgrammar harvests them verbatim into 46// knowledge/tool_grammar.conf, which nx_tools_api serves as this tool's MCP call grammar. A verb 47// missing HERE is invisible to every caller even though the code implements it -- `deep` and `file` 48// were exactly that, and the omission cost a near-duplicate rebuild of `deep` on 2026-08-14. 49// 50// nx_mmapbal scan [dir] walk dir (default buildroot/runtime) for .nx, report unbalanced funcs 51// nx_mmapbal deep [dir] RECURSIVE SUMMARY-ONLY: classified totals + coverage envelope -- USE THIS FOR POPULATION QUESTIONS 52// nx_mmapbal hot [dir] ENUMERATE the page-backed-in-a-loop worklist: one line per function, HOT-TOTAL last 53// nx_mmapbal file <path> ONE file: the only attributable verify on a shared tree (a total-count delta is not attributable) 54// 55// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 56// LAYERING (law): runtime/ CANNOT import _hdl_build/, and nx_gate_verdict_lib.nx (the verdict READER, 57// carrying gv_slurp/gv_len) is misplaced in _hdl_build/ -- so this organ cannot reuse it from here and 58// carries a minimal local slurp instead. That is the layering law winning over rule 15, not a DRY miss. 59// Lifting that lib to runtime/ via nx_liblift is filed separately; it is not safe to move mid-flight 60// while a sibling lane holds nx_gate_rollup, which imports it. 61import "nx_syscalls.nx" 62import "nx_gate_verdict.nx" 63 64const MB_DIRBUF: i64 = 262144 65const MB_FILECAP: i64 = 524288 66const MB_PATHCAP: i64 = 1024 67const MB_DIR_TYPE: i64 = 4 68const MB_NL_C: i64 = 10 69const MB_SLASH_C: i64 = 47 70const MB_SPACE_C: i64 = 32 71const MB_TAB_C: i64 = 9 72const MB_DOT_C: i64 = 46 73const MB_N_C: i64 = 110 74const MB_X_C: i64 = 120 75const MB_WORD: i64 = 8 76const MB_ST_SLOTS: i64 = 32 77const MB_S_FILES: i64 = 0 78const MB_S_FUNCS: i64 = 1 79const MB_S_BAD: i64 = 2 80const MB_S_MAIN: i64 = 3 81const MB_S_SKIP: i64 = 4 82const MB_S_SITES: i64 = 5 83// Added 2026-08-06 -- see the `deep` verb for why each of these exists. 84const MB_S_TRUNC: i64 = 6 // files that FILLED the read cap, i.e. were silently cut short 85const MB_S_INIT: i64 = 7 // unbalanced funcs whose name marks them the documented FP class 86const MB_S_INITSITES: i64 = 8 // their sites, kept OUT of the convictable headline 87const MB_S_DIRS: i64 = 9 // directories walked (1 == the old non-recursive behaviour) 88const MB_S_QOVF: i64 = 10 // directory-queue overflows; any is a coverage hole 89// SHAPE CLASSES (mode 2). A corpus total tells you HOW MUCH is leaking; it does not tell you which 90// codemod pays. These buckets rank the remediation by drop-in-ability, which is the only ordering 91// that matters when the total is five figures and hand edits are not an option. 92const MB_S_C_SHIM: i64 = 11 // calls ccz_cat_num but hand-rolls the mmap'd fd shim -> nxi_fd drop-in 93const MB_S_C_SHIMS: i64 = 12 94const MB_S_C_INLINE: i64 = 13 // open-coded %10 digit loop into an mmap'd scratch -> same output, needs proof 95const MB_S_C_INLINES: i64 = 14 96const MB_S_C_OTHER: i64 = 15 // everything else: a real buffer with a real lifetime, no free 97const MB_S_C_OTHERS: i64 = 16 98 99// Iterative directory worklist. Deliberately a QUEUE and not recursion: this organ must report 100// coverage honestly, and a recursion depth limit that trips is far harder to detect and report than 101// a queue slot that refuses. Overflow increments MB_S_QOVF and drops coverage_complete to 0. 102const MB_QCAP: i64 = 512 103const MB_DOT_ENT: *u8 = "." 104const MB_DOTDOT_ENT: *u8 = ".." 105 106func mb_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 107 108func mb_streq(a: *u8, b: *u8) -> i64 { 109 var i: i64 = 0 110 while a[i] != (0 as u8) { 111 if a[i] != b[i] { return 0 } 112 i = i + 1 113 } 114 if b[i] != (0 as u8) { return 0 } 115 return 1 116} 117 118// read a whole file into buf; returns bytes read, 0 if unreadable. FAIL-CLOSED: an unreadable file is 119// counted as SKIPPED and drops coverage_complete to 0 -- it is never silently treated as clean. 120func mb_slurp(path: *u8, buf: *u8, cap: i64) -> i64 { 121 let fd: i64 = sys_openat_rd(path) 122 if fd < 0 { return 0 } 123 var tot: i64 = 0 124 var go: i64 = 1 125 while go == 1 { 126 let r: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot) 127 if r <= 0 { go = 0 } else { tot = tot + r } 128 if tot >= cap { go = 0 } 129 } 130 sys_close(fd) 131 return tot 132} 133 134func mb_line_end(buf: *u8, s: i64, e: i64) -> i64 { 135 var i: i64 = s 136 while i < e { 137 if buf[i] == (MB_NL_C as u8) { return i } 138 i = i + 1 139 } 140 return e 141} 142 143func mb_starts(buf: *u8, s: i64, e: i64, needle: *u8) -> i64 { 144 var k: i64 = 0 145 while needle[k] != (0 as u8) { 146 if s + k >= e { return 0 } 147 if buf[s+k] != needle[k] { return 0 } 148 k = k + 1 149 } 150 return 1 151} 152 153// leading whitespace then "//" = a comment line; its mentions of sys_mmap are prose, not code 154func mb_is_comment(buf: *u8, s: i64, e: i64) -> i64 { 155 var i: i64 = s 156 var go: i64 = 1 157 while go == 1 { 158 if i >= e { return 0 } 159 let c: i64 = buf[i] as i64 160 if c == MB_SPACE_C { i = i + 1 } else { 161 if c == MB_TAB_C { i = i + 1 } else { go = 0 } 162 } 163 } 164 return mb_starts(buf, i, e, "//" as *u8) 165} 166 167// ⚠⚠STRING LITERALS ARE SKIPPED, FOR THE SAME REASON COMMENTS ARE. This organ has always skipped 168// comment lines "so a header that merely MENTIONS sys_mmap cannot fabricate a finding" -- and then 169// counted the identical text inside a QUOTED STRING, which is the other way source can quote source. 170// MEASURED 2026-08-15, on this very file: mb_loop_allocs contains the literal "sys_mmap(" as the needle 171// it searches for, so the scanner counted its own search pattern as an allocation and reported 172// `mmap=2 munmap=1` for a function with exactly one of each. A FALSE LEAK, in the leak detector, 173// caused by the detector's own source. 174// ★★★★★★A DETECTOR THAT SCANS SOURCE FINDS ITS OWN SEARCH PATTERN -- and the organ had already 175// learned this lesson for comments and not applied it one syntax over. 176// Escapes are honoured so a \" inside a literal does not end it early and re-expose the tail. 177const MB_QUOTE_C: i64 = 34 178const MB_BSLASH_C: i64 = 92 179func mb_count_range(buf: *u8, s: i64, e: i64, needle: *u8) -> i64 { 180 var cnt: i64 = 0 181 var ls: i64 = s 182 while ls < e { 183 let le: i64 = mb_line_end(buf, ls, e) 184 if mb_is_comment(buf, ls, le) == 0 { 185 var i: i64 = ls 186 var instr: i64 = 0 187 while i < le { 188 let c: i64 = buf[i] as i64 189 if instr == 1 { 190 if c == MB_BSLASH_C { i = i + 2 } else { 191 if c == MB_QUOTE_C { instr = 0; i = i + 1 } else { i = i + 1 } 192 } 193 } else { 194 if c == MB_QUOTE_C { instr = 1; i = i + 1 } else { 195 if mb_starts(buf, i, le, needle) == 1 { cnt = cnt + 1 } 196 i = i + 1 197 } 198 } 199 } 200 } 201 ls = le + 1 202 } 203 return cnt 204} 205 206// signature returns a pointer => ownership TRANSFER to the caller, which is correct, not a leak 207func mb_owns(buf: *u8, s: i64, e: i64) -> i64 { 208 var i: i64 = s 209 while i < e { 210 if mb_starts(buf, i, e, "-> *" as *u8) == 1 { return 1 } 211 i = i + 1 212 } 213 return 0 214} 215 216func mb_put_range(buf: *u8, s: i64, e: i64) -> i64 { 217 sys_write(1, ((buf as i64) + s) as *u8, e - s) 218 return 0 219} 220 221func mb_is_nx(nm: *u8, n: i64) -> i64 { 222 if n < 4 { return 0 } 223 if nm[n-3] != (MB_DOT_C as u8) { return 0 } 224 if nm[n-2] != (MB_N_C as u8) { return 0 } 225 if nm[n-1] != (MB_X_C as u8) { return 0 } 226 return 1 227} 228 229func mb_join(dir: *u8, nm: *u8, out: *u8) -> i64 { 230 var o: i64 = 0 231 var i: i64 = 0 232 while dir[i] != (0 as u8) { out[o] = dir[i]; o = o + 1; i = i + 1 } 233 out[o] = MB_SLASH_C as u8; o = o + 1 234 i = 0 235 while nm[i] != (0 as u8) { out[o] = nm[i]; o = o + 1; i = i + 1 } 236 out[o] = 0 as u8 237 return o 238} 239 240// ARENA WEIGHTING (2026-08-06). After the small-allocation bump arena landed in nx_syscalls.sys_mmap, 241// a SITE IS NO LONGER A PAGE. Requests <= NXA_SMALL_MAX are bump-allocated at ~48 bytes with no VMA of 242// their own; larger requests still cost a full page AND a kernel VMA per call. A raw site total now 243// overstates harm by roughly 85x across the cheap half, so this ruler must WEIGH what it counts or it 244// will keep reporting a five-figure alarm for a cost that has largely been priced away. 245// MB_ARENA_MAX MUST TRACK NXA_SMALL_MAX in nx_syscalls.nx. If the threshold moves and this does not, 246// the weighing lies in the SAFE-LOOKING direction, which is the one direction a leak ruler must never 247// drift. 248// 249// ⚠⚠IT DRIFTED, AND "MUST TRACK" IS WHY. MEASURED 2026-08-15: this read 64 while nx_syscalls has 250// declared NXA_SMALL_MAX = 256 since the arena landed. Every request in the 65..256 band is bump 251// allocated at ~48 bytes with no VMA, and this ruler was weighing all of them as a full page plus a 252// kernel VMA -- so the headline overstated harm across that whole band. The header predicted the 253// opposite drift and called that the dangerous one; this drift is the OTHER failure, crying wolf, and 254// ★A DETECTOR WITH FALSE POSITIVES IS WORSE THAN NONE BECAUSE EVERYONE LEARNS TO IGNORE IT. 255// ★★★★★★A COMMENT SAYING "MUST TRACK X" IS A LAW THAT HAS TO BE REMEMBERED, THEREFORE ONE THAT 256// WILL BE SKIPPED -- AND THE SECOND COPY OF A THRESHOLD IS THE DUPLICATE-RULER DEFECT WEARING A 257// CONSTANT. nx_syscalls is already imported here, so the value is now DERIVED and drift is impossible 258// by construction rather than by anybody's diligence. 259const MB_ARENA_MAX: i64 = NXA_SMALL_MAX 260const MB_S_W_ARENA: i64 = 17 261const MB_S_W_PAGE: i64 = 18 262const MB_S_W_UNK: i64 = 19 263const MB_S_W_TOT: i64 = 20 264 265// ARENA-ONLY: convicted functions whose EVERY unmatched request is <= NXA_SMALL_MAX (2026-08-15). 266// ★★★★★★A RULER MUST NOT DEMAND AN ACTION ITS OWN ALLOCATOR DEFINES AS A NO-OP. sys_munmap in 267// nx_syscalls opens with `if len <= NXA_SMALL_MAX { return 0 }` -- for a small request the free is a 268// LITERAL NO-OP, because the bytes came from a bump chunk and unmapping an interior pointer would tear 269// a hole in a chunk still holding other callers' live allocations. So "add the missing munmap" is not 270// a fix for these; it is a call that provably does nothing. Convicting them is the same shape as a 271// predicate that is equally true of the correct design. 272// This does NOT move MB_S_BAD: the legacy headline is untouched and every existing consumer keeps its 273// number. It SPLITS it, so a reader can see which part is the segfault-causing class the header cares 274// about ("CRITICAL for long-running loops ... the leak hits RLIMIT_AS -> mmap returns -12 -> SEGFAULT") 275// and which part is arena churn that costs ~48 bytes and no VMA. 276// ⚠UNKNOWN SIZES STAY CONVICTED. A site whose argument this organ cannot resolve is NOT counted as 277// arena-only -- moving an unresolved size into the reassuring bucket is the one direction a leak ruler 278// must never drift, which is this file's own standing rule. 279const MB_S_ARENAONLY: i64 = 21 280const MB_S_ARENAONLYSITES: i64 = 22 281// PAGE-BACKED and REPEATEDLY EXECUTED -- the class that actually exhausts vm.max_map_count. 282// ⚠⚠THE LEXICAL LOOP AXIS ALONE IS NOT THIS CLASS, AND I SHIPPED IT BELIEVING IT WAS. Found by using 283// it: nx_gallery_serve -- a daemon that has been UP on :18090 throughout -- reported `page_backed=35 284// of which in_a_loop=0`, while 35 of its unbalanced functions are HTTP request handlers taking 285// (rbuf, req, rn). They allocate once per CALL and the accept loop is in another function entirely, so 286// a lexical intra-function scan scores the estate's most dangerous leaks at ZERO. 287// ★★★★★★"REPEATEDLY EXECUTED" HAS TWO SOURCES AND A LEXICAL SCAN SEES ONLY ONE: a loop INSIDE the 288// function, or a long-lived process calling the function forever. Measuring one and naming it after 289// both is the same narrower-subject defect this organ was built to expose. 290// The second source is detectable per FILE with no call graph: a source that calls sys_listen/sys_accept 291// IS a server, and every unbalanced per-call allocation in it repeats for the life of the process. 292// The two reasons OVERLAP by construction, so the union is the worklist and the parts are printed 293// beside it rather than summed. 294const MB_S_HOT: i64 = 23 // union: the worklist 295const MB_S_HOT_LOOP: i64 = 24 // reason A: lexically inside a while body 296const MB_S_HOT_SRV: i64 = 25 // reason B: in a NON-forking server (handlers run in the long-lived parent) 297const MB_S_SRV_FORK: i64 = 26 // EXCLUDED: server that forks per connection -- the child's exit frees it 298 299// THE RATCHET (2026-08-15). D2 used to assert `unbalanced_funcs == 0` over a 102,396-function corpus. 300// That can never pass, so this gate was PERMANENTLY RED -- and ★★★★★★A DETECTOR THAT IS PERMANENTLY 301// RED IS ONE EVERYONE LEARNS TO IGNORE, which is the likeliest reason a scanner this good was not in 302// any roster. A ratchet against a recorded floor can be GREEN today, turns RED the moment somebody 303// adds a page-backed leak, and tightens itself when the number falls -- ★A RATCHET THAT DOES NOT 304// TIGHTEN WHEN YOU IMPROVE IS JUST A THRESHOLD. 305// It ratchets PAGE-BACKED, not the raw headline: the arena-only two thirds cannot be fixed by adding a 306// munmap, so holding them in the number would make the gate demand impossible work forever. 307// ⚠MISSING FLOOR IS THE THIRD STATE, NOT A PASS. An absent conf means this run has no baseline to 308// judge against, which is "I could not look", and a gate that treats that as success is the exact 309// shape of a green that means UNEXAMINED. 310// ⚠⚠STATED LIMITATION: this is a COUNT ratchet on a SHARED tree, so a rise says a leak appeared but 311// not WHOSE -- the estate's own law (nx_unwired) is a NAME SET for precisely that reason. Carrying 312// 6,394 names is the correct next rung and is named here rather than left implied. 313const MB_RATCHET_PATH: *u8 = "knowledge/status/mmapbal_pagebacked.conf" 314const MB_RATCHET_CAP: i64 = 4096 315 316// PER-FILE CONST TABLE (2026-08-06). 31pct of weighed sites passed a NAMED CONST rather than a literal, 317// so a literal-only weigher leaves a third of the corpus in UNKNOWN and the headline stays soft. This 318// resolves `const NAME: i64 = <decimal>` declared IN THE SAME FILE and looks up bare-identifier args. 319// SCOPE IS DELIBERATELY FILE-LOCAL: a const imported from another module stays UNKNOWN rather than 320// being guessed, because resolving across the import graph is a real rung and a wrong resolution would 321// move a site into ARENA -- the reassuring direction, which is the one this ruler must never drift. 322const MBC_MAX: i64 = 1024 323const MBC_NAMELEN: i64 = 48 324 325static mbc_names: *u8 326static mbc_vals: *i64 327static mbc_n: *i64 328 329func mbc_init() -> i64 { 330 if (mbc_n as i64) != 0 { return 0 } 331 mbc_names = sys_mmap(MBC_MAX * MBC_NAMELEN) 332 mbc_vals = sys_mmap(MBC_MAX * MB_WORD) as *i64 333 mbc_n = sys_mmap(16) as *i64 334 return 0 335} 336 337// 1 iff c is [A-Za-z0-9_] -- the identifier alphabet this corpus actually uses. 338func mbc_isident(c: i64) -> i64 { 339 if c == 95 { return 1 } 340 if c >= 48 { if c <= 57 { return 1 } } 341 if c >= 65 { if c <= 90 { return 1 } } 342 if c >= 97 { if c <= 122 { return 1 } } 343 return 0 344} 345 346// Rebuild the table from one file buffer. ONLY `const NAME: i64 = <decimal>` is accepted -- a computed 347// or hex initialiser is SKIPPED rather than half-parsed, so anything not certainly known stays UNKNOWN. 348func mbc_scan(buf: *u8, n: i64) -> i64 { 349 mbc_init() 350 mbc_n[0] = 0 351 var ls: i64 = 0 352 while ls < n { 353 let le: i64 = mb_line_end(buf, ls, n) 354 if mb_starts(buf, ls, le, "const " as *u8) == 1 { 355 let ns: i64 = ls + 6 356 var q: i64 = ns 357 var go3: i64 = 1 358 while go3 == 1 { 359 if q >= le { go3 = 0 } else { 360 if mbc_isident(buf[q] as i64) == 1 { q = q + 1 } else { go3 = 0 } 361 } 362 } 363 let ne: i64 = q 364 var eq: i64 = 0 - 1 365 var r: i64 = ne 366 while r < le { 367 if buf[r] == (61 as u8) { eq = r; r = le } else { r = r + 1 } 368 } 369 if eq > 0 { 370 var d: i64 = eq + 1 371 var go4: i64 = 1 372 while go4 == 1 { 373 if d >= le { go4 = 0 } else { 374 if buf[d] == (32 as u8) { d = d + 1 } else { go4 = 0 } 375 } 376 } 377 var v: i64 = 0 378 var digs: i64 = 0 379 var ok: i64 = 1 380 var go5: i64 = 1 381 while go5 == 1 { 382 if d >= le { go5 = 0 } else { 383 let c: i64 = buf[d] as i64 384 if c < 48 { ok = 0; go5 = 0 } else { 385 if c > 57 { ok = 0; go5 = 0 } else { 386 v = v * 10 + (c - 48) 387 digs = digs + 1 388 d = d + 1 389 } 390 } 391 } 392 } 393 let nl: i64 = ne - ns 394 if digs > 0 { if ok == 1 { if nl > 0 { if nl < MBC_NAMELEN { 395 let cnt: i64 = mbc_n[0] 396 if cnt < MBC_MAX { 397 let dst: *u8 = ((mbc_names as i64) + cnt * MBC_NAMELEN) as *u8 398 var k: i64 = 0 399 while k < nl { dst[k] = buf[ns + k]; k = k + 1 } 400 dst[nl] = 0 as u8 401 mbc_vals[cnt] = v 402 mbc_n[0] = cnt + 1 403 } 404 } } } } 405 } 406 } 407 ls = le + 1 408 } 409 return mbc_n[0] 410} 411 412// Value of the const named by buf[s,e), or -1 when THIS FILE does not declare it. 413func mbc_lookup(buf: *u8, s: i64, e: i64) -> i64 { 414 // ⚠⚠REFUSE ON AN UNBUILT TABLE INSTEAD OF DEREFERENCING IT. MEASURED 2026-08-15: the weigher used to 415 // be reachable only from the directory walk, which calls mbc_scan first; widening its call site to 416 // every mode reached it from the `file` verb, where the table had never been built -- mbc_n was 417 // still 0 and this line SIGSEGV'd at address 0. ★MOVING A CALL INTO A NEW CONTEXT MOVES IT AWAY 418 // FROM THE SETUP THAT MADE IT SAFE. The entry point now builds the table (the real fix), and this 419 // refuses as UNKNOWN if it ever happens again -- unknown keeps a site OUT of ARENA, which is the 420 // direction this ruler must never drift. 421 if (mbc_n as i64) == 0 { return 0 - 1 } 422 let nl: i64 = e - s 423 if nl <= 0 { return 0 - 1 } 424 if nl >= MBC_NAMELEN { return 0 - 1 } 425 let cnt: i64 = mbc_n[0] 426 var i: i64 = 0 427 while i < cnt { 428 let nm: *u8 = ((mbc_names as i64) + i * MBC_NAMELEN) as *u8 429 var k: i64 = 0 430 var same: i64 = 1 431 while k < nl { 432 if nm[k] != buf[s + k] { same = 0; k = nl } else { k = k + 1 } 433 } 434 if same == 1 { if nm[nl] == (0 as u8) { return mbc_vals[i] } } 435 i = i + 1 436 } 437 return 0 - 1 438} 439 440// Classify every sys_mmap( site in [s,e) by its LITERAL argument. A non-literal -- a named const or an 441// expression -- is counted UNKNOWN and NEVER assumed cheap: assuming would bias the headline downward, 442// and a ruler that drifts toward reassurance is worse than no ruler. 443func mb_weigh_range(buf: *u8, s: i64, e: i64, st: *i64) -> i64 { 444 var ls: i64 = s 445 while ls < e { 446 let le: i64 = mb_line_end(buf, ls, e) 447 if mb_is_comment(buf, ls, le) == 0 { 448 var i: i64 = ls 449 while i < le { 450 if mb_starts(buf, i, le, "sys_mmap(" as *u8) == 1 { 451 st[MB_S_W_TOT] = st[MB_S_W_TOT] + 1 452 var p: i64 = i + 9 453 var v: i64 = 0 454 var digits: i64 = 0 455 var lit: i64 = 1 456 var go2: i64 = 1 457 while go2 == 1 { 458 if p >= le { lit = 0; go2 = 0 } else { 459 let c: i64 = buf[p] as i64 460 if c == 41 { go2 = 0 } else { 461 if c < 48 { lit = 0; go2 = 0 } else { 462 if c > 57 { lit = 0; go2 = 0 } else { 463 v = v * 10 + (c - 48) 464 digits = digits + 1 465 p = p + 1 466 } 467 } 468 } 469 } 470 } 471 if digits == 0 { lit = 0 } 472 if lit == 0 { 473 // Not a literal. If the WHOLE argument is a bare identifier, resolve it against 474 // this file's const table. An expression (MB_ST_SLOTS * MB_WORD) stays UNKNOWN 475 // rather than being guessed -- a wrong resolution would move a site into ARENA, 476 // the reassuring direction. 477 let a: i64 = i + 9 478 var b2: i64 = a 479 var idok: i64 = 1 480 var go6: i64 = 1 481 while go6 == 1 { 482 if b2 >= le { idok = 0; go6 = 0 } else { 483 let c2: i64 = buf[b2] as i64 484 if c2 == 41 { go6 = 0 } else { 485 if mbc_isident(c2) == 1 { b2 = b2 + 1 } else { idok = 0; go6 = 0 } 486 } 487 } 488 } 489 var rv: i64 = 0 - 1 490 if idok == 1 { rv = mbc_lookup(buf, a, b2) } 491 if rv < 0 { st[MB_S_W_UNK] = st[MB_S_W_UNK] + 1 } else { 492 if rv <= MB_ARENA_MAX { st[MB_S_W_ARENA] = st[MB_S_W_ARENA] + 1 } else { st[MB_S_W_PAGE] = st[MB_S_W_PAGE] + 1 } 493 } 494 } else { 495 if v <= MB_ARENA_MAX { st[MB_S_W_ARENA] = st[MB_S_W_ARENA] + 1 } else { st[MB_S_W_PAGE] = st[MB_S_W_PAGE] + 1 } 496 } 497 } 498 i = i + 1 499 } 500 } 501 ls = le + 1 502 } 503 return 0 504} 505 506// Read the ratchet floor: the first run of decimal digits in the conf. Returns -1 when the file is 507// absent or holds no number, which the caller MUST treat as UNMEASURED rather than as zero. 508func mb_read_floor() -> i64 { 509 let b: *u8 = sys_mmap(MB_RATCHET_CAP) 510 let n: i64 = mb_slurp(MB_RATCHET_PATH, b, MB_RATCHET_CAP) 511 if n <= 0 { sys_munmap(b, MB_RATCHET_CAP); return 0 - 1 } 512 var i: i64 = 0 513 var v: i64 = 0 514 var got: i64 = 0 515 while i < n { 516 let c: i64 = b[i] as i64 517 if c >= 48 { 518 if c <= 57 { v = v * 10 + (c - 48); got = 1; i = i + 1 } else { if got == 1 { i = n } else { i = i + 1 } } 519 } else { if got == 1 { i = n } else { i = i + 1 } } 520 } 521 sys_munmap(b, MB_RATCHET_CAP) 522 if got == 0 { return 0 - 1 } 523 return v 524} 525 526// Tighten the floor. Announces, because a ratchet that moves in silence is indistinguishable from one 527// that never moved, and the next reader cannot tell an improvement from a stale file. 528func mb_write_floor(v: i64) -> i64 { 529 let fd: i64 = sys_openat_wr(MB_RATCHET_PATH, MODE_0644) 530 if fd < 0 { 531 gv_puts(" RATCHET-UNWRITABLE " as *u8); gv_puts(MB_RATCHET_PATH) 532 gv_puts(" -- the floor did NOT tighten; fix the path before trusting the next run\n" as *u8) 533 return 0 - 1 534 } 535 let b: *u8 = sys_mmap(64) 536 var t: *u8 = sys_mmap(32) 537 var m: i64 = v 538 var k: i64 = 0 539 if m == 0 { t[0] = 48 as u8; k = 1 } 540 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 541 var w: i64 = 0 542 while k > 0 { k = k - 1; b[w] = t[k]; w = w + 1 } 543 b[w] = 10 as u8 544 w = w + 1 545 sys_write(fd, b, w) 546 sys_close(fd) 547 sys_munmap(b, 64) 548 sys_munmap(t, 32) 549 return 0 550} 551 552// LOOP SCOPE (2026-08-15). This organ's own header names the real signal -- "the real signal (helpers 553// called in loops)" -- and then never measures it: an unmatched page allocation in a helper called ONCE 554// costs one page for the life of a process that is about to exit, while the SAME code inside a while 555// body is the RLIMIT_AS / vm.max_map_count exhaustion nx_syscalls documents with a dmesg-proven 556// SEGFAULT. Those two deserve opposite priorities and had one number. 557// 558// Lexical, single pass, no regex: track brace depth; a '{' that follows a `while` token opens a loop 559// body, and every depth so marked stays marked until its matching '}'. A sys_mmap( seen while any 560// depth is marked is inside a loop. 561// ⚠STATED IMPRECISION, because the next reader would otherwise trust this as exact: unbalancedness is 562// measured PER FUNCTION (na > nf), so this proves the function is unbalanced AND allocates in a loop -- 563// it does NOT prove the specific unmatched site is the looping one. That is strictly stronger than 564// "unbalanced" and strictly weaker than per-site attribution, and it is the shape the header asks for. 565// ⚠Depths beyond MB_LOOPDEPTH_MAX are treated as IN a loop if any shallower depth is marked -- the 566// conservative direction, since the alternative would silently exonerate deeply nested code. 567// ⚠⚠THIS LOOP DETECTOR IS A KNOWN DUPLICATE AND IS NAMED AS ONE. `as_mmap_in_loop` in nx_assure 568// (rule R3) already walks brace depth with a per-depth "opened by a while" bit -- the same algorithm. 569// I did not run nx_capsearch before writing this, and ★★★★★★"I DIDN'T KNOW IT EXISTED" IS A RETRIEVAL 570// FAILURE, NOT A DISCOVERY. 571// It is NOT deleted, because this file's own header states the layering law that forbids the fix: 572// runtime/ CANNOT import _hdl_build/, and nx_assure lives there. So the duplication is FORCED, and the 573// honest response is to make the two AGREE rather than pretend one does not exist. 574// The incumbent was MORE CORRECT than my copy: it word-boundary-checks the `while` keyword and mine did 575// not, so mine matched any identifier ENDING in "while". ★A SECOND RULER IS NOT MERELY REDUNDANT -- IT 576// IS USUALLY THE WORSE OF THE TWO, BECAUSE THE INCUMBENT HAS ALREADY BEEN BITTEN. Semantics adopted. 577// (The purposes do differ: R3 flags ANY mmap in a loop as a rule smell; this one only asks the question 578// for functions ALREADY convicted as unbalanced AND page-backed.) 579const MB_LOOPDEPTH_MAX: i64 = 64 580// 1 iff a `while` KEYWORD starts at i -- not the tail of a longer identifier. 581func mb_while_at(buf: *u8, i: i64, e: i64) -> i64 { 582 if mb_starts(buf, i, e, "while" as *u8) == 0 { return 0 } 583 if i == 0 { return 1 } 584 let p: i64 = buf[i-1] as i64 585 if p >= 97 { if p <= 122 { return 0 } } 586 if p >= 65 { if p <= 90 { return 0 } } 587 if p >= 48 { if p <= 57 { return 0 } } 588 if p == 95 { return 0 } 589 return 1 590} 591const MB_BRACE_OPEN: i64 = 123 592const MB_BRACE_CLOSE: i64 = 125 593// ⚠⚠COMMENTS AND STRINGS ARE SKIPPED HERE TOO, AND THAT IS NOT COSMETIC: this walks BRACE DEPTH, so a 594// '{' or '}' inside a comment or a quoted string does not merely add a spurious match -- it CORRUPTS 595// THE DEPTH for the rest of the function and every loop verdict after it. The first cut of this scanned 596// raw bytes, and its own explanatory comment contained a brace. ★THE FIX FOR ONE SYNTAX IS THE FIX FOR 597// THE OTHER; EXTRACT IT, DO NOT RE-TYPE IT SOMEWHERE ELSE AND FORGET A CASE. 598func mb_loop_allocs(buf: *u8, s: i64, e: i64) -> i64 { 599 let mark: *i64 = sys_mmap(MB_LOOPDEPTH_MAX * MB_WORD) as *i64 600 var z: i64 = 0 601 while z < MB_LOOPDEPTH_MAX { mark[z] = 0; z = z + 1 } 602 var depth: i64 = 0 603 var marked: i64 = 0 604 var pending: i64 = 0 605 var hits: i64 = 0 606 var ls: i64 = s 607 while ls < e { 608 let le: i64 = mb_line_end(buf, ls, e) 609 if mb_is_comment(buf, ls, le) == 0 { 610 var i: i64 = ls 611 var instr: i64 = 0 612 while i < le { 613 let c: i64 = buf[i] as i64 614 if instr == 1 { 615 if c == MB_BSLASH_C { i = i + 2 } else { 616 if c == MB_QUOTE_C { instr = 0; i = i + 1 } else { i = i + 1 } 617 } 618 } else { 619 if c == MB_QUOTE_C { instr = 1; i = i + 1 } else { 620 if c == MB_BRACE_OPEN { 621 depth = depth + 1 622 if pending == 1 { 623 if depth < MB_LOOPDEPTH_MAX { mark[depth] = 1 } 624 marked = marked + 1 625 pending = 0 626 } 627 i = i + 1 628 } else { 629 if c == MB_BRACE_CLOSE { 630 if depth < MB_LOOPDEPTH_MAX { 631 if mark[depth] == 1 { mark[depth] = 0; marked = marked - 1 } 632 } 633 if depth > 0 { depth = depth - 1 } 634 i = i + 1 635 } else { 636 if mb_while_at(buf, i, le) == 1 { pending = 1; i = i + 5 } else { 637 if mb_starts(buf, i, le, "sys_mmap(" as *u8) == 1 { 638 if marked > 0 { hits = hits + 1 } 639 i = i + 9 640 } else { i = i + 1 } 641 } 642 } 643 } 644 } 645 } 646 } 647 } 648 ls = le + 1 649 } 650 sys_munmap(mark, MB_LOOPDEPTH_MAX * MB_WORD) 651 return hits 652} 653 654// 1 iff buf[s,e) ends with needle. 655func mb_ends_at(buf: *u8, s: i64, e: i64, needle: *u8) -> i64 { 656 var nl: i64 = 0 657 while needle[nl] != (0 as u8) { nl = nl + 1 } 658 if e - nl < s { return 0 } 659 var k: i64 = 0 660 while k < nl { 661 if buf[e - nl + k] != needle[k] { return 0 } 662 k = k + 1 663 } 664 return 1 665} 666 667// 1 iff the function NAME -- the text between "func " and its first '(' -- ends in one of the 668// suffixes this organ's own header names as the ownership-via-struct-field FALSE-POSITIVE class 669// (*_init / *_ctx_setup / *_new). SUFFIX-ANCHORED AT THE '(' ON PURPOSE: a plain substring search 670// would also match a function merely MENTIONING _new in a parameter name, and over-exempting is 671// strictly worse than over-reporting here -- it hides a real leak behind a plausible label. This 672// only ever moves a finding into a separate bucket; nothing is dropped. 673func mb_is_initclass(buf: *u8, s: i64, e: i64) -> i64 { 674 var p: i64 = s 675 var lp: i64 = 0 - 1 676 while p < e { 677 if buf[p] == (40 as u8) { lp = p; p = e } else { p = p + 1 } 678 } 679 if lp < 0 { return 0 } 680 if mb_ends_at(buf, s, lp, "_init" as *u8) == 1 { return 1 } 681 if mb_ends_at(buf, s, lp, "_ctx_setup" as *u8) == 1 { return 1 } 682 if mb_ends_at(buf, s, lp, "_new" as *u8) == 1 { return 1 } 683 return 0 684} 685 686// scan one file buffer for unbalanced functions; accumulates into st. 687// mode 0 = LEGACY (print every finding, no classification -- byte-identical to the pre-2026-08-06 688// behaviour, so `scan` and `file` remain the baseline oracle this organ is measured against) 689// mode 1 = DEEP (silent, and the initializer class is diverted out of the convictable headline) 690func mb_scan_buf(path: *u8, buf: *u8, n: i64, st: *i64, mode: i64) -> i64 { 691 // Computed ONCE per file, not per function: does this source run a server loop? A file that calls 692 // sys_listen or sys_accept keeps calling its handlers for the life of the process, so an unbalanced 693 // per-call allocation in it repeats without bound even with no loop in sight. 694 // ★★★★★★A LEAK IN A FORKED CHILD DIES WITH THE CHILD, SO A FORKING SERVER IS THE SAFEST CASE 695 // FOR THIS QUESTION, NOT THE MOST DANGEROUS. The first cut of this axis counted every accepting 696 // file as hot and was BACKWARDS for the estate's dominant server shape. MEASURED on 697 // nx_gallery_serve: it reported 35 hot handlers, and the accept loop is 698 // let pid: i64 = sys_fork() 699 // if pid == 0 { ...handle... sys_exit(0) } 700 // -- every handler runs in a short-lived child the kernel reclaims at exit. All 35 were FALSE. 701 // The daemon's own comments show its author already designs for this ("build the indexes in a 702 // CHILD so its big reads don't persist in the long-lived parent VA"). 703 // ⚠STATED IMPRECISION, and it drifts REASSURING, which is the direction this ruler must never 704 // drift -- so it is named loudly rather than buried: the fork evidence is FILE-level, so a server 705 // that forks only at BOOT and serves requests in the parent is wrongly excluded here. Separating 706 // those needs call-graph reachability (is this handler reached from the parent's accept path?), 707 // which is a real rung, not a tweak. Until then the two server kinds are counted SEPARATELY and 708 // printed, so the excluded population is visible instead of silently absorbed. 709 var is_server: i64 = 0 710 if mb_count_range(buf, 0, n, "sys_listen(" as *u8) > 0 { is_server = 1 } 711 if mb_count_range(buf, 0, n, "sys_accept(" as *u8) > 0 { is_server = 1 } 712 var forks: i64 = 0 713 if mb_count_range(buf, 0, n, "sys_fork(" as *u8) > 0 { forks = 1 } 714 var srv_hot: i64 = 0 715 if is_server == 1 { if forks == 0 { srv_hot = 1 } } 716 var ls: i64 = 0 717 while ls < n { 718 let le: i64 = mb_line_end(buf, ls, n) 719 if mb_starts(buf, ls, le, "func " as *u8) == 1 { 720 var fe: i64 = le + 1 721 var scanning: i64 = 1 722 while scanning == 1 { 723 if fe >= n { scanning = 0 } else { 724 let xe: i64 = mb_line_end(buf, fe, n) 725 if mb_starts(buf, fe, xe, "func " as *u8) == 1 { scanning = 0 } else { fe = xe + 1 } 726 } 727 } 728 st[MB_S_FUNCS] = st[MB_S_FUNCS] + 1 729 let na: i64 = mb_count_range(buf, ls, fe, "sys_mmap(" as *u8) 730 let nf: i64 = mb_count_range(buf, ls, fe, "sys_munmap(" as *u8) 731 if na > nf { 732 if mb_owns(buf, ls, le) == 0 { 733 if mb_starts(buf, ls, le, "func main(" as *u8) == 1 { 734 st[MB_S_MAIN] = st[MB_S_MAIN] + 1 735 } else { 736 // CLASSIFY, NEVER SILENTLY EXCLUDE. In mode 1 an initializer-class finding 737 // goes to its own counter instead of the convictable one; in mode 0 nothing 738 // moves, so the legacy verdict is unchanged. 739 var icls: i64 = 0 740 if mode >= 1 { icls = mb_is_initclass(buf, ls, le) } 741 if icls == 1 { 742 st[MB_S_INIT] = st[MB_S_INIT] + 1 743 st[MB_S_INITSITES] = st[MB_S_INITSITES] + na - nf 744 } else { 745 st[MB_S_BAD] = st[MB_S_BAD] + 1 746 st[MB_S_SITES] = st[MB_S_SITES] + na - nf 747 } 748 // SHAPE CLASSIFICATION (mode 2). Deliberately three coarse buckets tested in 749 // priority order, not a taxonomy: ccz_cat_num present means the function already 750 // calls the correct zero-alloc primitive and only hand-rolled the freeing shim 751 // around it, so nxi_fd is a literal drop-in. An open-coded %10 loop is the same 752 // OUTPUT reached the long way -- migratable, but it must be PROVEN byte-equal 753 // per function rather than assumed. Everything else is a buffer with a real 754 // lifetime and is NOT codemod work. 755 // ONLY classify what actually landed in the CONVICTABLE bucket. The first cut 756 // of this ran for every convicted function including the init-class ones, which 757 // had already been diverted to their own counter -- so the buckets summed to 758 // BAD + INIT and overcounted by exactly 173. The K2 partition check caught it; 759 // without that check it would have been reported as fact. 760 // ★COMPOSE THE WEIGHER, DO NOT RE-PARSE SIZES. Snapshot the three size counters, 761 // run the ONE size parser over this function's range, read the delta back: that 762 // is this function's own sites, measured by the same code that produces the 763 // corpus weights, so a second size parser can never drift from the first. 764 // Runs for EVERY convicted function, not just mode 2, because `deep` -- the verb 765 // that answers population questions -- is mode 1 and would otherwise have no 766 // size information at all. 767 if icls == 0 { 768 let a0: i64 = st[MB_S_W_ARENA] 769 let p0: i64 = st[MB_S_W_PAGE] 770 let u0: i64 = st[MB_S_W_UNK] 771 mb_weigh_range(buf, ls, fe, st) 772 // ★★★★★★A RULER THAT CANNOT TELL WHICH SITE IS UNMATCHED WILL KEEP CONVICTING 773 // CODE THAT IS ALREADY FIXED. Unbalancedness is per-FUNCTION (na > nf) but the 774 // weigher sees EVERY site, so a function that correctly frees its 2 KiB page 775 // buffer and still holds a 64-byte arena scratch kept reporting PAGE-BACKED -- 776 // MEASURED on nx_estate right after fixing it. Left alone, every function fixed 777 // by this campaign stays RED forever and the ratchet can never reach zero, which 778 // is the permanently-red pathology arriving through a different door. 779 // The sound test with the data available: if the count of page-backed-or-unknown 780 // sites is covered by the munmap count, then every expensive allocation is 781 // plausibly freed and whatever remains unmatched must be arena -- where 782 // sys_munmap is a documented no-op and there is nothing to fix. 783 // ⚠Still per-function, not per-site: it says the pages COULD all be covered, not 784 // that they are the ones that were. Wrong in the direction of exonerating, so it 785 // is bounded by the same call that makes it useful -- and unknown sizes count 786 // AGAINST exoneration, which keeps the reassuring direction expensive. 787 let dpages: i64 = (st[MB_S_W_PAGE] - p0) + (st[MB_S_W_UNK] - u0) 788 var arena_only: i64 = 0 789 if dpages <= nf { arena_only = 1 } 790 if arena_only == 1 { 791 st[MB_S_ARENAONLY] = st[MB_S_ARENAONLY] + 1 792 st[MB_S_ARENAONLYSITES] = st[MB_S_ARENAONLYSITES] + na - nf 793 } else { 794 // PAGE-BACKED. Only now is the loop question worth asking: a page 795 // allocation in a one-shot helper costs one page until exit, the same 796 // code in a while body is the documented SEGFAULT. 797 var in_loop: i64 = 0 798 if mb_loop_allocs(buf, ls, fe) > 0 { in_loop = 1 } 799 if in_loop == 1 { st[MB_S_HOT_LOOP] = st[MB_S_HOT_LOOP] + 1 } 800 if srv_hot == 1 { st[MB_S_HOT_SRV] = st[MB_S_HOT_SRV] + 1 } 801 if is_server == 1 { if forks == 1 { st[MB_S_SRV_FORK] = st[MB_S_SRV_FORK] + 1 } } 802 var hot: i64 = 0 803 if in_loop == 1 { hot = 1 } 804 if srv_hot == 1 { hot = 1 } 805 if hot == 1 { 806 st[MB_S_HOT] = st[MB_S_HOT] + 1 807 // ★★★★★A COUNT WITHOUT A WORKLIST IS NOT ACTIONABLE. `deep` could rank 808 // this class and could not name it, and `scan` names everything and 809 // truncates. mode 3 prints ONLY these, so the stream is bounded by 810 // the class itself rather than by the corpus. 811 if mode == 3 { 812 // ★NAME THE REASON ON THE ROW. The two reasons need different 813 // judgement -- a loop hit is fixed by hoisting or freeing inside 814 // the function, a non-forking-server hit is a per-request leak in 815 // a long-lived parent and is a production severity. A worklist 816 // that prints only HOT makes every reader re-derive which. 817 if in_loop == 1 { 818 if srv_hot == 1 { gv_puts("HOT[loop+srv] " as *u8) } else { gv_puts("HOT[loop] " as *u8) } 819 } else { gv_puts("HOT[srv] " as *u8) } 820 gv_puts(path) 821 gv_puts(" " as *u8) 822 mb_put_range(buf, ls, le) 823 gv_puts("\n" as *u8) 824 } 825 } 826 } 827 } 828 var do_cls: i64 = 0 829 if mode == 2 { if icls == 0 { do_cls = 1 } } 830 if do_cls == 1 { 831 let nshim: i64 = mb_count_range(buf, ls, fe, "ccz_cat_num(" as *u8) 832 var ndig: i64 = mb_count_range(buf, ls, fe, "%10" as *u8) 833 if ndig == 0 { ndig = mb_count_range(buf, ls, fe, "% 10" as *u8) } 834 if nshim > 0 { 835 st[MB_S_C_SHIM] = st[MB_S_C_SHIM] + 1 836 st[MB_S_C_SHIMS] = st[MB_S_C_SHIMS] + na - nf 837 } else { 838 if ndig > 0 { 839 st[MB_S_C_INLINE] = st[MB_S_C_INLINE] + 1 840 st[MB_S_C_INLINES] = st[MB_S_C_INLINES] + na - nf 841 } else { 842 st[MB_S_C_OTHER] = st[MB_S_C_OTHER] + 1 843 st[MB_S_C_OTHERS] = st[MB_S_C_OTHERS] + na - nf 844 } 845 } 846 } 847 if mode == 0 { 848 gv_puts(" UNBALANCED " as *u8) 849 gv_puts(path) 850 gv_puts("\n " as *u8) 851 mb_put_range(buf, ls, le) 852 gv_puts("\n mmap=" as *u8) 853 gv_num(na) 854 gv_puts(" munmap=" as *u8) 855 gv_num(nf) 856 gv_puts(" leaked_sites=" as *u8) 857 gv_num(na - nf) 858 gv_puts("\n" as *u8) 859 } 860 } 861 } 862 } 863 ls = fe 864 } else { 865 ls = le + 1 866 } 867 } 868 return 0 869} 870 871// ITERATIVE BFS over root AND EVERY SUBDIRECTORY (2026-08-06). 872// THE OLD WALK WAS ONE LEVEL DEEP. It tested `if ty != MB_DIR_TYPE` and then simply DROPPED every 873// directory entry -- not scanned, and not counted as skipped either -- so an entire subtree could be 874// absent from a run that still printed coverage_complete=1. MEASURED: buildroot/runtime/_hdl_build 875// holds 7,918 files and hundreds of unbalanced functions, none of which have ever appeared in the 876// corpus total this organ has reported since 2026-07-31. A scanner that cannot see a directory must 877// not be able to claim completeness over it. 878// 879// A QUEUE, NOT RECURSION, on purpose: this organ's job is honest coverage, and a recursion depth 880// limit that trips is far harder to detect and report than a queue slot that refuses. Overflow is 881// counted and drops coverage_complete. 882func mb_walk(root: *u8, st: *i64, mode: i64) -> i64 { 883 let q: *u8 = sys_mmap(MB_QCAP * MB_PATHCAP) 884 let dbuf: *u8 = sys_mmap(MB_DIRBUF) 885 let fbuf: *u8 = sys_mmap(MB_FILECAP) 886 let path: *u8 = sys_mmap(MB_PATHCAP) 887 var head: i64 = 0 888 var tail: i64 = 0 889 var si: i64 = 0 890 while root[si] != (0 as u8) { q[si] = root[si]; si = si + 1 } 891 q[si] = 0 as u8 892 tail = 1 893 while head < tail { 894 let dcur: *u8 = ((q as i64) + head * MB_PATHCAP) as *u8 895 head = head + 1 896 let fd: i64 = sys_openat_rd(dcur) 897 if fd < 0 { st[MB_S_SKIP] = st[MB_S_SKIP] + 1 } else { 898 st[MB_S_DIRS] = st[MB_S_DIRS] + 1 899 var go: i64 = 1 900 while go == 1 { 901 let nr: i64 = sys_getdents64(fd, dbuf, MB_DIRBUF) 902 if nr <= 0 { go = 0 } else { 903 var off: i64 = 0 904 while off < nr { 905 let rec: *u8 = ((dbuf as i64) + off) as *u8 906 let ty: i64 = dirent_type(rec) 907 let nm: *u8 = dirent_name(rec) 908 let nmn: i64 = mb_len(nm) 909 if ty == MB_DIR_TYPE { 910 if mb_streq(nm, MB_DOT_ENT) == 0 { 911 if mb_streq(nm, MB_DOTDOT_ENT) == 0 { 912 if tail >= MB_QCAP { st[MB_S_QOVF] = st[MB_S_QOVF] + 1 } else { 913 let slot: *u8 = ((q as i64) + tail * MB_PATHCAP) as *u8 914 mb_join(dcur, nm, slot) 915 tail = tail + 1 916 } 917 } 918 } 919 } else { 920 if mb_is_nx(nm, nmn) == 1 { 921 mb_join(dcur, nm, path) 922 let bn: i64 = mb_slurp(path, fbuf, MB_FILECAP) 923 if bn <= 0 { 924 st[MB_S_SKIP] = st[MB_S_SKIP] + 1 925 } else { 926 st[MB_S_FILES] = st[MB_S_FILES] + 1 927 // mb_slurp STOPS AT THE CAP, it does not fail. A full buffer means 928 // the tail of the file was never read and every function past it is 929 // invisible -- silently, while the old summary still said complete. 930 // Counting it and dropping coverage is the L011 honesty this organ 931 // claims in its own header. 932 if bn >= MB_FILECAP { st[MB_S_TRUNC] = st[MB_S_TRUNC] + 1 } 933 // Rebuild the const table for THIS file before scanning it, so a 934 // sys_mmap(SOME_CONST) can be weighed instead of shrugged at. 935 mbc_scan(fbuf, bn) 936 mb_scan_buf(path, fbuf, bn, st, mode) 937 } 938 } 939 } 940 off = off + dirent_reclen(rec) 941 } 942 } 943 } 944 sys_close(fd) 945 } 946 } 947 sys_munmap(q, MB_QCAP * MB_PATHCAP) 948 sys_munmap(dbuf, MB_DIRBUF) 949 sys_munmap(fbuf, MB_FILECAP) 950 sys_munmap(path, MB_PATHCAP) 951 return 0 952} 953 954func main(argc: i64, argv: *i64) -> i64 { 955 var dir: *u8 = "buildroot/runtime" as *u8 956 if argc >= 3 { dir = argv[2] as *u8 } 957 958 // SINGLE-FILE MODE: `nx_mmapbal file <path>`. Added 2026-07-31 because a TOTAL-COUNT delta over a 959 // SHARED tree is NOT attributable to one lane. Measured: between two scans of buildroot/runtime, 960 // sibling seats added 7 files / 84 funcs / 13 unbalanced funcs, masking this lane's -3 fix under a 961 // +16 drift -- and directory (getdents) order is not stable across runs either, so absence at a byte 962 // offset proves nothing. Per-file scanning is the ONLY sound way to verify one fix on a live corpus. 963 if argc >= 3 { 964 if mb_streq(argv[1] as *u8, "file" as *u8) == 1 { 965 let one: *u8 = argv[2] as *u8 966 let fb1: *u8 = sys_mmap(MB_FILECAP) 967 let st1: *i64 = sys_mmap(MB_ST_SLOTS * MB_WORD) as *i64 968 var z1: i64 = 0 969 while z1 < MB_ST_SLOTS { st1[z1] = 0; z1 = z1 + 1 } 970 gv_puts("=== NX-MMAPBAL -- single file " as *u8) 971 gv_puts(one) 972 gv_puts(" ===\n" as *u8) 973 let n1: i64 = mb_slurp(one, fb1, MB_FILECAP) 974 if n1 <= 0 { 975 gv_puts("verdict=RED rule=file-unreadable\n" as *u8) 976 sys_exit(1) 977 return 1 978 } 979 st1[MB_S_FILES] = 1 980 // Build the const table for THIS file first, exactly as mb_walk does before every scan. 981 // ★A PRECONDITION BELONGS AT EVERY ENTRY POINT, NOT AT THE ONE YOU HAPPENED TO TEST. 982 mbc_scan(fb1, n1) 983 mb_scan_buf(one, fb1, n1, st1, 0) 984 gv_puts("\nfiles_scanned=1 funcs_scanned=" as *u8) 985 gv_num(st1[MB_S_FUNCS]) 986 gv_puts("\nunbalanced_funcs=" as *u8) 987 gv_num(st1[MB_S_BAD]) 988 gv_puts(" leaked_sites=" as *u8) 989 gv_num(st1[MB_S_SITES]) 990 gv_puts(" main_scope_informational=" as *u8) 991 gv_num(st1[MB_S_MAIN]) 992 gv_puts("\n of the unbalanced: arena_only=" as *u8) 993 gv_num(st1[MB_S_ARENAONLY]) 994 gv_puts(" (every unmatched request <= " as *u8) 995 gv_num(MB_ARENA_MAX) 996 gv_puts(" B, where sys_munmap is a documented NO-OP) page_backed_or_unknown=" as *u8) 997 gv_num(st1[MB_S_BAD] - st1[MB_S_ARENAONLY]) 998 gv_puts(" of which REPEATEDLY EXECUTED=" as *u8) 999 gv_num(st1[MB_S_HOT]) 1000 gv_puts(" (in_a_loop=" as *u8) 1001 gv_num(st1[MB_S_HOT_LOOP]) 1002 gv_puts(" in_a_NON_FORKING_server=" as *u8) 1003 gv_num(st1[MB_S_HOT_SRV]) 1004 gv_puts(" | excluded_forking_server=" as *u8) 1005 gv_num(st1[MB_S_SRV_FORK]) 1006 gv_puts(") <- the fixable class, ranked" as *u8) 1007 gv_puts("\ncoverage_complete=1\n\n" as *u8) 1008 let c1: *i64 = gv_ctr() 1009 gv_check("F1 no unbalanced non-owning function in this file", st1[MB_S_BAD] == 0, c1) 1010 let r1: i64 = gv_verdict("MMAPBAL-FILE", c1, "every mmap matched by a munmap or handed to the caller") 1011 sys_exit(r1) 1012 return r1 1013 } 1014 } 1015 1016 // DEEP VERB (2026-08-06): `nx_mmapbal deep [dir]` -- RECURSIVE, SUMMARY-ONLY, CLASSIFIED. 1017 // Summary-only is not a convenience. The per-finding stream is ~6.7k findings and the summary 1018 // block sits BELOW it, so every transport this organ is called through (MCP capture cap 163840B, 1019 // nx_fs 1MiB line scan) truncates before reaching it -- the organ whose entire job is to produce a 1020 // number could not deliver that number through any channel it is actually invoked by. Quiet mode 1021 // puts the number in front of the flood instead of behind it. 1022 // HOT VERB (2026-08-15): ENUMERATE the page-backed-in-a-loop class -- the ranked worklist `deep` 1023 // can count but not name. Bounded by the class (hundreds), not by the corpus (tens of thousands), 1024 // which is why this can stream where `scan` cannot. The summary still prints last, so a caller who 1025 // gets a truncated read can tell: no trailing HOT-TOTAL line means the list is PARTIAL. 1026 if argc >= 2 { 1027 if mb_streq(argv[1] as *u8, "hot" as *u8) == 1 { 1028 let sth: *i64 = sys_mmap(MB_ST_SLOTS * MB_WORD) as *i64 1029 var zh: i64 = 0 1030 while zh < MB_ST_SLOTS { sth[zh] = 0; zh = zh + 1 } 1031 gv_puts("=== NX-MMAPBAL hot -- page-backed AND allocating inside a loop -- over " as *u8) 1032 gv_puts(dir) 1033 gv_puts(" ===\n" as *u8) 1034 mb_walk(dir, sth, 3) 1035 gv_puts("HOT-TOTAL " as *u8) 1036 gv_num(sth[MB_S_HOT]) 1037 gv_puts(" of page_backed " as *u8) 1038 gv_num(sth[MB_S_BAD] - sth[MB_S_ARENAONLY]) 1039 gv_puts(" of convictable " as *u8) 1040 gv_num(sth[MB_S_BAD]) 1041 gv_puts(" files=" as *u8) 1042 gv_num(sth[MB_S_FILES]) 1043 gv_puts(" coverage_complete=" as *u8) 1044 var covh: i64 = 1 1045 if sth[MB_S_SKIP] != 0 { covh = 0 } 1046 if sth[MB_S_TRUNC] != 0 { covh = 0 } 1047 if sth[MB_S_QOVF] != 0 { covh = 0 } 1048 gv_num(covh) 1049 gv_puts("\n" as *u8) 1050 return 0 1051 } 1052 if mb_streq(argv[1] as *u8, "deep" as *u8) == 1 { 1053 let std: *i64 = sys_mmap(MB_ST_SLOTS * MB_WORD) as *i64 1054 var zd: i64 = 0 1055 while zd < MB_ST_SLOTS { std[zd] = 0; zd = zd + 1 } 1056 gv_puts("=== NX-MMAPBAL deep -- recursive, truncation-honest, classified -- over " as *u8) 1057 gv_puts(dir) 1058 gv_puts(" ===\n" as *u8) 1059 mb_walk(dir, std, 1) 1060 gv_puts("dirs_walked=" as *u8) 1061 gv_num(std[MB_S_DIRS]) 1062 gv_puts(" files_scanned=" as *u8) 1063 gv_num(std[MB_S_FILES]) 1064 gv_puts(" files_skipped=" as *u8) 1065 gv_num(std[MB_S_SKIP]) 1066 gv_puts(" files_truncated=" as *u8) 1067 gv_num(std[MB_S_TRUNC]) 1068 gv_puts(" queue_overflows=" as *u8) 1069 gv_num(std[MB_S_QOVF]) 1070 gv_puts("\nfuncs_scanned=" as *u8) 1071 gv_num(std[MB_S_FUNCS]) 1072 gv_puts("\nCONVICTABLE unbalanced_funcs=" as *u8) 1073 gv_num(std[MB_S_BAD]) 1074 gv_puts(" leaked_sites=" as *u8) 1075 gv_num(std[MB_S_SITES]) 1076 gv_puts("\n ARENA-ONLY unbalanced_funcs=" as *u8) 1077 gv_num(std[MB_S_ARENAONLY]) 1078 gv_puts(" leaked_sites=" as *u8) 1079 gv_num(std[MB_S_ARENAONLYSITES]) 1080 gv_puts("\n (every unmatched request <= " as *u8) 1081 gv_num(MB_ARENA_MAX) 1082 gv_puts(" B, so it came from the bump arena and sys_munmap\n" as *u8) 1083 gv_puts(" RETURNS 0 WITHOUT FREEING -- adding the call is provably a no-op, not a fix.\n" as *u8) 1084 gv_puts(" These are a subset of CONVICTABLE above, not a separate population.)\n" as *u8) 1085 gv_puts(" PAGE-BACKED unbalanced_funcs=" as *u8) 1086 gv_num(std[MB_S_BAD] - std[MB_S_ARENAONLY]) 1087 gv_puts(" of which REPEATEDLY EXECUTED=" as *u8) 1088 gv_num(std[MB_S_HOT]) 1089 gv_puts(" <- RANK THE WORKLIST BY THIS\n" as *u8) 1090 gv_puts(" reasons (they OVERLAP, so they are not summed): in_a_loop=" as *u8) 1091 gv_num(std[MB_S_HOT_LOOP]) 1092 gv_puts(" in_a_NON_FORKING_server=" as *u8) 1093 gv_num(std[MB_S_HOT_SRV]) 1094 gv_puts(" <- A FLOOR, NOT A VALUE\n" as *u8) 1095 gv_puts(" (the accept loop and its handlers are in DIFFERENT files in the estate's larger\n" as *u8) 1096 gv_puts(" daemons -- MEASURED: nx_mgmt_api.nx holds 42 page-backed handlers and scores\n" as *u8) 1097 gv_puts(" ZERO here because sys_accept lives in another file. A FILE-LEVEL AXIS CANNOT\n" as *u8) 1098 gv_puts(" SEE A SPLIT HANDLER/LOOP, so this undercounts by an unknown amount and only\n" as *u8) 1099 gv_puts(" call-graph reachability can close it.)" as *u8) 1100 gv_puts("\n EXCLUDED AS SAFE: in_a_FORKING_server=" as *u8) 1101 gv_num(std[MB_S_SRV_FORK]) 1102 gv_puts(" -- the handler runs in a child that exits,\n" as *u8) 1103 gv_puts(" so the kernel reclaims it. Counting these was BACKWARDS: for THIS question a\n" as *u8) 1104 gv_puts(" forking server is the SAFEST shape. ⚠Fork evidence is FILE-level, so a server that\n" as *u8) 1105 gv_puts(" forks only at BOOT and serves in the parent is wrongly excluded -- a REASSURING\n" as *u8) 1106 gv_puts(" -direction miss, named because closing it needs call-graph reachability.\n" as *u8) 1107 gv_puts(" (a page allocation in a one-shot helper costs one page until the process\n" as *u8) 1108 gv_puts(" exits; the same code executed forever is the documented SEGFAULT. Two ways to\n" as *u8) 1109 gv_puts(" be executed forever -- a while body, or a process that listens and accepts --\n" as *u8) 1110 gv_puts(" and a lexical scan sees only the first. Per-FUNCTION, so it proves the function\n" as *u8) 1111 gv_puts(" is unbalanced AND repeatedly executed, not that the unmatched site is the one.)\n" as *u8) 1112 gv_puts(" THE FIXABLE CLASS: a full page and a kernel VMA per call,\n" as *u8) 1113 gv_puts(" which is the RLIMIT_AS/vm.max_map_count exhaustion this scanner exists for.\n" as *u8) 1114 gv_puts(" (includes sites whose size this organ could not resolve -- unknown is\n" as *u8) 1115 gv_puts(" never counted as arena, because that is the reassuring direction.)\n" as *u8) 1116 gv_puts("INIT-UNPROVEN unbalanced_funcs=" as *u8) 1117 gv_num(std[MB_S_INIT]) 1118 gv_puts(" leaked_sites=" as *u8) 1119 gv_num(std[MB_S_INITSITES]) 1120 gv_puts("\n (*_init/*_ctx_setup/*_new: ownership via an out-param struct field is\n" as *u8) 1121 gv_puts(" invisible to mb_owns, so these are UNPROVEN, not exonerated -- read\n" as *u8) 1122 gv_puts(" the callee before convicting or clearing any of them.)\n" as *u8) 1123 gv_puts("main_scope_informational=" as *u8) 1124 gv_num(std[MB_S_MAIN]) 1125 gv_puts("\ncoverage_complete=" as *u8) 1126 var cov: i64 = 1 1127 if std[MB_S_SKIP] != 0 { cov = 0 } 1128 if std[MB_S_TRUNC] != 0 { cov = 0 } 1129 if std[MB_S_QOVF] != 0 { cov = 0 } 1130 if cov == 1 { gv_puts("1" as *u8) } else { gv_puts("0 -- every count above is a FLOOR; see files_skipped/files_truncated/queue_overflows" as *u8) } 1131 gv_puts("\n\n" as *u8) 1132 let ctd: *i64 = gv_ctr() 1133 gv_check("D1 corpus readable (at least one .nx scanned)", std[MB_S_FILES] > 0, ctd) 1134 1135 // D2 IS NOW A RATCHET ON THE FIXABLE CLASS. See MB_RATCHET_PATH for why the old 1136 // `== 0` tooth could never pass and what that cost. 1137 let pb: i64 = std[MB_S_BAD] - std[MB_S_ARENAONLY] 1138 let floor: i64 = mb_read_floor() 1139 if floor < 0 { 1140 gv_puts(" ratchet floor ABSENT at " as *u8); gv_puts(MB_RATCHET_PATH) 1141 gv_puts(" -- seeding it at " as *u8); gv_num(pb) 1142 gv_puts("\n This run therefore has NO baseline to judge against and does not claim one.\n" as *u8) 1143 mb_write_floor(pb) 1144 gv_need("D2 page-backed leaks are at or below the recorded floor", 0, ctd) 1145 } else { 1146 gv_puts(" ratchet floor=" as *u8); gv_num(floor) 1147 gv_puts(" page_backed=" as *u8); gv_num(pb) 1148 if pb < floor { 1149 gv_puts(" TIGHTENING -> " as *u8); gv_num(pb) 1150 gv_puts("\n" as *u8) 1151 mb_write_floor(pb) 1152 } else { 1153 if pb > floor { 1154 gv_puts(" REGRESSION +" as *u8); gv_num(pb - floor) 1155 gv_puts(" page-backed leak(s) appeared since the floor was set.\n" as *u8) 1156 gv_puts(" ⚠This is a COUNT on a shared tree: it proves a leak appeared, NOT whose.\n" as *u8) 1157 } else { gv_puts(" HOLDING at the floor\n" as *u8) } 1158 } 1159 gv_check("D2 page-backed leaks are at or below the recorded floor", pb <= floor, ctd) 1160 } 1161 gv_check("D3 coverage complete (nothing skipped, truncated or unwalked)", cov == 1, ctd) 1162 let rcd: i64 = gv_verdict("MMAPBAL-DEEP", ctd, "recursive, truncation-honest, initializer class separated from the headline") 1163 sys_exit(rcd) 1164 return rcd 1165 } 1166 } 1167 1168 // CLASSES VERB (2026-08-06): `nx_mmapbal classes [dir]` -- recursive, summary-only, bucketed by 1169 // SHAPE. A five-figure site total answers "how bad" and says nothing about "what to build". This 1170 // ranks the remediation: SHIM is a literal one-line drop-in onto nx_itoa_lib, INLINE is the same 1171 // output reached the long way and needs a per-function byte-equality proof before it can be 1172 // rewritten, OTHER is a buffer with a genuine lifetime and is not codemod work at all. 1173 if argc >= 2 { 1174 if mb_streq(argv[1] as *u8, "classes" as *u8) == 1 { 1175 let stc: *i64 = sys_mmap(MB_ST_SLOTS * MB_WORD) as *i64 1176 var zc: i64 = 0 1177 while zc < MB_ST_SLOTS { stc[zc] = 0; zc = zc + 1 } 1178 gv_puts("=== NX-MMAPBAL classes -- convictable sites bucketed by remediation shape -- over " as *u8) 1179 gv_puts(dir) 1180 gv_puts(" ===\n" as *u8) 1181 mb_walk(dir, stc, 2) 1182 gv_puts("dirs_walked=" as *u8) 1183 gv_num(stc[MB_S_DIRS]) 1184 gv_puts(" files_scanned=" as *u8) 1185 gv_num(stc[MB_S_FILES]) 1186 gv_puts(" files_skipped=" as *u8) 1187 gv_num(stc[MB_S_SKIP]) 1188 gv_puts(" files_truncated=" as *u8) 1189 gv_num(stc[MB_S_TRUNC]) 1190 gv_puts("\nCONVICTABLE funcs=" as *u8) 1191 gv_num(stc[MB_S_BAD]) 1192 gv_puts(" sites=" as *u8) 1193 gv_num(stc[MB_S_SITES]) 1194 gv_puts("\n SHIM (calls ccz_cat_num, hand-rolls the freeing shim -- nxi_fd DROP-IN) funcs=" as *u8) 1195 gv_num(stc[MB_S_C_SHIM]) 1196 gv_puts(" sites=" as *u8) 1197 gv_num(stc[MB_S_C_SHIMS]) 1198 gv_puts("\n INLINE (open-coded %10 digit loop -- same output, PROVE byte-equal per func) funcs=" as *u8) 1199 gv_num(stc[MB_S_C_INLINE]) 1200 gv_puts(" sites=" as *u8) 1201 gv_num(stc[MB_S_C_INLINES]) 1202 gv_puts("\n OTHER (buffer with a real lifetime -- NOT codemod work) funcs=" as *u8) 1203 gv_num(stc[MB_S_C_OTHER]) 1204 gv_puts(" sites=" as *u8) 1205 gv_num(stc[MB_S_C_OTHERS]) 1206 gv_puts("\nWEIGHED BY ALLOCATION SIZE (a site is no longer a page -- arena threshold " as *u8) 1207 gv_num(MB_ARENA_MAX) 1208 gv_puts("B)\n DENOMINATOR -- THIS BLOCK COUNTS A DIFFERENT POPULATION FROM THE CLASS BUCKETS ABOVE, and until\ 1209 2026-08-07 both were printed under the same bare word sites=. The class buckets count the LEAKED\ 1210 EXCESS (na - nf) and sum to CONVICTABLE sites. These weight buckets count EVERY non-comment\ 1211 sys_mmap( site inside a convicted function (na), so their total is LARGER and their denominator is\ 1212 mmap_sites_scanned below -- NEVER the leaked-site count. The gap is exactly the munmaps those\ 1213 functions DO perform, so a nonzero value means PARTIAL-FREE, not total-leak.\ 1214 A READER WHO UNIFIES TWO ADJACENT COUNTERS SHARING ONE LABEL HAS BEEN MISLED BY THE REPORT.\ 1215 mmap_sites_scanned=" as *u8) 1216 gv_num(stc[MB_S_W_TOT]) 1217 gv_puts(" munmaps_inside_convicted=" as *u8) 1218 gv_num(stc[MB_S_W_TOT] - stc[MB_S_SITES]) 1219 gv_puts("\ 1220 ARENA-SERVED (<=thr: bump-allocated ~48B, no VMA of its own) mmap_sites=" as *u8) 1221 gv_num(stc[MB_S_W_ARENA]) 1222 gv_puts("\n REAL-PAGE (>thr: still a full page AND a kernel VMA per call) mmap_sites=" as *u8) 1223 gv_num(stc[MB_S_W_PAGE]) 1224 gv_puts("\n UNKNOWN (const or expression -- never assumed cheap) mmap_sites=" as *u8) 1225 gv_num(stc[MB_S_W_UNK]) 1226 gv_puts("\nINIT-UNPROVEN funcs=" as *u8) 1227 gv_num(stc[MB_S_INIT]) 1228 gv_puts(" sites=" as *u8) 1229 gv_num(stc[MB_S_INITSITES]) 1230 gv_puts("\ncoverage_complete=" as *u8) 1231 var cvc: i64 = 1 1232 if stc[MB_S_SKIP] != 0 { cvc = 0 } 1233 if stc[MB_S_TRUNC] != 0 { cvc = 0 } 1234 if stc[MB_S_QOVF] != 0 { cvc = 0 } 1235 if cvc == 1 { gv_puts("1" as *u8) } else { gv_puts("0 -- counts are FLOORS" as *u8) } 1236 gv_puts("\n\n" as *u8) 1237 let ctc: *i64 = gv_ctr() 1238 gv_check("K1 corpus readable", stc[MB_S_FILES] > 0, ctc) 1239 gv_check("K2 buckets sum to the convictable total (a partition is a claim -- check it)", stc[MB_S_C_SHIM] + stc[MB_S_C_INLINE] + stc[MB_S_C_OTHER] == stc[MB_S_BAD], ctc) 1240 gv_check("K4 weight buckets sum to the sites weighed (same partition discipline as K2)", stc[MB_S_W_ARENA] + stc[MB_S_W_PAGE] + stc[MB_S_W_UNK] == stc[MB_S_W_TOT], ctc) 1241 // K5 BINDS THE TWO POPULATIONS THAT K2 AND K4 EACH CHECK ONLY IN ISOLATION. Both walks cover 1242 // the SAME convicted non-init functions and BOTH skip comments (mb_count_range and 1243 // mb_weigh_range carry the identical mb_is_comment guard), so W_TOT = sum(na) while 1244 // SITES = sum(na - nf) with nf >= 0 -- therefore W_TOT >= SITES is STRUCTURAL, not a 1245 // calibrated threshold. It can invert only if the two walks diverge in SUBJECT, which is 1246 // precisely the confusion that let these two counters be printed as if comparable. 1247 // TWO PARTITIONS THAT ARE EACH INTERNALLY CONSISTENT CAN STILL DESCRIBE DIFFERENT 1248 // POPULATIONS -- ONLY A TOOTH ACROSS THEM CAN CATCH THAT, AND NEITHER K2 NOR K4 COULD. 1249 gv_check("K5 all-mmap-sites >= leaked-excess (proves the two walks still share a subject)", stc[MB_S_W_TOT] >= stc[MB_S_SITES], ctc) 1250 gv_check("K3 coverage complete", cvc == 1, ctc) 1251 let rcc: i64 = gv_verdict("MMAPBAL-CLASSES", ctc, "convictable sites bucketed by remediation shape") 1252 sys_exit(rcc) 1253 return rcc 1254 } 1255 } 1256 1257 let fd: i64 = sys_openat_rd(dir) 1258 if fd < 0 { 1259 gv_puts("NX-MMAPBAL\nverdict=RED rule=scandir-absent dir=" as *u8) 1260 gv_puts(dir) 1261 gv_puts("\n" as *u8) 1262 sys_exit(1) 1263 return 1 1264 } 1265 1266 let dbuf: *u8 = sys_mmap(MB_DIRBUF) 1267 let fbuf: *u8 = sys_mmap(MB_FILECAP) 1268 let path: *u8 = sys_mmap(MB_PATHCAP) 1269 let st: *i64 = sys_mmap(MB_ST_SLOTS * MB_WORD) as *i64 1270 var z: i64 = 0 1271 while z < MB_ST_SLOTS { st[z] = 0; z = z + 1 } 1272 1273 gv_puts("=== NX-MMAPBAL -- static mmap/munmap balance over " as *u8) 1274 gv_puts(dir) 1275 gv_puts(" ===\n" as *u8) 1276 1277 var go: i64 = 1 1278 while go == 1 { 1279 let nr: i64 = sys_getdents64(fd, dbuf, MB_DIRBUF) 1280 if nr <= 0 { go = 0 } else { 1281 var off: i64 = 0 1282 while off < nr { 1283 let rec: *u8 = ((dbuf as i64) + off) as *u8 1284 let ty: i64 = dirent_type(rec) 1285 let nm: *u8 = dirent_name(rec) 1286 let nmn: i64 = mb_len(nm) 1287 if ty != MB_DIR_TYPE { 1288 if mb_is_nx(nm, nmn) == 1 { 1289 mb_join(dir, nm, path) 1290 let bn: i64 = mb_slurp(path, fbuf, MB_FILECAP) 1291 if bn <= 0 { 1292 st[MB_S_SKIP] = st[MB_S_SKIP] + 1 1293 } else { 1294 st[MB_S_FILES] = st[MB_S_FILES] + 1 1295 mb_scan_buf(path, fbuf, bn, st, 0) 1296 } 1297 } 1298 } 1299 off = off + dirent_reclen(rec) 1300 } 1301 } 1302 } 1303 sys_close(fd) 1304 1305 gv_puts("\nfiles_scanned=" as *u8) 1306 gv_num(st[MB_S_FILES]) 1307 gv_puts(" files_skipped=" as *u8) 1308 gv_num(st[MB_S_SKIP]) 1309 gv_puts(" funcs_scanned=" as *u8) 1310 gv_num(st[MB_S_FUNCS]) 1311 gv_puts("\nunbalanced_funcs=" as *u8) 1312 gv_num(st[MB_S_BAD]) 1313 gv_puts(" leaked_sites=" as *u8) 1314 gv_num(st[MB_S_SITES]) 1315 gv_puts(" main_scope_informational=" as *u8) 1316 gv_num(st[MB_S_MAIN]) 1317 gv_puts("\ncoverage_complete=" as *u8) 1318 if st[MB_S_SKIP] == 0 { gv_puts("1" as *u8) } else { gv_puts("0" as *u8) } 1319 gv_puts("\n\n" as *u8) 1320 1321 let ctr: *i64 = gv_ctr() 1322 gv_check("C1 corpus readable (at least one .nx scanned)", st[MB_S_FILES] > 0, ctr) 1323 gv_check("C2 no unbalanced non-owning function", st[MB_S_BAD] == 0, ctr) 1324 gv_check("C3 coverage complete (zero unreadable files)", st[MB_S_SKIP] == 0, ctr) 1325 let rc: i64 = gv_verdict("MMAPBAL", ctr, "every mmap matched by a munmap or handed to the caller") 1326 sys_exit(rc) 1327 return rc 1328}