code wiki / (root) / nx_dedup_audit.nx

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}