nx_dedup_audit.nx source
↩ module page · 417 lines · 14638 B
1// nx_dedup_audit.nx -- pure-NishiLang substrate duplicate-primitive auditor.
2//
3// Walks a runtime directory (default: the cwd or argv[1]), parses every
4// nx_*.nx file (excluding _test.nx), extracts every `func nx_...`
5// declaration, computes a stem by stripping the module prefix, and
6// emits JSONL clusters (stem appears in >= 2 files).
7//
8// All work in pure NishiLang -- directory iteration via sys_getdents64
9// (through nx_dirent.nx), file read via sys_read_file, output via
10// sys_write (through runtime.nx::println). Zero shell-tool calls.
11//
12// Output format (stdout, one line per cluster):
13// {"stem":"<stem>","count":N,"members":[{"name":"...","file":"..."},...]}
14//
15// genealogy_id: baker_1995_clone_detection + smith_2009_code_clones
16// + roy_cordy_2007_clone_taxonomy
17// lineage_id: function_name_stem_grouping + equivalence_class_partition
18// axioms: NX_AX_REL_REFLEXIVITY (stem(a) = stem(a))
19// + NX_AX_REL_SYMMETRY (stem(a)=stem(b) -> stem(b)=stem(a))
20// + NX_AX_REL_TRANSITIVITY (a~b, b~c -> a~c)
21// together: equivalence relation on function-stems, so
22// clusters are well-defined disjoint classes.
23
24// nx_safety_envelope:
25// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
26// sil_target: SIL1
27// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
28// verdict: NOT_YET_EVALUATED
29
30import "syscalls.nx"
31import "runtime.nx"
32import "nx_axioms.nx"
33import "nx_dirent.nx"
34import "nx_fcntl.nx"
35
36const NX_DEDUP_MAX_RECORDS: i64 = 8000
37const NX_DEDUP_MAX_STR: i64 = 96
38const NX_DEDUP_RECORD_BYTES: i64 = 24
39const NX_DEDUP_DIR_BUF: i64 = 16384
40
41struct DedupRecord {
42 stem: *u8, // strdup'd
43 name: *u8,
44 file: *u8,
45}
46
47// ===== string helpers (substrate-native) ================================
48
49func dedup_byte_copy(dst: *u8, src: *u8, n: i64) -> i64 {
50 var i: i64 = 0
51 while i < n {
52 dst[i] = src[i]
53 i = i + 1
54 }
55 dst[n] = 0
56 return 0
57}
58
59func dedup_strdup(s: *u8, n: i64) -> *u8 {
60 let buf: *u8 = sys_mmap(n + 1)
61 dedup_byte_copy(buf, s, n)
62 return buf
63}
64
65func dedup_is_alnum_us(c: i64) -> i64 {
66 if c >= 48 { if c <= 57 { return 1 } } // 0-9
67 if c >= 65 { if c <= 90 { return 1 } } // A-Z
68 if c >= 97 { if c <= 122 { return 1 } } // a-z
69 if c == 95 { return 1 } // _
70 return 0
71}
72
73func dedup_starts_with(buf: *u8, off: i64, len: i64, pat: *u8) -> i64 {
74 var i: i64 = 0
75 while pat[i] != 0 {
76 if off + i >= len { return 0 }
77 if buf[off + i] != pat[i] { return 0 }
78 i = i + 1
79 }
80 return 1
81}
82
83// ===== filename pattern check ===========================================
84//
85// Accept names matching nx_*.nx and rejecting nx_*_test.nx.
86// Returns 1 if matches, 0 otherwise.
87
88func dedup_name_matches(name: *u8, name_len: i64) -> i64 {
89 if name_len < 6 { return 0 } // "nx_*.nx" minimum
90 // prefix "nx_"
91 if name[0] != 110 { return 0 } // 'n'
92 if name[1] != 120 { return 0 } // 'x'
93 if name[2] != 95 { return 0 } // '_'
94 // suffix ".nx"
95 if name[name_len - 3] != 46 { return 0 } // '.'
96 if name[name_len - 2] != 110 { return 0 } // 'n'
97 if name[name_len - 1] != 120 { return 0 } // 'x'
98 // reject "_test.nx"
99 if name_len >= 8 {
100 if name[name_len - 8] == 95 { // '_'
101 if name[name_len - 7] == 116 { // 't'
102 if name[name_len - 6] == 101 { // 'e'
103 if name[name_len - 5] == 115 { // 's'
104 if name[name_len - 4] == 116 { // 't'
105 return 0
106 }
107 }
108 }
109 }
110 }
111 }
112 return 1
113}
114
115// Extract module name (filename minus "nx_" prefix and ".nx" suffix).
116// Writes to out_buf (caller-provided, null-terminated). Returns length.
117
118func dedup_module_from_filename(name: *u8, name_len: i64, out_buf: *u8) -> i64 {
119 let module_len: i64 = name_len - 6 // strip "nx_" + ".nx"
120 if module_len <= 0 { return 0 }
121 var i: i64 = 0
122 while i < module_len {
123 out_buf[i] = name[3 + i]
124 i = i + 1
125 }
126 out_buf[module_len] = 0
127 return module_len
128}
129
130// ===== function-name parsing ===========================================
131//
132// Look for lines matching: ^func[ \t]+nx_<ident>(
133// Extract <ident> = name including the nx_ prefix.
134
135func dedup_at_func_start(buf: *u8, off: i64, len: i64) -> i64 {
136 // Check buf[off..off+5] == "func " AND we're at line start
137 // (off == 0 OR buf[off-1] == '\n').
138 if off > 0 {
139 if buf[off - 1] != 10 { return 0 } // '\n'
140 }
141 if off + 5 > len { return 0 }
142 if buf[off] != 102 { return 0 } // 'f'
143 if buf[off + 1] != 117 { return 0 } // 'u'
144 if buf[off + 2] != 110 { return 0 } // 'n'
145 if buf[off + 3] != 99 { return 0 } // 'c'
146 // separator: space or tab
147 let sep: i64 = buf[off + 4]
148 if sep != 32 {
149 if sep != 9 { return 0 }
150 }
151 return 1
152}
153
154// Starting at `off` (which is just past "func "), skip whitespace, then
155// extract identifier into out_name (null-terminated). Returns the
156// identifier length, or 0 if not a valid nx_<ident> followed by '('.
157
158func dedup_extract_funcname(buf: *u8, off: i64, len: i64, out_name: *u8) -> i64 {
159 var p: i64 = off
160 // skip whitespace after "func"
161 var done_ws: i64 = 0
162 while done_ws == 0 {
163 if p >= len { done_ws = 1 }
164 if done_ws == 0 {
165 let c: i64 = buf[p]
166 if c == 32 { p = p + 1 }
167 if c != 32 {
168 if c == 9 { p = p + 1 }
169 if c != 9 { done_ws = 1 }
170 }
171 }
172 }
173 if p >= len { return 0 }
174 // now p points at first non-ws after "func "
175 if dedup_starts_with(buf, p, len, "nx_" as *u8) == 0 { return 0 }
176 let start: i64 = p
177 var done_id: i64 = 0
178 while done_id == 0 {
179 if p >= len { done_id = 1 }
180 if done_id == 0 {
181 if dedup_is_alnum_us(buf[p]) == 1 { p = p + 1 }
182 if dedup_is_alnum_us(buf[p]) == 0 { done_id = 1 }
183 }
184 }
185 let end: i64 = p
186 // Must be followed by '('.
187 if p >= len { return 0 }
188 if buf[p] != 40 { return 0 } // '('
189 let name_len: i64 = end - start
190 if name_len <= 3 { return 0 } // need more than "nx_"
191 if name_len >= NX_DEDUP_MAX_STR { return 0 }
192 dedup_byte_copy(out_name, ((buf as i64) + start) as *u8, name_len)
193 return name_len
194}
195
196// Compute stem: strip "nx_<first_word>_" universally.
197// E.g.:
198// nx_geom_isqrt -> "isqrt"
199// nx_th_isqrt -> "isqrt"
200// nx_image_alloc -> "alloc"
201// nx_dedup_extract_funcname -> "extract_funcname"
202//
203// We strip the FUNCTION's own first-word prefix, not the file's
204// module name -- because some files use abbreviated prefixes
205// (e.g. nx_theorems7.nx contains nx_th_* functions).
206// Returns stem length, writes to out_stem.
207
208func dedup_compute_stem(name: *u8, name_len: i64,
209 out_stem: *u8) -> i64 {
210 if name_len < 5 { return 0 } // "nx_X_" minimum
211 if name[0] != 110 { return 0 } // 'n'
212 if name[1] != 120 { return 0 } // 'x'
213 if name[2] != 95 { return 0 } // '_'
214 // find first underscore after position 3
215 var p: i64 = 3
216 var done: i64 = 0
217 while done == 0 {
218 if p >= name_len { done = 1 }
219 if done == 0 {
220 if name[p] == 95 { done = 1 } // '_'
221 if name[p] != 95 { p = p + 1 }
222 }
223 }
224 if p >= name_len { return 0 } // no second underscore
225 let start: i64 = p + 1
226 let stem_len: i64 = name_len - start
227 if stem_len <= 0 { return 0 }
228 var k: i64 = 0
229 while k < stem_len {
230 out_stem[k] = name[start + k]
231 k = k + 1
232 }
233 out_stem[stem_len] = 0
234 return stem_len
235}
236
237// ===== file processing =================================================
238
239func dedup_process_file(file_path: *u8, file_basename: *u8,
240 records: *DedupRecord, n_records: *i64) -> i64 {
241 let out_len: *i64 = (sys_mmap(8)) as *i64
242 out_len[0] = 0
243 let buf: *u8 = sys_read_file(file_path, out_len)
244 if (buf as i64) == 0 { return -1 }
245 let n: i64 = out_len[0]
246
247 let name_buf: *u8 = sys_mmap(NX_DEDUP_MAX_STR)
248 let stem_buf: *u8 = sys_mmap(NX_DEDUP_MAX_STR)
249
250 var off: i64 = 0
251 while off < n {
252 if dedup_at_func_start(buf, off, n) == 1 {
253 // off..off+5 is "func "; skip past that
254 let name_len: i64 = dedup_extract_funcname(buf, off + 5, n, name_buf)
255 if name_len > 0 {
256 let stem_len: i64 = dedup_compute_stem(name_buf, name_len,
257 stem_buf)
258 if stem_len > 0 {
259 if n_records[0] < NX_DEDUP_MAX_RECORDS {
260 let r_idx: i64 = n_records[0]
261 let r_addr: i64 = (records as i64) + r_idx * NX_DEDUP_RECORD_BYTES
262 let rec: *DedupRecord = r_addr as *DedupRecord
263 rec.stem = dedup_strdup(stem_buf, stem_len)
264 rec.name = dedup_strdup(name_buf, name_len)
265 rec.file = file_basename
266 n_records[0] = r_idx + 1
267 }
268 }
269 }
270 }
271 off = off + 1
272 }
273 return 0
274}
275
276// ===== directory walking + dispatch ===================================
277
278func dedup_walk_runtime(dir_path: *u8, records: *DedupRecord,
279 n_records: *i64) -> i64 {
280 let fd: i64 = nx_openat(NX_AT_FDCWD, dir_path,
281 NX_O_RDONLY | NX_O_DIRECTORY, 0)
282 if fd < 0 { return -1 }
283
284 let buf: *u8 = sys_mmap(NX_DEDUP_DIR_BUF)
285 let dr_raw: *u8 = sys_mmap(NX_DIRENT_BYTES)
286 let dr: *NxDirent = dr_raw as *NxDirent
287 let path_buf: *u8 = sys_mmap(512)
288 let module_buf: *u8 = sys_mmap(NX_DEDUP_MAX_STR)
289
290 let dir_len: i64 = strlen(dir_path)
291
292 var batch: i64 = nx_dirent_read(fd, buf, NX_DEDUP_DIR_BUF)
293 while batch > 0 {
294 var off: i64 = 0
295 while off < batch {
296 let next_off: i64 = nx_dirent_iter(buf, off, batch, dr)
297 if next_off <= 0 { off = batch + 1 }
298 if off <= batch {
299 let nm_len: i64 = nx_dirent_name_len(dr)
300 if dedup_name_matches(dr.name, nm_len) == 1 {
301 // Build full path: dir_path + "/" + name
302 dedup_byte_copy(path_buf, dir_path, dir_len)
303 path_buf[dir_len] = 47 // '/'
304 dedup_byte_copy(((path_buf as i64) + dir_len + 1) as *u8,
305 dr.name, nm_len)
306 let bn: *u8 = dedup_strdup(dr.name, nm_len)
307 dedup_process_file(path_buf, bn, records, n_records)
308 }
309 off = next_off
310 }
311 }
312 batch = nx_dirent_read(fd, buf, NX_DEDUP_DIR_BUF)
313 }
314 sys_close(fd)
315 return 0
316}
317
318// ===== grouping + emission =============================================
319
320// Emit one JSONL cluster line.
321func dedup_emit_cluster(records: *DedupRecord, n_records: i64,
322 primary_idx: i64, seen: *u8) -> i64 {
323 let r_addr: i64 = (records as i64) + primary_idx * NX_DEDUP_RECORD_BYTES
324 let pri: *DedupRecord = r_addr as *DedupRecord
325 let stem: *u8 = pri.stem
326
327 // count matches
328 var count: i64 = 0
329 var i: i64 = 0
330 while i < n_records {
331 let ri: i64 = (records as i64) + i * NX_DEDUP_RECORD_BYTES
332 let r: *DedupRecord = ri as *DedupRecord
333 if streq(r.stem, stem) == 1 { count = count + 1 }
334 i = i + 1
335 }
336 if count < 2 { return 0 }
337
338 // Emit JSONL.
339 print("{\"stem\":\"" as *u8)
340 print(stem)
341 print("\",\"count\":" as *u8)
342 print_i64(count)
343 print(",\"members\":[" as *u8)
344 var emitted: i64 = 0
345 var j: i64 = 0
346 while j < n_records {
347 let rj_addr: i64 = (records as i64) + j * NX_DEDUP_RECORD_BYTES
348 let rj: *DedupRecord = rj_addr as *DedupRecord
349 if streq(rj.stem, stem) == 1 {
350 if emitted > 0 { print("," as *u8) }
351 print("{\"name\":\"" as *u8)
352 print(rj.name)
353 print("\",\"file\":\"" as *u8)
354 print(rj.file)
355 print("\"}" as *u8)
356 seen[j] = 1
357 emitted = emitted + 1
358 }
359 j = j + 1
360 }
361 println("]}" as *u8)
362 return count
363}
364
365// ===== entry ============================================================
366
367func main(argc: i64, argv: *i64) -> i64 {
368 var dir_path: *u8 = "nxc2/runtime" as *u8
369 if argc >= 2 {
370 dir_path = (argv[1]) as *u8
371 }
372
373 let n_records: *i64 = (sys_mmap(8)) as *i64
374 n_records[0] = 0
375 let records_raw: *u8 = sys_mmap(NX_DEDUP_MAX_RECORDS * NX_DEDUP_RECORD_BYTES)
376 let records: *DedupRecord = records_raw as *DedupRecord
377
378 if dedup_walk_runtime(dir_path, records, n_records) != 0 {
379 println("error: cannot open runtime directory" as *u8)
380 return 1
381 }
382
383 // O(N^2) grouping with seen[] mask to avoid double-emission.
384 let nr: i64 = n_records[0]
385 let seen: *u8 = sys_mmap(nr + 1)
386 var s: i64 = 0
387 while s < nr {
388 seen[s] = 0
389 s = s + 1
390 }
391 var n_clusters: i64 = 0
392 var n_dup_funcs: i64 = 0
393 var i: i64 = 0
394 while i < nr {
395 if seen[i] == 0 {
396 let cnt: i64 = dedup_emit_cluster(records, nr, i, seen)
397 if cnt >= 2 {
398 n_clusters = n_clusters + 1
399 n_dup_funcs = n_dup_funcs + cnt
400 } else {
401 seen[i] = 1
402 }
403 }
404 i = i + 1
405 }
406
407 // Trailing summary line (also JSONL, distinguishable by "summary" key).
408 print("{\"summary\":true,\"total_functions\":" as *u8)
409 print_i64(nr)
410 print(",\"duplicate_clusters\":" as *u8)
411 print_i64(n_clusters)
412 print(",\"duplicate_instances\":" as *u8)
413 print_i64(n_dup_funcs)
414 println("}" as *u8)
415
416 return 0
417}