nx_parse_static_gate.nx source
↩ module page · 130 lines · 6541 B
1// nx_parse_static_gate.nx -- THE MISSING PARSER GATE for the static-as-argument defect.
2//
3// WHY THIS EXISTS: nx_cc rejects a STATIC passed directly as a function argument with
4// "nx_parse: argument type mismatch in call to 'F': arg N is a POINTER but the parameter
5// is an INTEGER"
6// even though the static is DECLARED i64 and the parameter IS i64. Located 2026-08-01 in
7// runtime/nx_parse.nx: inject_statics (line ~5187) registers a static as a Local whose `ty`
8// is the SLOT POINTER *(decl_ty), and the scalar-load path (~line 2690) then loads at L.ty,
9// producing a POINTER-typed value. The codebase already compensates for this at three
10// "Static-ptr STRIDE fix" sites (2751/3849/4002) for subscripts and field chains; the
11// call-argument path is the one that was never fixed.
12//
13// COST OF THE DEFECT, MEASURED: it made nx_browser (1,810,737 B) and nishi (1,129,654 B)
14// UNBUILDABLE -- complete working source that nobody could compile -- plus nx_js_conformance,
15// nx_js_es_census, nx_bugforge and latent instances reachable only by closure reordering
16// (nx_trimesh.tm_noise3 surfaced in a file nobody edited). Workarounds cost 24 hand casts.
17//
18// ⚠ THIS GATE IS EXPECTED TO BE **RED** UNTIL THE COMPILER IS FIXED. That is its job: T3 is
19// the defect, and it flips GREEN exactly when the fix lands. A gate that only ever passes
20// cannot tell you a fix worked.
21//
22// ⚠⚠ WARNING TO WHOEVER FIXES IT -- the obvious one-line fix is UNSAFE and I proved it before
23// shipping (debt 1785610588). Detecting a static by "ty_kind says scalar but ty.kind is
24// TY_PTR" MISFIRES ON EVERY POINTER LOCAL: nx_parse.nx:3427 registers an ordinary
25// `let p: *u8 = ...` as add_local(P, name, v, 0, TY_I64, let_ty) -- the IDENTICAL shape.
26// That change would silently unwrap every pointer load in the language. The defect fails
27// LOUDLY at parse; that fix would miscompile SILENTLY. Statics differ only in that value_id
28// is an IR global rather than an alloca, which the Local struct does not expose -- so a
29// correct fix needs a new is_static field or an IR is-global query.
30//
31// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 only when the fix lands.
32import "nx_syscalls.nx"
33import "nx_gate_verdict.nx"
34
35const PG_BUF: i64 = 65536
36const PG_MODE: i64 = 420
37
38// Write a source file, compile it with the LIVE sovereign compiler, return the child exit code.
39// 0 = compiled clean. Non-zero = nx_cc rejected it.
40static pg_argv: i64 = 0
41
42func pg_write(path: *u8, body: *u8) -> i64 {
43 let fd: i64 = sys_openat_wr(path, PG_MODE)
44 if fd < 0 { return 0 - 1 }
45 var n: i64 = 0
46 while body[n] != (0 as u8) { n = n + 1 }
47 sys_write(fd, body, n)
48 sys_close(fd)
49 return 0
50}
51
52// fork + exec the compiler on `src`, discarding its output; return WEXITSTATUS.
53func pg_compile() -> i64 {
54 let av: *i64 = pg_argv as *i64
55 let pid: i64 = sys_fork()
56 if pid < 0 { return 127 }
57 if pid == 0 {
58 let dn: i64 = sys_openat_wr("/dev/null" as *u8, PG_MODE)
59 if dn >= 0 { sys_dup3(dn, 1, 0) sys_dup3(dn, 2, 0) }
60 sys_execve("_offc/nx_cc_sovereign.elf" as *u8, av, 0 as *i64)
61 sys_exit(127)
62 }
63 let st: *i64 = sys_mmap(16) as *i64
64 sys_wait4(pid, st, 0)
65 let sig: i64 = st[0] & 0x7f
66 if sig != 0 { return 128 + sig }
67 return (st[0] >> 8) & 0xff
68}
69
70func pg_case(name: *u8, src: *u8, body: *u8, want_ok: i64, ctr: *i64) -> i64 {
71 pg_write(src, body)
72 let av: *i64 = pg_argv as *i64
73 av[0] = "_offc/nx_cc_sovereign.elf" as *u8 as i64
74 av[1] = src as i64
75 av[2] = 0
76 let rc: i64 = pg_compile()
77 var ok: i64 = 0
78 if want_ok == 1 { if rc == 0 { ok = 1 } }
79 if want_ok == 0 { if rc != 0 { ok = 1 } }
80 gv_check(name, ok, ctr)
81 return ok
82}
83
84func main(argc: i64, argv: *i64) -> i64 {
85 gv_head("nx_parse static-as-argument -- the call-arg path must load a static at its DECLARED type")
86 let ctr: *i64 = gv_ctr()
87 pg_argv = sys_mmap(64) as i64
88
89 // T1 CONTROL: a plain scalar LOCAL passed to an i64 parameter must compile.
90 // If this ever fails the gate itself is broken, not the compiler.
91 pg_case("T1 control: scalar LOCAL -> i64 param compiles" as *u8,
92 "/tmp/_pg_t1.nx" as *u8,
93 "func f(a: i64) -> i64 { return a }\nfunc main(argc: i64, argv: *i64) -> i64 { let x: i64 = 7\n return f(x) }\n" as *u8,
94 1, ctr)
95
96 // T2 CONTROL: a POINTER LOCAL passed to a pointer parameter must compile.
97 // This is the case the tempting one-line fix would have BROKEN (see header warning).
98 pg_case("T2 control: POINTER local -> ptr param compiles (the fix must not break this)" as *u8,
99 "/tmp/_pg_t2.nx" as *u8,
100 "func g(p: *u8) -> i64 { return p[0] as i64 }\nfunc main(argc: i64, argv: *i64) -> i64 { let b: *u8 = sys_mmap(8)\n return g(b) }\nimport \"nx_syscalls.nx\"\n" as *u8,
101 1, ctr)
102
103 // T3 THE DEFECT: a STATIC declared i64, passed to an i64 parameter, must compile.
104 // Currently REJECTED as "arg 1 is a POINTER but the parameter is an INTEGER".
105 // This tooth is RED today and flips GREEN when the compiler is fixed.
106 pg_case("T3 DEFECT: STATIC i64 -> i64 param must compile (RED until nx_cc is fixed)" as *u8,
107 "/tmp/_pg_t3.nx" as *u8,
108 "static s: i64\nfunc f(a: i64) -> i64 { return a }\nfunc main(argc: i64, argv: *i64) -> i64 { s = 7\n return f(s) }\n" as *u8,
109 1, ctr)
110
111 // T4 NON-VACUITY: the same static WITH an explicit cast must compile today.
112 // This proves the gate is measuring the missing deref and not something else --
113 // the cast forces exactly the dereference the call-arg path fails to apply.
114 pg_case("T4 non-vacuity: STATIC with explicit `as i64` compiles TODAY" as *u8,
115 "/tmp/_pg_t4.nx" as *u8,
116 "static s: i64\nfunc f(a: i64) -> i64 { return a }\nfunc main(argc: i64, argv: *i64) -> i64 { s = 7\n return f(s as i64) }\n" as *u8,
117 1, ctr)
118
119 // T5 BITE: genuinely malformed source MUST be rejected. Without this the gate could
120 // pass by accepting everything, which is the failure mode of every green-only gate.
121 pg_case("T5 bite: malformed source is REJECTED (gate can fail)" as *u8,
122 "/tmp/_pg_t5.nx" as *u8,
123 "func f(a: i64) -> i64 { return zzz_undefined_symbol(a) }\n" as *u8,
124 0, ctr)
125
126 let rc: i64 = gv_verdict("PARSE-STATIC-ARG", ctr,
127 "a static passed directly as an argument must load at its DECLARED type, not its slot-pointer type")
128 sys_exit(rc)
129 return rc
130}