code wiki / _hdl_build / nx_dup_source_check.nx

nx_dup_source_check.nx

buildroot/runtime/_hdl_build/nx_dup_source_check.nx

58476 B1041 linesdepth 3pulls 3 transitivereach 0 importersview sourcekind tool
docsdependenciesstructsconstsfunctions

about

nx_dup_source_check.nx v3 -- ANTI-CLOBBER cross-tree source-dup detector. 🚨v3 EXISTS BECAUSE v2 WAS AN OOM BOMB THAT TOOK THE NAS DOWN (2026-07-20). Root cause, stated plainly: v2 widened the scan from 2 hardcoded dirs to ALL discovered source trees (78 pairs) -- a good change -- but REUSED v1's `exists()` helper UNCHANGED, and that helper did `sys_mmap(4096)` PER CALL and never unmapped. v1 called it ~15k times (ONE pair) = ~60MB = survivable. v2 called it ~250k times = ~1GB LEAKED PER RUN, on a cron. Result: NAS userspace starved -- kernel still SYN-ACKs but sshd gives no banner, HTTPS returns status=0, even DSM stops answering. ★THE LESSON, so it is never repeated: CHANGING A CALLER'S FAN-OUT RE-COSTS EVERY CALLEE. A 2->78 pair change is a 17x amplifier on an O(files) helper. Reviewing only the code you WROTE is not enough -- you must re-cost the code you INHERITED AND AMPLIFIED. And note what the v2 selftest could NOT do: it was GREEN 4/4 because fixture dirs hold 2-3 files, so it tested the ALGORITHM at scale and never the RESOURCE at scale. A scale-law tooth that asserts only correctness-on-big-input is HALF a tooth; the other half is a RESOURCE bound. v3 FIX: every path buffer is allocated ONCE PER PAIR (or once per walk) and passed down, never per file. Allocation profile is now O(pairs + dirs), not O(files), and it is DECLARED in the output envelope so the property is inspectable rather than assumed. Detection semantics and the emitted " DUP basename: " line are UNCHANGED (rule 19) -- nx_favela_census counts that exact substring out of knowledge/status/dup_source.log. expect_exit:0 when clean.

dependencies 2 imports · 0 importers

nx_syscalls.nx nx_itoa_lib.nx nx_dup_source_check.nx

imports: nx_syscalls.nxnx_itoa_lib.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main dp_streq dp_atoi dp_fingerprint dp_fp_count sys_openat_rd sys_mmap sys_getdents64 dirent_type ends_nx dirent_name dirent_reclen sys_close dp_w sys_write dp_wn nxi_out nxi_fd sys_mmap ↻ ccz_cat_num sys_write ↻ sys_munmap sys_openat_rd ↻ sys_mmap ↻ sys_getdents64 ↻ dirent_type ↻ dirent_name ↻ ends_nx ↻ join_path dp_slurp sys_openat_rd ↻ sys_read sys_close ↻ dp_sum dp_mtime sys_fstatat dirent_reclen ↻ sys_close ↻ sys_exit dp_w ↻

structs

none

consts

23const K_MAGIC_131072: i64 = 131072
24const K_MAGIC_4096: i64 = 4096
28const K_CMP_CAP: i64 = 1048576
35const FP_MULT: i64 = 131
36const FP_MASK: i64 = 1099511627775
37const FP_FILECAP: i64 = 1048576
42const FP_PAGE_DEF: i64 = 300
43const FP_PAGE_MAX: i64 = 900
47const FP_STATBUF: i64 = 144
48const FP_MTIME_SLOT: i64 = 11
49const FP_TEST_MTIME: i64 = 1750000000
227const DP_NDLCAP: i64 = 512

functions

51func dp_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
56func dp_wn(v: i64) -> i64 { nxi_out(v); return 0 }
58func ends_nx(nm: *u8) -> i64
65func join_path(buf: *u8, dir: *u8, name: *u8) -> i64
75func exists(dir: *u8, name: *u8, scratch: *u8) -> i64
82func is_dot(nm: *u8) -> i64
called by 1: discover
90func str_copy(dst: *u8, src: *u8) -> i64 { var i: i64=0; while src[i]!=(0 as u8) { dst[i]=src[i]; i=i+1 } dst[i]=0 as u8; return i }
called by 1: discover
92func dp_streq(a: *u8, b: *u8) -> i64
112func dp_in_buildroot(dir: *u8) -> i64
called by 1: dp_is_probed
127func dp_probe_rank(dir: *u8) -> i64
called by 1: scan2 calls 1: dp_streq
141func dp_deadwin(ra: i64, ha: i64, rb: i64, hb: i64) -> i64
called by 2: scan2selftest
147func dp_is_probed(dir: *u8) -> i64
called by 1: scan2 calls 2: dp_in_buildrootdp_streq
158func dp_atoi(s: *u8) -> i64
called by 1: main
169func dp_sum(buf: *u8, n: i64) -> i64
179func dp_slurp(path: *u8, buf: *u8, cap: i64) -> i64
196func dp_same(pa: *u8, pb: *u8, ba: *u8, bb: *u8, cap: i64) -> i64
called by 1: scan2 calls 1: dp_slurp
230func dp_find_in(hay: *u8, hn: i64, ndl: *u8, nn: i64) -> i64
called by 1: dp_exports_overlap
252func dp_find_at(hay: *u8, hn: i64, ndl: *u8, nn: i64) -> i64
called by 1: dp_sig_mismatch
272func dp_arity(buf: *u8, n: i64, start: i64) -> i64
called by 1: dp_sig_mismatch
297func dp_exports_overlap(pa: *u8, pb: *u8, ba: *u8, bb: *u8, cap: i64) -> i64
called by 1: scan2 calls 3: dp_slurpsys_mmapdp_find_in
343func dp_sig_mismatch(pa: *u8, pb: *u8, ba: *u8, bb: *u8, cap: i64) -> i64
385func dp_has_main(path: *u8, buf: *u8, cap: i64) -> i64
called by 1: scan2 calls 1: dp_slurp
416func dp_fresh(pa: *u8, pb: *u8, dirA: *u8, dirB: *u8, sb: *u8) -> i64
called by 1: scan2 calls 3: dp_mtimedp_wdp_wn
434func scan2(dirA: *u8, dirB: *u8, verbose: i64, stats: *i64, ca: *u8, cb: *u8, ccap: i64) -> i64
553func scan(dirA: *u8, dirB: *u8, verbose: i64) -> i64
563func wfile(path: *u8) -> i64 { let fd: i64=sys_openat_wr(path, 420); if fd>=0 { sys_write(fd, "x" as *u8, 1); sys_close(fd) } return 0 }
566func wfile2(path: *u8, s: *u8) -> i64
573func discover(root: *u8, out: *u8, maxd: i64, maxdepth: i64, cap: *i64) -> i64
628func selftest() -> i64
836func dp_mtime(path: *u8, sb: *u8) -> i64
842func dp_fp_count(dir: *u8) -> i64
863func dp_fingerprint(dir: *u8, offset: i64, limit: i64) -> i64
942func main(argc: i64, argv: *i64) -> i64