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}