nx_jsbalance.nx source
↩ module page · 208 lines · 9487 B
1// nx_jsbalance.nx -- DOES THE EMITTED JAVASCRIPT EVEN LEX? A structural balance check over a page
2// this estate GENERATES, so a bad emit cannot reach a browser as a black screen.
3//
4// WHY THIS EXISTS, AND IT IS MY OWN OUTAGE. 2026-08-25 I edited nx_game_page_emit's emitted guard,
5// shipped it through build -> promote -> ship lane -> live docroot, and /world/beach and /world/craft
6// both went BLACK. Every gate in the chain was GREEN: the organ compiled, the wasm-vm gate passed,
7// the ship lane read the page back and verified its marker. NOT ONE OF THEM PARSES THE JAVASCRIPT.
8// The estate emits ~410 KB of JS per page and had no check that it lexes at all, so a single stray
9// brace or unterminated string takes the whole script down -- and with the script down, even the
10// software-renderer fallback never runs. That is the difference between "ugly" and "black".
11// ★A PIPELINE THAT VERIFIES EVERY STAGE EXCEPT THE ARTIFACT'S OWN LANGUAGE IS GREEN ALL THE WAY TO
12// THE OUTAGE.
13//
14// WHAT IT CHECKS, AND ONLY THIS: the three faults that take a whole script out at once --
15// 1. an unterminated string or template literal at EOF
16// 2. an unterminated block comment at EOF
17// 3. bracket depth that ends non-zero, or ever goes NEGATIVE (a close before its open)
18// It is a LEXER, not a parser. It cannot see a misspelled identifier or a wrong argument, and it does
19// not pretend to: those fail one call, not the whole file, and the browser reports them.
20//
21// ⚠ DECLARED IMPRECISION, because a checker whose blind spots are unstated gets trusted as exact.
22// JavaScript cannot be lexed without parsing: `/` is division or the start of a regex literal
23// depending on the grammatical position before it, and no scanner settles that without a parser.
24// This one treats every `/` as division, so a REGEX LITERAL containing a quote or a brace would be
25// miscounted. That is the one construct it can be wrong about, and it errs by REPORTING IMBALANCE on
26// a valid file -- loud, never silent. Before wiring, it is calibrated on the known-good live page:
27// if it cannot pass a page a browser runs, it is too naive to gate with, and the calibration says so
28// rather than the ratchet being loosened to fit.
29// license_tier: ORIGINAL No hw writes (Rule 26).
30import "nx_syscalls.nx"
31
32// scanner states
33const JSB_CODE: i64 = 0
34const JSB_SQ: i64 = 1 // '...'
35const JSB_DQ: i64 = 2 // "..."
36const JSB_BT: i64 = 3 // `...`
37const JSB_LINE: i64 = 4 // // ...
38const JSB_BLOCK: i64 = 5 // /* ... */
39
40// out[] slots
41const JSB_O_BRACE: i64 = 0 // final {} depth
42const JSB_O_PAREN: i64 = 1 // final () depth
43const JSB_O_BRACK: i64 = 2 // final [] depth
44const JSB_O_STATE: i64 = 3 // scanner state at EOF
45const JSB_O_NEGAT: i64 = 4 // offset of the first NEGATIVE depth, -1 if none
46const JSB_O_OPENAT: i64 = 5 // offset where the unterminated construct began, -1 if none
47const JSB_O_SCAN: i64 = 6 // bytes actually scanned -- the coverage figure
48const JSB_O_N: i64 = 8
49
50// exit codes, each naming ITS OWN fault rather than one generic failure
51const JSB_OK: i64 = 0
52const JSB_UNTERM_STR: i64 = 0 - 2
53const JSB_UNTERM_CMT: i64 = 0 - 3
54const JSB_DEPTH: i64 = 0 - 4
55const JSB_NEGATIVE: i64 = 0 - 5
56
57func jsb_check(buf: *u8, n: i64, out: *i64) -> i64 {
58 var i: i64 = 0
59 while i < JSB_O_N { out[i] = 0; i = i + 1 }
60 out[JSB_O_NEGAT] = 0 - 1
61 out[JSB_O_OPENAT] = 0 - 1
62
63 var st: i64 = JSB_CODE
64 var brace: i64 = 0
65 var paren: i64 = 0
66 var brack: i64 = 0
67 var openat: i64 = 0 - 1
68 var p: i64 = 0
69 while p < n {
70 let c: i64 = buf[p] & 255
71 // THE STATE AS IT WAS ON ENTRY. These branches are sequential ifs with no else, so testing
72 // the LIVE st means the branch that OPENS a string immediately falls into the branch that
73 // closes it -- the opening quote terminates itself and every string in the file is invisible.
74 // That is the banked "with no else, the first branch mutation makes the second test true"
75 // defect, and it cost this lexer its first three controls.
76 let st0: i64 = st
77 if st0 == JSB_CODE {
78 // A backslash outside a string is not an escape in JS, so it is NOT consumed here --
79 // treating it as one would swallow the character after it and shift everything.
80 if c == 39 { st = JSB_SQ
81 openat = p }
82 if c == 34 { st = JSB_DQ
83 openat = p }
84 if c == 96 { st = JSB_BT
85 openat = p }
86 if c == 47 {
87 if p + 1 < n {
88 let d: i64 = buf[p+1] & 255
89 if d == 47 { st = JSB_LINE }
90 if d == 42 { st = JSB_BLOCK
91 openat = p
92 p = p + 1 }
93 }
94 }
95 if c == 123 { brace = brace + 1 }
96 if c == 125 { brace = brace - 1
97 if brace < 0 { if out[JSB_O_NEGAT] < 0 { out[JSB_O_NEGAT] = p } } }
98 if c == 40 { paren = paren + 1 }
99 if c == 41 { paren = paren - 1
100 if paren < 0 { if out[JSB_O_NEGAT] < 0 { out[JSB_O_NEGAT] = p } } }
101 if c == 91 { brack = brack + 1 }
102 if c == 93 { brack = brack - 1
103 if brack < 0 { if out[JSB_O_NEGAT] < 0 { out[JSB_O_NEGAT] = p } } }
104 }
105 // Strings: a backslash escapes the NEXT byte, which is how a quote lives inside its own
106 // quotes. Skipping that byte is the whole reason a naive brace count is not enough.
107 if st0 == JSB_SQ {
108 if c == 92 { p = p + 1 }
109 if c == 39 { st = JSB_CODE
110 openat = 0 - 1 }
111 }
112 if st0 == JSB_DQ {
113 if c == 92 { p = p + 1 }
114 if c == 34 { st = JSB_CODE
115 openat = 0 - 1 }
116 }
117 if st0 == JSB_BT {
118 // Template literals nest ${ } and this scanner does NOT follow that nesting: a brace
119 // inside a template is simply not counted, which is the conservative direction -- it can
120 // miss an imbalance there, never invent one.
121 if c == 92 { p = p + 1 }
122 if c == 96 { st = JSB_CODE
123 openat = 0 - 1 }
124 }
125 if st0 == JSB_LINE {
126 if c == 10 { st = JSB_CODE }
127 }
128 if st0 == JSB_BLOCK {
129 if c == 42 {
130 if p + 1 < n {
131 if (buf[p+1] & 255) == 47 { st = JSB_CODE
132 openat = 0 - 1
133 p = p + 1 }
134 }
135 }
136 }
137 p = p + 1
138 }
139
140 out[JSB_O_BRACE] = brace
141 out[JSB_O_PAREN] = paren
142 out[JSB_O_BRACK] = brack
143 out[JSB_O_STATE] = st
144 out[JSB_O_OPENAT] = openat
145 out[JSB_O_SCAN] = n
146
147 // Ordered so the MOST specific fault is named first. A generic "unbalanced" on a file whose real
148 // problem is an unterminated string sends the reader to the wrong place entirely.
149 if st == JSB_SQ { return JSB_UNTERM_STR }
150 if st == JSB_DQ { return JSB_UNTERM_STR }
151 if st == JSB_BT { return JSB_UNTERM_STR }
152 if st == JSB_BLOCK { return JSB_UNTERM_CMT }
153 if out[JSB_O_NEGAT] >= 0 { return JSB_NEGATIVE }
154 if brace != 0 { return JSB_DEPTH }
155 if paren != 0 { return JSB_DEPTH }
156 if brack != 0 { return JSB_DEPTH }
157 return JSB_OK
158}
159
160// Scan only what lies between <script ...> and </script>, so page prose and the HTML around it are
161// never counted. Returns the same codes as jsb_check; -1 if no script element is present at all,
162// which is itself a finding for a page whose entire behaviour is script.
163const JSB_NO_SCRIPT: i64 = 0 - 1
164
165func jsb_find(buf: *u8, n: i64, from: i64, pat: *u8, plen: i64) -> i64 {
166 var i: i64 = from
167 while i + plen <= n {
168 var k: i64 = 0
169 var hit: i64 = 1
170 while k < plen {
171 if (buf[i+k] & 255) != (pat[k] & 255) { hit = 0
172 k = plen }
173 k = k + 1
174 }
175 if hit == 1 { return i }
176 i = i + 1
177 }
178 return 0 - 1
179}
180
181func jsb_check_page(buf: *u8, n: i64, out: *i64) -> i64 {
182 // LARGEST script body, not the first: the boot-guard tag precedes the engine, and a lexer
183 // that judged the 545-byte guard while the 433KB engine went unlexed was measured doing
184 // exactly that (ship receipt: scanned=545). A referee must not lose its subject to a
185 // page-structure change.
186 var best_s: i64 = 0 - 1
187 var best_e: i64 = 0 - 1
188 var pos: i64 = 0
189 while pos < n {
190 let open: i64 = jsb_find(buf, n, pos, "<script" as *u8, 7)
191 if open < 0 { pos = n } else {
192 let gt: i64 = jsb_find(buf, n, open, ">" as *u8, 1)
193 if gt < 0 { pos = n } else {
194 let close: i64 = jsb_find(buf, n, gt, "</script" as *u8, 8)
195 var end: i64 = n
196 if close >= 0 { end = close }
197 let start: i64 = gt + 1
198 if end > start { if end - start > best_e - best_s {
199 best_s = start
200 best_e = end
201 } }
202 pos = end + 8
203 }
204 }
205 }
206 if best_s < 0 { return JSB_NO_SCRIPT }
207 return jsb_check((buf as i64 + best_s) as *u8, best_e - best_s, out)
208}