code wiki / (root) / nx_jsbalance.nx

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}