code wiki / _hdl_build / nx_dupfunc_test.nx
nx_dupfunc_test.nx source
↩ module page · 130 lines · 6681 B
1// nx_dupfunc_test.nx -- gate for the function-body duplication detector.
2//
3// THE PROPERTY THAT MATTERS is T1: two identical bodies behind DIFFERENT
4// NAMES must hash the same. Every duplicate this tool found in the real tree
5// has a different name in each copy -- the integer-printing helper appears
6// 198 times as vn, t_putn, gn, dn, wn and tn -- so a detector keyed on names
7// would report a clean tree. Ignoring the name is the whole design.
8//
9// T4 is a regression test for a bug that made the tool useless while
10// reporting success: NishiLang has no break, the body scan escaped by
11// assigning its loop variable, and that destroyed the position it had just
12// found. Every body came out empty, so the scan measured 0 functions across
13// 17000 files and printed verdict=GREEN. ★A detector that reports nothing
14// looks exactly like a clean tree.
15// expect_exit: 0 license_tier: ORIGINAL
16import "nx_syscalls.nx"
17import "nx_gatelib.nx"
18import "nx_gate.nx"
19
20func tw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
21func tn(v: i64) -> i64 { let b: *u8 = sys_mmap(24); var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, b, k); return 0 }
22func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
23
24func x_hash(buf: *u8, n: i64) -> i64 {
25 var h: i64 = 1469598103934665603
26 var i: i64 = 0
27 while i < n { h = h ^ (buf[i] as i64); h = h * 1099511628211; i = i + 1 }
28 return h
29}
30
31// The normalisation under test, re-derived independently: strip comments,
32// drop the signature up to the first brace, collapse whitespace runs.
33func x_norm(src: *u8, n: i64, out: *u8) -> i64 {
34 let tmp: *u8 = sys_mmap(8192)
35 let sn: i64 = gl_strip_comments(src, n, tmp)
36 var p: i64 = 0
37 var start: i64 = 0 - 1
38 while p < sn {
39 if start < 0 { if tmp[p] == (123 as u8) { start = p + 1 } }
40 p = p + 1
41 }
42 if start < 0 { start = sn }
43 var w: i64 = 0
44 var q: i64 = start
45 var ws: i64 = 0
46 while q < sn {
47 let c: i64 = tmp[q] as i64
48 var isws: i64 = 0
49 if c == 32 { isws = 1 }
50 if c == 9 { isws = 1 }
51 if c == 10 { isws = 1 }
52 if c == 13 { isws = 1 }
53 if isws == 1 { if ws == 0 { out[w] = 32 as u8; w = w + 1; ws = 1 } }
54 else { out[w] = tmp[q]; w = w + 1; ws = 0 }
55 q = q + 1
56 }
57 return w
58}
59
60func main() -> i64 {
61 var pass: i64 = 0
62 var total: i64 = 0
63 let a: *u8 = sys_mmap(8192)
64 let b: *u8 = sys_mmap(8192)
65
66 // --- T1 ***NAMES MUST BE IGNORED.*** The same body behind two
67 // different names is the shape every real duplicate in this tree
68 // takes. If this fails the tool reports a clean tree forever. ---
69 total = total + 1
70 let f1: *u8 = "func gs_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != 0 { n = n + 1 } return n }" as *u8
71 let f2: *u8 = "func gq_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != 0 { n = n + 1 } return n }" as *u8
72 let n1: i64 = x_norm(f1, slen(f1), a)
73 let n2: i64 = x_norm(f2, slen(f2), b)
74 tw("T1 gs_len vs gq_len (identical body, different name): hashes " as *u8)
75 var ok1: i64 = 1
76 if x_hash(a, n1) != x_hash(b, n2) { ok1 = 0 }
77 if n1 <= 0 { ok1 = 0 }
78 if ok1 == 1 { pass = pass + 1; tw("MATCH: PASS\n" as *u8) } else { tw("DIFFER: FAIL\n" as *u8) }
79
80 // --- T2 NON-VACUITY. A genuinely different body must NOT match, or
81 // the tool would report the entire tree as one duplicate. ---
82 total = total + 1
83 let f3: *u8 = "func gs_len(s: *u8) -> i64 { var n: i64 = 1; while s[n] != 0 { n = n + 2 } return n }" as *u8
84 let n3: i64 = x_norm(f3, slen(f3), b)
85 tw("T2 same name, DIFFERENT body: hashes " as *u8)
86 if x_hash(a, n1) != x_hash(b, n3) { pass = pass + 1; tw("DIFFER: PASS\n" as *u8) } else { tw("MATCH: FAIL\n" as *u8) }
87
88 // --- T3 Comments and indentation are not differences. A copied
89 // function usually keeps the code and rewrites the comment. ---
90 total = total + 1
91 let f4: *u8 = "func other(s: *u8) -> i64 {\n // a completely different explanation\n var n: i64 = 0; while s[n] != 0 { n = n + 1 }\n return n }" as *u8
92 let n4: i64 = x_norm(f4, slen(f4), b)
93 tw("T3 same body, different comment and indentation: hashes " as *u8)
94 if x_hash(a, n1) == x_hash(b, n4) { pass = pass + 1; tw("MATCH: PASS\n" as *u8) } else { tw("DIFFER: FAIL\n" as *u8) }
95
96 // --- T4 ***THE BUG THAT REPORTED SUCCESS WHILE MEASURING NOTHING.***
97 // The body must be non-empty and must NOT contain the signature.
98 // Both halves matter: empty bodies made the scan return 0
99 // functions, and a body still carrying its signature would put
100 // the name back into the hash and undo T1. ---
101 total = total + 1
102 var ok4: i64 = 1
103 if n1 < 20 { ok4 = 0 }
104 if gl_has_token(a, n1, "gs_len" as *u8, 6) != 0 { ok4 = 0 }
105 if gl_has_token(a, n1, "return" as *u8, 6) != 1 { ok4 = 0 }
106 tw("T4 extracted body is non-empty (" as *u8); tn(n1)
107 tw("B), excludes the signature, and contains the code: " as *u8)
108 if ok4 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
109
110 // --- T5 A body-less declaration must not crash or masquerade as a
111 // duplicate of every other body-less thing in a meaningful way. ---
112 total = total + 1
113 let f5: *u8 = "func nobody(s: *u8) -> i64" as *u8
114 let n5: i64 = x_norm(f5, slen(f5), b)
115 tw("T5 signature with no body normalises to " as *u8); tn(n5)
116 tw(" bytes (below any sane min-body threshold): " as *u8)
117 if n5 < 20 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
118
119 // --- T6 The hash discriminates on a ONE CHARACTER change, which is the
120 // resolution a body comparison needs to be trustworthy. ---
121 total = total + 1
122 let f6: *u8 = "func gs_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != 0 { n = n + 1 } return m }" as *u8
123 let n6: i64 = x_norm(f6, slen(f6), b)
124 tw("T6 one character changed (return n -> return m): hashes " as *u8)
125 if x_hash(a, n1) != x_hash(b, n6) { pass = pass + 1; tw("DIFFER: PASS\n" as *u8) } else { tw("MATCH: FAIL\n" as *u8) }
126
127 tw("DUPFUNC-GATE passed " as *u8); tn(pass); tw("/" as *u8); tn(total)
128 if pass == total { tw(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
129 tw(" verdict=RED\n" as *u8); sys_exit(1); return 1
130}