code wiki / (root) / nx_libcheck.nx

nx_libcheck.nx source

↩ module page · 372 lines · 17129 B

1// nx_libcheck.nx -- DUPLICATE-LIBRARY DIVERGENCE DETECTOR. The instrument that would have caught BOTH 2// defects that cost this session real time (seq1423 dual-copy, seq1437 silent revert). 3// 4// THE INSIGHT: when a library exists in two directories, THE COPIES ARE EACH OTHER'S CONTROL. Nothing else 5// needs to be known about them. If they are byte-identical the tree is coherent; the moment they differ, 6// EITHER someone edited one copy and forgot the other (seq207 dual-copy -- organs under runtime/ resolve one, 7// organs under _hdl_build/ resolve the other, so half the ecosystem silently keeps old behaviour) OR one copy 8// was reverted underneath you (seq48/132/140 -- a sibling ships an old lib into buildroot and the NEXT BUILD 9// STILL SUCCEEDS, shipping the old behaviour with RC=0 and no diagnostic anywhere). 10// Both are invisible at build time. Both are one hash comparison away from being obvious. 11// 12// FAIL-CLOSED AND DATA-DRIVEN: divergence is a FAILURE by default. Some same-named files are legitimately 13// DIFFERENT ORGANS -- _hdl_build/nx_fs.nx is a coreutils multitool while the live nx_fs is nx_fsops -- so the 14// exceptions live in a CONF (rule 11), never in this code. An unlisted divergence exits 3. A conf that cannot 15// be read simply means zero exceptions, which fails LOUDER rather than quieter. 16// 17// Run it BEFORE a build that touches a shared lib. Cheap enough to run every time. 18// EXIT: 0 all duplicates identical (or expected) - 2 usage - 3 UNEXPECTED divergence - 4 no duplicates found 19// license_tier: ORIGINAL No hw writes (Rule 26). 20import "nx_syscalls.nx" 21 22const LC_MAXF: i64 = 24576 // runtime/ alone holds 10748 .nx and _hdl_build/ 6726 (measured), so 4096 23 // TRUNCATED THE INDEX SILENTLY on the first run and found 1 duplicate 24 // instead of 12. Sized with real headroom AND guarded below -- a cap you 25 // cannot detect hitting is the same defect this organ exists to catch. 26const LC_ARENA: i64 = 2097152 27const LC_DBUF: i64 = 262144 28const LC_FBUF: i64 = 2097152 29const LC_PATH: i64 = 1024 30const LC_MSG: i64 = 8192 31const LC_CONF: i64 = 65536 32const LC_FNV_BASIS: i64 = 1469598103934665603 33const LC_FNV_PRIME: i64 = 1099511628211 34const LC_STDOUT: i64 = 1 35const LC_NL: i64 = 10 36const LC_HASH_C: i64 = 35 37const LC_DOT: i64 = 46 38const LC_SLASH: i64 = 47 39const LC_EXIT_USAGE: i64 = 2 40const LC_EXIT_DIVERGED: i64 = 3 41const LC_EXIT_NODUPES: i64 = 4 42const LC_EXIT_INDEXCAP: i64 = 5 43 44func lc_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 45func lc_cat(d: *u8, off: i64, s: *u8) -> i64 { 46 var i: i64 = 0 47 while s[i] != (0 as u8) { d[off + i] = s[i]; i = i + 1 } 48 return off + i 49} 50func lc_num(d: *u8, off: i64, v: i64) -> i64 { 51 var m: i64 = v 52 var o: i64 = off 53 if m < 0 { m = 0 - m; d[o] = 45 as u8; o = o + 1 } 54 let t: *u8 = sys_mmap(32) 55 var k: i64 = 0 56 if m == 0 { t[0] = 48 as u8; k = 1 } 57 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 58 var i: i64 = 0 59 while i < k { d[o + i] = t[k - 1 - i]; i = i + 1 } 60 sys_munmap(t, 32) 61 return o + k 62} 63// CANONICAL HASH -- semantic equivalence, not byte equality (2026-07-30). 64// WHY: byte-hashing reported nx_site_lock_lib as DIVERGED when its ONLY difference was an import on line 1 65// versus line 3 plus comment text. An instrument that calls semantically identical files a hazard produces 66// exactly the noise that made nx_doc_constscan dark and unrunnable. A guard nobody trusts is not a guard. 67// HOW: strip from // to end-of-line, drop ALL spaces and tabs, skip empty lines, FNV-1a each surviving line, 68// and SUM the per-line hashes. Summation is COMMUTATIVE, so the result is independent of line ORDER -- which 69// is what makes reordered imports compare equal without any sorting buffer. 70func lc_canon(path: *u8) -> i64 { 71 let fd: i64 = sys_openat_rd(path) 72 if fd < 0 { 73 return 0 74 } 75 let b: *u8 = sys_mmap(LC_FBUF) 76 let lbuf: *u8 = sys_mmap(LC_PATH * 16) 77 var llen: i64 = 0 78 var total: i64 = 0 79 var incomment: i64 = 0 80 var prev: i64 = 0 81 var go: i64 = 1 82 while go == 1 { 83 let r: i64 = sys_read(fd, b, LC_FBUF) 84 if r <= 0 { 85 go = 0 86 } else { 87 var i: i64 = 0 88 while i < r { 89 let c: i64 = b[i] 90 if c == LC_NL { 91 if llen > 0 { 92 var lh2: i64 = LC_FNV_BASIS 93 var q: i64 = 0 94 while q < llen { 95 lh2 = (lh2 ^ (lbuf[q] as i64)) * LC_FNV_PRIME 96 q = q + 1 97 } 98 total = total + lh2 99 } 100 llen = 0 101 incomment = 0 102 prev = 0 103 } else { 104 if incomment == 0 { 105 if c != 32 { 106 if c != 9 { 107 lbuf[llen] = c as u8 108 llen = llen + 1 109 } 110 } 111 // A trailing comment must discard ONLY THE REST OF THE LINE, never the code before it. 112 // The first version reset the whole line hash here, so any line ending in a comment 113 // vanished entirely -- and it declared nx_cleanserve_resolve cosmetic-only when the 114 // _hdl_build copy really has 4 extra consts (each with a trailing comment) plus an 115 // extra import. A GUARD THAT SAYS BENIGN ABOUT A REAL DIVERGENCE IS WORSE THAN A NOISY 116 // ONE. Buffering the line lets us drop the already-appended first slash and keep the 117 // code that preceded it. 118 if c == LC_SLASH { 119 if prev == LC_SLASH { 120 incomment = 1 121 if llen > 0 { llen = llen - 1 } 122 } 123 } 124 prev = c 125 } 126 } 127 i = i + 1 128 } 129 } 130 } 131 if llen > 0 { 132 var lh3: i64 = LC_FNV_BASIS 133 var z: i64 = 0 134 while z < llen { 135 lh3 = (lh3 ^ (lbuf[z] as i64)) * LC_FNV_PRIME 136 z = z + 1 137 } 138 total = total + lh3 139 } 140 sys_close(fd) 141 sys_munmap(b, LC_FBUF) 142 sys_munmap(lbuf, LC_PATH * 16) 143 return total 144} 145 146func lc_streq(a: *u8, b: *u8) -> i64 { 147 var i: i64 = 0 148 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 149 if b[i] != (0 as u8) { return 0 } 150 return 1 151} 152// name ends with .nx (and is not a .nx.something shadow -- those are separate debt, not libs) 153func lc_is_nx(s: *u8) -> i64 { 154 let n: i64 = lc_len(s) 155 if n < 4 { return 0 } 156 if s[n - 3] != (LC_DOT as u8) { return 0 } 157 if s[n - 2] != (110 as u8) { return 0 } 158 if s[n - 1] != (120 as u8) { return 0 } 159 return 1 160} 161// FNV-1a over the whole file. szout[0] = bytes read, or -1 if unreadable. 162func lc_hash(path: *u8, szout: *i64) -> i64 { 163 szout[0] = 0 - 1 164 let fd: i64 = sys_openat_rd(path) 165 if fd < 0 { return 0 } 166 let b: *u8 = sys_mmap(LC_FBUF) 167 var total: i64 = 0 168 var h: i64 = LC_FNV_BASIS 169 var go: i64 = 1 170 while go == 1 { 171 let r: i64 = sys_read(fd, b, LC_FBUF) 172 if r <= 0 { go = 0 } else { 173 var i: i64 = 0 174 while i < r { h = (h ^ (b[i] as i64)) * LC_FNV_PRIME; i = i + 1 } 175 total = total + r 176 } 177 } 178 sys_close(fd) 179 sys_munmap(b, LC_FBUF) 180 szout[0] = total 181 return h 182} 183 184func main(argc: i64, argv: *i64) -> i64 { 185 let msg: *u8 = sys_mmap(LC_MSG) 186 var dirA: *u8 = "buildroot/runtime" as *u8 187 var dirB: *u8 = "buildroot/runtime/_hdl_build" as *u8 188 var conf: *u8 = "knowledge/libcheck_expected_diff.conf" as *u8 189 if argc >= 2 { dirA = argv[1] as *u8 } 190 if argc >= 3 { dirB = argv[2] as *u8 } 191 if argc >= 4 { conf = argv[3] as *u8 } 192 193 // ---- expected-divergence conf (rule 11: exceptions are DATA). Absent conf = zero exceptions = louder. 194 let cbuf: *u8 = sys_mmap(LC_CONF) 195 var cn: i64 = 0 196 let cfd: i64 = sys_openat_rd(conf) 197 if cfd >= 0 { 198 let r: i64 = sys_read(cfd, cbuf, LC_CONF) 199 if r > 0 { cn = r } 200 sys_close(cfd) 201 } 202 203 // ---- index dirA 204 let arena: *u8 = sys_mmap(LC_ARENA) 205 let noff: *i64 = sys_mmap(8 * LC_MAXF) as *i64 206 let nhash: *i64 = sys_mmap(8 * LC_MAXF) as *i64 207 let nsize: *i64 = sys_mmap(8 * LC_MAXF) as *i64 208 let ncanon: *i64 = sys_mmap(8 * LC_MAXF) as *i64 209 var nfiles: i64 = 0 210 var aoff: i64 = 0 211 let path: *u8 = sys_mmap(LC_PATH) 212 let szp: *i64 = sys_mmap(16) as *i64 213 214 let fdA: i64 = sys_openat_rd(dirA) 215 if fdA < 0 { 216 var u: i64 = lc_cat(msg, 0, "NX-LIBCHECK ERROR cannot open dirA=" as *u8) 217 u = lc_cat(msg, u, dirA); msg[u] = LC_NL as u8; u = u + 1 218 sys_write(LC_STDOUT, msg, u); return LC_EXIT_USAGE 219 } 220 let dbuf: *u8 = sys_mmap(LC_DBUF) 221 var goA: i64 = 1 222 while goA == 1 { 223 let nb: i64 = sys_getdents64(fdA, dbuf, LC_DBUF) 224 if nb <= 0 { goA = 0 } else { 225 var off: i64 = 0 226 while off < nb { 227 let rec: *u8 = (dbuf as i64 + off) as *u8 228 let rl: i64 = dirent_reclen(rec) 229 if rl <= 0 { off = nb } else { 230 let nm: *u8 = dirent_name(rec) 231 if lc_is_nx(nm) == 1 { if nfiles < LC_MAXF { 232 var po: i64 = 0 233 po = lc_cat(path, po, dirA) 234 path[po] = LC_SLASH as u8; po = po + 1 235 po = lc_cat(path, po, nm) 236 path[po] = 0 as u8 237 let h: i64 = lc_hash(path, szp) 238 if szp[0] >= 0 { 239 noff[nfiles] = aoff 240 var j: i64 = 0 241 while nm[j] != (0 as u8) { arena[aoff + j] = nm[j]; j = j + 1 } 242 arena[aoff + j] = 0 as u8 243 aoff = aoff + j + 1 244 nhash[nfiles] = h 245 nsize[nfiles] = szp[0] 246 ncanon[nfiles] = lc_canon(path) 247 nfiles = nfiles + 1 248 } 249 } } 250 off = off + rl 251 } 252 } 253 } 254 } 255 sys_close(fdA) 256 257 // ---- walk dirB and compare against the dirA index 258 var dupes: i64 = 0 259 var same: i64 = 0 260 var cosm: i64 = 0 261 var diverged: i64 = 0 262 var expected: i64 = 0 263 let fdB: i64 = sys_openat_rd(dirB) 264 if fdB < 0 { 265 var u2: i64 = lc_cat(msg, 0, "NX-LIBCHECK ERROR cannot open dirB=" as *u8) 266 u2 = lc_cat(msg, u2, dirB); msg[u2] = LC_NL as u8; u2 = u2 + 1 267 sys_write(LC_STDOUT, msg, u2); return LC_EXIT_USAGE 268 } 269 var goB: i64 = 1 270 while goB == 1 { 271 let nb: i64 = sys_getdents64(fdB, dbuf, LC_DBUF) 272 if nb <= 0 { goB = 0 } else { 273 var off: i64 = 0 274 while off < nb { 275 let rec: *u8 = (dbuf as i64 + off) as *u8 276 let rl: i64 = dirent_reclen(rec) 277 if rl <= 0 { off = nb } else { 278 let nm: *u8 = dirent_name(rec) 279 if lc_is_nx(nm) == 1 { 280 var idx: i64 = 0 - 1 281 var k: i64 = 0 282 while k < nfiles { 283 if lc_streq(nm, (arena as i64 + noff[k]) as *u8) == 1 { idx = k; k = nfiles } else { k = k + 1 } 284 } 285 if idx >= 0 { 286 dupes = dupes + 1 287 var po: i64 = 0 288 po = lc_cat(path, po, dirB) 289 path[po] = LC_SLASH as u8; po = po + 1 290 po = lc_cat(path, po, nm) 291 path[po] = 0 as u8 292 let h2: i64 = lc_hash(path, szp) 293 var cosmetic: i64 = 0 294 if h2 != nhash[idx] { 295 // bytes differ -- but is the CODE the same? comments and import ORDER are not 296 // hazards, and calling them one is how a guard earns the right to be ignored. 297 if lc_canon(path) == ncanon[idx] { cosmetic = 1 } 298 } 299 if cosmetic == 1 { 300 cosm = cosm + 1 301 var oc: i64 = lc_cat(msg, 0, " cosmetic-only " as *u8) 302 oc = lc_cat(msg, oc, nm) 303 oc = lc_cat(msg, oc, " (comments/import-order; CODE IDENTICAL)" as *u8) 304 msg[oc] = LC_NL as u8 305 oc = oc + 1 306 sys_write(LC_STDOUT, msg, oc) 307 } 308 if h2 == nhash[idx] { same = same + 1 } else { if cosmetic == 0 { 309 // is this basename listed as an EXPECTED divergence? 310 var exp: i64 = 0 311 var ci: i64 = 0 312 var ls: i64 = 0 313 while ci <= cn { 314 var atend: i64 = 0 315 if ci == cn { atend = 1 } else { if cbuf[ci] == (LC_NL as u8) { atend = 1 } } 316 if atend == 1 { 317 let ll: i64 = ci - ls 318 if ll > 0 { if cbuf[ls] != (LC_HASH_C as u8) { 319 var m2: i64 = 1 320 var q: i64 = 0 321 while q < ll { if cbuf[ls + q] != nm[q] { m2 = 0; q = ll } else { q = q + 1 } } 322 if m2 == 1 { if nm[ll] == (0 as u8) { exp = 1 } } 323 } } 324 ls = ci + 1 325 } 326 ci = ci + 1 327 } 328 var o3: i64 = 0 329 if exp == 1 { expected = expected + 1; o3 = lc_cat(msg, 0, " EXPECTED-DIFF " as *u8) } else { diverged = diverged + 1; o3 = lc_cat(msg, 0, " ** DIVERGED " as *u8) } 330 o3 = lc_cat(msg, o3, nm) 331 o3 = lc_cat(msg, o3, " A=" as *u8); o3 = lc_num(msg, o3, nsize[idx]) 332 o3 = lc_cat(msg, o3, "B B=" as *u8); o3 = lc_num(msg, o3, szp[0]) 333 o3 = lc_cat(msg, o3, "B" as *u8) 334 msg[o3] = LC_NL as u8; o3 = o3 + 1 335 sys_write(LC_STDOUT, msg, o3) 336 } 337 } 338 } 339 } 340 off = off + rl 341 } 342 } 343 } 344 } 345 sys_close(fdB) 346 347 var o: i64 = lc_cat(msg, 0, "NX-LIBCHECK dirA=" as *u8) 348 o = lc_cat(msg, o, dirA) 349 o = lc_cat(msg, o, " dirB=" as *u8); o = lc_cat(msg, o, dirB) 350 o = lc_cat(msg, o, " indexed=" as *u8); o = lc_num(msg, o, nfiles) 351 o = lc_cat(msg, o, " duplicates=" as *u8); o = lc_num(msg, o, dupes) 352 o = lc_cat(msg, o, " identical=" as *u8); o = lc_num(msg, o, same) 353 o = lc_cat(msg, o, " cosmetic=" as *u8); o = lc_num(msg, o, cosm) 354 o = lc_cat(msg, o, " expected_diff=" as *u8); o = lc_num(msg, o, expected) 355 o = lc_cat(msg, o, " DIVERGED=" as *u8); o = lc_num(msg, o, diverged) 356 o = lc_cat(msg, o, " verdict=" as *u8) 357 // INDEX-CAP GUARD. The first run of this organ reported indexed=4096 == LC_MAXF and found ONE duplicate 358 // instead of twelve: the index silently truncated and the scan was a lie. An instrument that can quietly 359 // see only part of the tree must REFUSE, not summarise. Also guards the name arena. 360 var rc: i64 = 0 361 if nfiles >= LC_MAXF { rc = LC_EXIT_INDEXCAP } 362 if aoff >= LC_ARENA - LC_PATH { rc = LC_EXIT_INDEXCAP } 363 if dupes == 0 { if rc == 0 { rc = LC_EXIT_NODUPES } } 364 if diverged > 0 { rc = LC_EXIT_DIVERGED } 365 if rc == 0 { o = lc_cat(msg, o, "GREEN-all-duplicate-libs-identical" as *u8) } 366 if rc == LC_EXIT_NODUPES { o = lc_cat(msg, o, "AMBER-no-duplicates-found-check-the-dirs" as *u8) } 367 if rc == LC_EXIT_INDEXCAP { o = lc_cat(msg, o, "RED-INDEX-CAP-HIT-scan-was-PARTIAL-raise-LC_MAXF-do-not-trust-these-counts" as *u8) } 368 if rc == LC_EXIT_DIVERGED { o = lc_cat(msg, o, "RED-UNEXPECTED-DIVERGENCE-half-the-tree-may-build-old-behaviour" as *u8) } 369 msg[o] = LC_NL as u8; o = o + 1 370 sys_write(LC_STDOUT, msg, o) 371 return rc 372}