code wiki / (root) / nx_lsp_gate.nx

nx_lsp_gate.nx source

↩ module page · 573 lines · 25971 B

1// ============================================================================================ 2// nx_lsp_gate.nx -- the referee for LN11 (nx_lsp, the NishiLang language server). 3// 4// WHAT IT PROVES, and why each half is load-bearing: 5// A CLEAN document must publish ZERO diagnostics and a BROKEN one must publish exactly ONE at the 6// right line and column. Either half alone is trivially passable: a server that always reports an 7// error passes the broken case, and a server that never reports one passes the clean case. Only 8// the PAIR discriminates, so the pairing is a gv_bite cell and not two independent teeth. 9// 10// The UTF-16 column is checked against a byte column that is DIFFERENT, on a line built at runtime 11// with multi-byte characters before the offending name. A server that passed byte columns through 12// would satisfy "character is a plausible number" and fail this. That is the anti-vacuity tooth. 13// 14// The transcript is a REAL client: a recorded byte stream fed to `nx_lsp serve` on stdin with 15// stdout captured. Nothing is stubbed, and the framing validator re-derives every Content-Length 16// from the bytes that followed it -- an escaping bug that shortens a body cannot hide behind a 17// self-consistent header. 18// 19// DELIBERATE DUPLICATION, DECLARED: this gate carries its OWN substring search, its OWN JSON string 20// escaper and its OWN UTF-16 counter. That is not the duplicate-ruler defect -- it is the opposite. 21// A test that measures the subject with the subject's own helpers cannot catch a defect IN those 22// helpers, and the escaper and the column arithmetic are exactly where the interesting defects live. 23// 24// FIXTURES ARE ASSEMBLED AT RUNTIME in /tmp/nx_lsp_gate/ -- never checked in, never shared with a 25// production beat, and never written into the estate's own source tree where a detector that scans 26// sources would find this gate's own bad input and report it as a real defect. The multi-byte 27// characters are CONSTRUCTED FROM BYTES rather than written as literals: a source file crosses 28// several transports before it reaches a compiler and each of them can re-encode a literal. 29// license_tier: ORIGINAL No hardware writes (Rule 26). 30// ============================================================================================ 31 32import "nx_syscalls.nx" 33import "nx_gate_verdict.nx" 34 35const GL_LF: i64 = 10 36const GL_CR: i64 = 13 37const GL_QUOTE: i64 = 34 38const GL_D0: i64 = 48 39const GL_D9: i64 = 57 40const GL_BSLASH: i64 = 92 41const GL_B10: i64 = 10 42const GL_WORD: i64 = 8 43const GL_U8_2: i64 = 192 44const GL_U8_3: i64 = 224 45const GL_U8_4: i64 = 240 46// The two bytes of U+00E9 in UTF-8. Named so the fixture's intent survives any transport. 47const GL_E9_HI: i64 = 195 48const GL_E9_LO: i64 = 169 49const GL_LBRACE: i64 = 123 50const GL_RBRACE: i64 = 125 51const GL_RBRACK: i64 = 93 52const GL_MINUS: i64 = 45 53const GL_ESC_N: i64 = 110 54const GL_ESC_R: i64 = 114 55const GL_ESC_T: i64 = 116 56const GL_TAB: i64 = 9 57const GL_SEPLEN: i64 = 4 58const GL_DIAGS_KEYLEN: i64 = 15 59// Scratch sizes for THIS gate's own buffers. Fixtures are a few hundred bytes and the capture is a 60// handful of LSP frames; these are generous by orders of magnitude and every writer is bounded. 61const GL_BUF: i64 = 262144 62const GL_PATH: i64 = 1024 63const GL_SMALL: i64 = 4096 64 65func gl_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 66 67func gl_find(b: *u8, n: i64, start: i64, pat: *u8) -> i64 { 68 let pl: i64 = gl_len(pat) 69 if pl == 0 { return 0 - 1 } 70 var i: i64 = start 71 if i < 0 { i = 0 } 72 while i + pl <= n { 73 var j: i64 = 0 74 var ok: i64 = 1 75 while j < pl { 76 if b[i + j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } 77 } 78 if ok == 1 { return i } 79 i = i + 1 80 } 81 return 0 - 1 82} 83 84func gl_count(b: *u8, n: i64, pat: *u8) -> i64 { 85 var c: i64 = 0 86 var at: i64 = 0 87 var run: i64 = 1 88 while run == 1 { 89 let f: i64 = gl_find(b, n, at, pat) 90 if f < 0 { run = 0 } else { c = c + 1; at = f + 1 } 91 } 92 return c 93} 94 95// first decimal integer following `key` at/after `start`; -1 when the key is absent. 96func gl_int_after(b: *u8, n: i64, start: i64, key: *u8) -> i64 { 97 let at: i64 = gl_find(b, n, start, key) 98 if at < 0 { return 0 - 1 } 99 var p: i64 = at + gl_len(key) 100 var neg: i64 = 0 101 if p < n { if b[p] == (GL_MINUS as u8) { neg = 1; p = p + 1 } } 102 var v: i64 = 0 103 var got: i64 = 0 104 var run: i64 = 1 105 while run == 1 { 106 if p >= n { run = 0 } else { 107 let c: i64 = b[p] as i64 108 if c < GL_D0 { run = 0 } else { 109 if c > GL_D9 { run = 0 } else { v = v * GL_B10 + (c - GL_D0); got = got + 1; p = p + 1 } } 110 } 111 } 112 if got == 0 { return 0 - 1 } 113 if neg == 1 { return 0 - v } 114 return v 115} 116 117// THE GATE'S OWN UTF-16 COUNTER. Independent of the subject's, on purpose. 118func gl_u16(b: *u8, n: i64, upto: i64) -> i64 { 119 var u: i64 = 0 120 var i: i64 = 0 121 var run: i64 = 1 122 while run == 1 { 123 if i >= upto { run = 0 } else { 124 if i >= n { run = 0 } else { 125 let c: i64 = b[i] as i64 126 var adv: i64 = 1 127 var units: i64 = 1 128 if c >= GL_U8_4 { adv = 4; units = 2 } else { 129 if c >= GL_U8_3 { adv = 3 } else { 130 if c >= GL_U8_2 { adv = 2 } } } 131 u = u + units 132 i = i + adv 133 } 134 } 135 } 136 return u 137} 138 139func gl_write_file(path: *u8, buf: *u8, n: i64) -> i64 { 140 let fd: i64 = sys_openat_wr(path, MODE_0644) 141 if fd < 0 { return 0 - 1 } 142 var off: i64 = 0 143 var run: i64 = 1 144 while run == 1 { 145 if off >= n { run = 0 } else { 146 let w: i64 = sys_write(fd, ((buf as i64) + off) as *u8, n - off) 147 if w <= 0 { run = 0 } else { off = off + w } 148 } 149 } 150 sys_close(fd) 151 if off != n { return 0 - 1 } 152 return 0 153} 154 155func gl_exists(path: *u8) -> i64 { 156 let fd: i64 = sys_openat_rd(path) 157 if fd < 0 { return 0 } 158 sys_close(fd) 159 return 1 160} 161 162// THE GATE'S OWN JSON STRING ESCAPER (client side). 163func gl_jesc(d: *u8, o: i64, s: *u8, n: i64) -> i64 { 164 var p: i64 = o 165 var i: i64 = 0 166 while i < n { 167 let c: i64 = s[i] as i64 168 if c == GL_QUOTE { d[p] = GL_BSLASH as u8; p = p + 1; d[p] = GL_QUOTE as u8; p = p + 1 } else { 169 if c == GL_BSLASH { d[p] = GL_BSLASH as u8; p = p + 1; d[p] = GL_BSLASH as u8; p = p + 1 } else { 170 if c == GL_LF { d[p] = GL_BSLASH as u8; p = p + 1; d[p] = GL_ESC_N as u8; p = p + 1 } else { 171 if c == GL_CR { d[p] = GL_BSLASH as u8; p = p + 1; d[p] = GL_ESC_R as u8; p = p + 1 } else { 172 if c == GL_TAB { d[p] = GL_BSLASH as u8; p = p + 1; d[p] = GL_ESC_T as u8; p = p + 1 } else { 173 d[p] = s[i]; p = p + 1 174 } } } } } 175 i = i + 1 176 } 177 return p 178} 179 180// Content-Length framing, written the way a real client writes it. 181func gl_frame(d: *u8, o: i64, body: *u8, bn: i64) -> i64 { 182 var p: i64 = gv_cat(d, o, "Content-Length: " as *u8) 183 p = gv_catn(d, p, bn) 184 d[p] = GL_CR as u8; p = p + 1 185 d[p] = GL_LF as u8; p = p + 1 186 d[p] = GL_CR as u8; p = p + 1 187 d[p] = GL_LF as u8; p = p + 1 188 var i: i64 = 0 189 while i < bn { d[p] = body[i]; p = p + 1; i = i + 1 } 190 return p 191} 192 193func gl_spawn(elf: *u8, argv: *i64, in_path: *u8, out_path: *u8) -> i64 { 194 let pid: i64 = sys_fork() 195 if pid < 0 { return 0 - 1 } 196 if pid == 0 { 197 if (in_path as i64) != 0 { 198 let ifd: i64 = sys_openat_rd(in_path) 199 if ifd >= 0 { sys_dup3(ifd, 0, 0); sys_close(ifd) } 200 } 201 if (out_path as i64) != 0 { 202 let ofd: i64 = sys_openat_wr(out_path, MODE_0644) 203 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_close(ofd) } 204 } 205 let envp: *i64 = sys_mmap(GL_WORD * 2) as *i64 206 envp[0] = 0 207 sys_execve(elf, argv, envp) 208 sys_exit(127) 209 } 210 let st: *i64 = sys_mmap(GL_WORD * 2) as *i64 211 st[0] = 0 212 sys_wait4(pid, st, 0) 213 return wait_status_rc(st[0]) 214} 215 216func gl_slurp(path: *u8, len_out: *i64) -> *u8 { 217 len_out[0] = 0 218 let b: *u8 = sys_read_file(path, len_out) 219 if (b as i64) == 0 { len_out[0] = 0 } 220 return b 221} 222 223// EVERY Content-Length in the capture must be followed by exactly that many body bytes, and the next 224// thing after a body must be another header or end-of-stream. Returns the frame count, or -1 when 225// the framing does not hold. A self-consistent-but-wrong length is precisely what an escaping bug 226// produces, so re-deriving from the bytes is the only check worth having. 227func gl_frames_valid(b: *u8, n: i64) -> i64 { 228 var pos: i64 = 0 229 var frames: i64 = 0 230 var run: i64 = 1 231 while run == 1 { 232 if pos >= n { run = 0 } else { 233 let at: i64 = gl_find(b, n, pos, "Content-Length: " as *u8) 234 if at != pos { run = 0; frames = 0 - 1 } else { 235 let clen: i64 = gl_int_after(b, n, pos, "Content-Length: " as *u8) 236 if clen <= 0 { run = 0; frames = 0 - 1 } else { 237 let sep: i64 = gl_find(b, n, pos, "\r\n\r\n" as *u8) 238 if sep < 0 { run = 0; frames = 0 - 1 } else { 239 let bs: i64 = sep + GL_SEPLEN 240 if bs + clen > n { run = 0; frames = 0 - 1 } else { 241 var okb: i64 = 1 242 if b[bs] != (GL_LBRACE as u8) { okb = 0 } 243 if b[bs + clen - 1] != (GL_RBRACE as u8) { okb = 0 } 244 if okb == 0 { run = 0; frames = 0 - 1 } else { 245 frames = frames + 1 246 pos = bs + clen 247 } 248 } 249 } 250 } 251 } 252 } 253 } 254 return frames 255} 256 257func main() -> i64 { 258 let ctr: *i64 = gv_ctr() 259 gv_head("nx_lsp_gate -- LN11: LSP initialize plus a diagnostics round-trip against a recorded client transcript, with goto-definition and hover over the compiler's own code graph" as *u8) 260 261 // ---- SETUP. Scratch is /tmp/<gate>/, created here: a teardown does not run when a run crashes, 262 // ---- and a gate that shares a fixture with a production beat measures the beat, not the code. 263 let dir: *u8 = sys_mmap(GL_PATH) 264 dir[gv_cat(dir, 0, "/tmp/nx_lsp_gate" as *u8)] = 0 as u8 265 sys_mkdir(dir, MODE_0755) 266 267 let p_helper: *u8 = sys_mmap(GL_PATH) 268 let p_ok: *u8 = sys_mmap(GL_PATH) 269 let p_bad: *u8 = sys_mmap(GL_PATH) 270 let p_u16: *u8 = sys_mmap(GL_PATH) 271 let p_tr: *u8 = sys_mmap(GL_PATH) 272 let p_cap: *u8 = sys_mmap(GL_PATH) 273 let p_out: *u8 = sys_mmap(GL_PATH) 274 p_helper[gv_cat(p_helper, 0, "/tmp/nx_lsp_gate/helper.nx" as *u8)] = 0 as u8 275 p_ok[gv_cat(p_ok, 0, "/tmp/nx_lsp_gate/main_ok.nx" as *u8)] = 0 as u8 276 p_bad[gv_cat(p_bad, 0, "/tmp/nx_lsp_gate/main_bad.nx" as *u8)] = 0 as u8 277 p_u16[gv_cat(p_u16, 0, "/tmp/nx_lsp_gate/main_u16.nx" as *u8)] = 0 as u8 278 p_tr[gv_cat(p_tr, 0, "/tmp/nx_lsp_gate/client.transcript" as *u8)] = 0 as u8 279 p_cap[gv_cat(p_cap, 0, "/tmp/nx_lsp_gate/server.capture" as *u8)] = 0 as u8 280 p_out[gv_cat(p_out, 0, "/tmp/nx_lsp_gate/oneshot.out" as *u8)] = 0 as u8 281 282 // SUBJECT RESOLUTION, PRINTED NOT ASSUMED. Runners fork _offc/, /api/promote writes the serving 283 // root, and a mutation bite refreshes both -- so prefer the twin the runners use and fall back to 284 // the promoted binary. Either way the path is on the record: a gate that does not say WHICH 285 // binary it measured cannot be re-run against the same subject by the next reader. 286 let elf: *u8 = sys_mmap(GL_PATH) 287 elf[gv_cat(elf, 0, "_offc/nx_lsp.elf" as *u8)] = 0 as u8 288 if gl_exists(elf) == 0 { elf[gv_cat(elf, 0, "nx_lsp.elf" as *u8)] = 0 as u8 } 289 gv_puts(" subject: " as *u8) gv_puts(elf) gv_puts("\n" as *u8) 290 291 // ---- FIXTURES, assembled here so no bad input is ever a checked-in source byte. 292 let fx: *u8 = sys_mmap(GL_BUF) 293 let fn: i64 = gv_cat(fx, 0, "func helper_add(a: i64, b: i64) -> i64 {\n return a + b\n}\n" as *u8) 294 gl_write_file(p_helper, fx, fn) 295 296 let ok_txt: *u8 = sys_mmap(GL_BUF) 297 let ok_n: i64 = gv_cat(ok_txt, 0, "import \"helper.nx\"\nfunc main() -> i64 {\n let x: i64 = helper_add(1, 2)\n return x\n}\n" as *u8) 298 ok_txt[ok_n] = 0 as u8 299 gl_write_file(p_ok, ok_txt, ok_n) 300 301 let bad_txt: *u8 = sys_mmap(GL_BUF) 302 let bad_n: i64 = gv_cat(bad_txt, 0, "import \"helper.nx\"\nfunc main() -> i64 {\n let x: i64 = helper_addd(1, 2)\n return x\n}\n" as *u8) 303 bad_txt[bad_n] = 0 as u8 304 gl_write_file(p_bad, bad_txt, bad_n) 305 306 // ONE LINE, with three two-byte characters before the undefined name, so the UTF-16 column and 307 // the byte column are provably different numbers. Bytes, not literals -- see the header. 308 let u16_txt: *u8 = sys_mmap(GL_BUF) 309 var un0: i64 = gv_cat(u16_txt, 0, "func main() -> i64 { let s: *u8 = \"" as *u8) 310 var e: i64 = 0 311 while e < 3 { 312 u16_txt[un0] = GL_E9_HI as u8; un0 = un0 + 1 313 u16_txt[un0] = GL_E9_LO as u8; un0 = un0 + 1 314 e = e + 1 315 } 316 let u16_n: i64 = gv_cat(u16_txt, un0, "\" return zzz(1) }\n" as *u8) 317 u16_txt[u16_n] = 0 as u8 318 gl_write_file(p_u16, u16_txt, u16_n) 319 320 var setup_ok: i64 = 0 321 if gl_exists(p_helper) == 1 { if gl_exists(p_ok) == 1 { if gl_exists(p_bad) == 1 { 322 if gl_exists(p_u16) == 1 { setup_ok = 1 } } } } 323 gv_need("fixtures written to /tmp/nx_lsp_gate" as *u8, setup_ok, ctr) 324 gv_need("subject binary _offc/nx_lsp.elf is present" as *u8, gl_exists(elf), ctr) 325 326 // ---- expected positions, DERIVED from the fixture bytes rather than written down. 327 let bad_line: i64 = 2 328 let bad_at: i64 = gl_find(bad_txt, bad_n, 0, "helper_addd" as *u8) 329 var bad_ls: i64 = 0 330 var q: i64 = 0 331 while q < bad_at { if bad_txt[q] == (GL_LF as u8) { bad_ls = q + 1 } q = q + 1 } 332 let bad_col_bytes: i64 = bad_at - bad_ls 333 let bad_name_len: i64 = gl_len("helper_addd" as *u8) 334 335 let u16_at: i64 = gl_find(u16_txt, u16_n, 0, "zzz" as *u8) 336 let u16_expect: i64 = gl_u16(u16_txt, u16_n, u16_at) 337 338 let ok_at: i64 = gl_find(ok_txt, ok_n, 0, "helper_add(1" as *u8) 339 var ok_ls: i64 = 0 340 q = 0 341 while q < ok_at { if ok_txt[q] == (GL_LF as u8) { ok_ls = q + 1 } q = q + 1 } 342 let ok_col: i64 = ok_at - ok_ls 343 344 gv_puts(" derived: bad_line0=" as *u8) gv_num(bad_line) 345 gv_puts(" bad_col_bytes=" as *u8) gv_num(bad_col_bytes) 346 gv_puts(" u16_byte_col=" as *u8) gv_num(u16_at) 347 gv_puts(" u16_expect=" as *u8) gv_num(u16_expect) 348 gv_puts(" def_col=" as *u8) gv_num(ok_col) 349 gv_puts("\n" as *u8) 350 351 // ---- ONE-SHOT DIAGNOSTICS ----------------------------------------------------------------- 352 let argv3: *i64 = sys_mmap(GL_WORD * 8) as *i64 353 let a_diag: *u8 = sys_mmap(GL_SMALL) 354 a_diag[gv_cat(a_diag, 0, "diag" as *u8)] = 0 as u8 355 let a_syms: *u8 = sys_mmap(GL_SMALL) 356 a_syms[gv_cat(a_syms, 0, "symbols" as *u8)] = 0 as u8 357 let a_serve: *u8 = sys_mmap(GL_SMALL) 358 a_serve[gv_cat(a_serve, 0, "serve" as *u8)] = 0 as u8 359 360 let lenp: *i64 = sys_mmap(GL_WORD * 2) as *i64 361 362 argv3[0] = elf as i64 363 argv3[1] = a_diag as i64 364 argv3[2] = p_ok as i64 365 argv3[3] = 0 366 gl_spawn(elf, argv3, 0 as *u8, p_out) 367 let cb: *u8 = gl_slurp(p_out, lenp) 368 let cn: i64 = lenp[0] 369 let clean_diags: i64 = gl_count(cb, cn, "\"severity\":" as *u8) 370 371 argv3[2] = p_bad as i64 372 gl_spawn(elf, argv3, 0 as *u8, p_out) 373 let bb: *u8 = gl_slurp(p_out, lenp) 374 let bn2: i64 = lenp[0] 375 let bad_diags: i64 = gl_count(bb, bn2, "\"severity\":" as *u8) 376 let bad_start: i64 = gl_find(bb, bn2, 0, "\"start\":" as *u8) 377 var got_line: i64 = 0 - 1 378 var got_char: i64 = 0 - 1 379 if bad_start >= 0 { 380 got_line = gl_int_after(bb, bn2, bad_start, "\"line\":" as *u8) 381 got_char = gl_int_after(bb, bn2, bad_start, "\"character\":" as *u8) 382 } 383 let msg_names_it: i64 = gl_find(bb, bn2, 0, "helper_addd" as *u8) 384 385 gv_puts(" measured: clean_diags=" as *u8) gv_num(clean_diags) 386 gv_puts(" bad_diags=" as *u8) gv_num(bad_diags) 387 gv_puts(" line=" as *u8) gv_num(got_line) 388 gv_puts(" character=" as *u8) gv_num(got_char) 389 gv_puts("\n" as *u8) 390 391 var bad_fires: i64 = 0 392 if bad_diags == 1 { bad_fires = 1 } 393 gv_bite("neg-control-clean-document-silent-and-broken-document-fires (either half alone is passable by a server that always, or never, reports an error)" as *u8, bad_fires, clean_diags, ctr) 394 395 var line_ok: i64 = 0 396 if got_line == bad_line { line_ok = 1 } 397 gv_check("T1 the diagnostic lands on the offending line, mapped back through the compiler LineMap from the import-expanded unit" as *u8, line_ok, ctr) 398 399 var col_ok: i64 = 0 400 if got_char >= bad_col_bytes { if got_char <= bad_col_bytes + bad_name_len { col_ok = 1 } } 401 gv_check("T2 the diagnostic column falls inside the offending identifier, derived from the caret the compiler drew" as *u8, col_ok, ctr) 402 403 var msg_ok: i64 = 0 404 if msg_names_it >= 0 { msg_ok = 1 } 405 gv_check("T3 the published message is the compiler's own text and names the offending symbol" as *u8, msg_ok, ctr) 406 407 var one_ok: i64 = 0 408 if bad_diags == 1 { one_ok = 1 } 409 gv_check("T4 exactly ONE diagnostic for one error -- an import-expanded unit must not attribute the imported file's contents to this document" as *u8, one_ok, ctr) 410 411 // ---- UTF-16 COLUMN ------------------------------------------------------------------------- 412 argv3[2] = p_u16 as i64 413 gl_spawn(elf, argv3, 0 as *u8, p_out) 414 let ub: *u8 = gl_slurp(p_out, lenp) 415 let un: i64 = lenp[0] 416 let u_start: i64 = gl_find(ub, un, 0, "\"start\":" as *u8) 417 var u_char: i64 = 0 - 1 418 if u_start >= 0 { u_char = gl_int_after(ub, un, u_start, "\"character\":" as *u8) } 419 gv_puts(" measured: utf16_character=" as *u8) gv_num(u_char) 420 gv_puts(" byte_column=" as *u8) gv_num(u16_at) 421 gv_puts(" expected_utf16=" as *u8) gv_num(u16_expect) 422 gv_puts("\n" as *u8) 423 424 var u_ok: i64 = 0 425 if u_char == u16_expect { u_ok = 1 } 426 gv_check("T5 the published character is the UTF-16 code-unit column LSP requires" as *u8, u_ok, ctr) 427 428 var u_not_bytes: i64 = 0 429 if u16_expect < u16_at { if u_char != u16_at { u_not_bytes = 1 } } 430 gv_check("neg-control-utf16-column-is-not-the-byte-column (three two-byte characters precede the name, so a server passing bytes through cannot satisfy this)" as *u8, u_not_bytes, ctr) 431 432 // ---- THE RECORDED CLIENT TRANSCRIPT -------------------------------------------------------- 433 let tr: *u8 = sys_mmap(GL_BUF) 434 let bodyb: *u8 = sys_mmap(GL_BUF) 435 var t: i64 = 0 436 var bo: i64 = 0 437 438 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"processId\":null,\"capabilities\":{}}}" as *u8) 439 t = gl_frame(tr, t, bodyb, bo) 440 441 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}" as *u8) 442 t = gl_frame(tr, t, bodyb, bo) 443 444 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///tmp/nx_lsp_gate/main_ok.nx\",\"languageId\":\"nishi\",\"version\":1,\"text\":\"" as *u8) 445 bo = gl_jesc(bodyb, bo, ok_txt, ok_n) 446 bo = gv_cat(bodyb, bo, "\"}}}" as *u8) 447 t = gl_frame(tr, t, bodyb, bo) 448 449 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"textDocument/definition\",\"params\":{\"textDocument\":{\"uri\":\"file:///tmp/nx_lsp_gate/main_ok.nx\"},\"position\":{\"line\":2,\"character\":" as *u8) 450 bo = gv_catn(bodyb, bo, ok_col) 451 bo = gv_cat(bodyb, bo, "}}}" as *u8) 452 t = gl_frame(tr, t, bodyb, bo) 453 454 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///tmp/nx_lsp_gate/main_ok.nx\"},\"position\":{\"line\":2,\"character\":" as *u8) 455 bo = gv_catn(bodyb, bo, ok_col) 456 bo = gv_cat(bodyb, bo, "}}}" as *u8) 457 t = gl_frame(tr, t, bodyb, bo) 458 459 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"textDocument/completion\",\"params\":{}}" as *u8) 460 t = gl_frame(tr, t, bodyb, bo) 461 462 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"shutdown\",\"params\":null}" as *u8) 463 t = gl_frame(tr, t, bodyb, bo) 464 465 bo = gv_cat(bodyb, 0, "{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}" as *u8) 466 t = gl_frame(tr, t, bodyb, bo) 467 468 gl_write_file(p_tr, tr, t) 469 470 argv3[1] = a_serve as i64 471 argv3[2] = 0 472 let srv_rc: i64 = gl_spawn(elf, argv3, p_tr, p_cap) 473 let sb: *u8 = gl_slurp(p_cap, lenp) 474 let sn: i64 = lenp[0] 475 let frames: i64 = gl_frames_valid(sb, sn) 476 477 gv_puts(" measured: transcript_bytes=" as *u8) gv_num(t) 478 gv_puts(" capture_bytes=" as *u8) gv_num(sn) 479 gv_puts(" frames=" as *u8) gv_num(frames) 480 gv_puts(" server_exit=" as *u8) gv_num(srv_rc) 481 gv_puts("\n" as *u8) 482 483 // BIND EVERY AGGREGATE ASSERTION TO ITS DENOMINATOR. Zero answered frames is not a pass. 484 gv_subjects("LSP frames the server actually emitted" as *u8, frames, ctr) 485 486 var frame_ok: i64 = 0 487 if frames > 0 { frame_ok = 1 } 488 gv_check("T6 every Content-Length in the capture is followed by exactly that many bytes of a complete JSON object, re-derived from the bytes rather than trusted" as *u8, frame_ok, ctr) 489 490 var init_ok: i64 = 0 491 if gl_find(sb, sn, 0, "\"textDocumentSync\":1" as *u8) >= 0 { 492 if gl_find(sb, sn, 0, "\"definitionProvider\":true" as *u8) >= 0 { 493 if gl_find(sb, sn, 0, "\"hoverProvider\":true" as *u8) >= 0 { init_ok = 1 } } } 494 gv_check("T7 initialize returns ServerCapabilities advertising exactly the three surfaces this server implements" as *u8, init_ok, ctr) 495 496 var no_lies: i64 = 1 497 if gl_find(sb, sn, 0, "completionProvider" as *u8) >= 0 { no_lies = 0 } 498 if gl_find(sb, sn, 0, "renameProvider" as *u8) >= 0 { no_lies = 0 } 499 if gl_find(sb, sn, 0, "documentSymbolProvider" as *u8) >= 0 { no_lies = 0 } 500 if gl_find(sb, sn, 0, "referencesProvider" as *u8) >= 0 { no_lies = 0 } 501 gv_check("neg-control-initialize-advertises-nothing-it-does-not-implement (a client greys out its own fallback on a false capability, so the user gets silence instead of a feature)" as *u8, no_lies, ctr) 502 503 var pub_ok: i64 = 0 504 if gl_find(sb, sn, 0, "\"method\":\"textDocument/publishDiagnostics\"" as *u8) >= 0 { 505 if gl_find(sb, sn, 0, "main_ok.nx" as *u8) >= 0 { pub_ok = 1 } 506 } 507 gv_check("T8 didOpen drove an unsolicited publishDiagnostics for the opened document -- the round trip this rung names" as *u8, pub_ok, ctr) 508 509 let pub_at: i64 = gl_find(sb, sn, 0, "\"diagnostics\":[" as *u8) 510 var pub_empty: i64 = 0 511 if pub_at >= 0 { 512 if sb[pub_at + GL_DIAGS_KEYLEN] == (GL_RBRACK as u8) { pub_empty = 1 } 513 } 514 gv_check("T9 the clean document that IMPORTS A SIBLING publishes an EMPTY diagnostic list -- proving the dirty-buffer shadow resolves the same import set a real build would" as *u8, pub_empty, ctr) 515 516 var def_ok: i64 = 0 517 let def_at: i64 = gl_find(sb, sn, 0, "\"id\":2" as *u8) 518 if def_at >= 0 { 519 if gl_find(sb, sn, def_at, "helper.nx" as *u8) >= 0 { def_ok = 1 } 520 } 521 gv_check("T10 goto-definition resolved ACROSS A FILE BOUNDARY to the declaring file, over the LineMap the compiler itself built" as *u8, def_ok, ctr) 522 523 var hov_ok: i64 = 0 524 let hov_at: i64 = gl_find(sb, sn, 0, "\"id\":3" as *u8) 525 if hov_at >= 0 { 526 if gl_find(sb, sn, hov_at, "helper_add" as *u8) >= 0 { hov_ok = 1 } 527 } 528 gv_check("T11 hover returned the declaration of the symbol under the cursor" as *u8, hov_ok, ctr) 529 530 var unknown_ok: i64 = 0 531 let unk_at: i64 = gl_find(sb, sn, 0, "\"id\":9" as *u8) 532 if unk_at >= 0 { 533 if gl_find(sb, sn, unk_at, "-32601" as *u8) >= 0 { unknown_ok = 1 } 534 } 535 gv_check("neg-control-an-unimplemented-request-is-answered-with-an-error-not-a-fabricated-result" as *u8, unknown_ok, ctr) 536 537 var shut_ok: i64 = 0 538 if srv_rc == 0 { 539 if gl_find(sb, sn, 0, "\"id\":4,\"result\":null" as *u8) >= 0 { shut_ok = 1 } 540 } 541 gv_check("T12 shutdown then exit leaves the process at LSP exit code 0 -- exit without a prior shutdown must be 1, so the code carries the handshake" as *u8, shut_ok, ctr) 542 543 var no_shadow_leak: i64 = 1 544 if gl_find(sb, sn, 0, ".nxlsp" as *u8) >= 0 { no_shadow_leak = 0 } 545 gv_check("neg-control-no-shadow-path-reaches-the-client (a Location under the dirty-buffer shadow would open a file the user does not have)" as *u8, no_shadow_leak, ctr) 546 547 // ---- THE SYMBOL INDEX ARTIFACT ------------------------------------------------------------- 548 argv3[1] = a_syms as i64 549 argv3[2] = p_ok as i64 550 argv3[3] = 0 551 gl_spawn(elf, argv3, 0 as *u8, p_out) 552 let yb: *u8 = gl_slurp(p_out, lenp) 553 let yn: i64 = lenp[0] 554 let nsyms: i64 = gl_int_after(yb, yn, 0, "syms=" as *u8) 555 let cov: i64 = gl_int_after(yb, yn, 0, "coverage_complete=" as *u8) 556 let unmapped: i64 = gl_int_after(yb, yn, 0, "unmapped=" as *u8) 557 gv_puts(" measured: syms=" as *u8) gv_num(nsyms) 558 gv_puts(" unmapped=" as *u8) gv_num(unmapped) 559 gv_puts(" coverage_complete=" as *u8) gv_num(cov) 560 gv_puts("\n" as *u8) 561 562 var cov_ok: i64 = 0 563 if cov == 1 { if nsyms > 0 { cov_ok = 1 } } 564 gv_check("T13 the symbol index declares complete coverage AND a non-zero population -- a coverage flag over an empty index is the empty-set pass" as *u8, cov_ok, ctr) 565 566 var xfile_ok: i64 = 0 567 if gl_find(yb, yn, 0, "helper_add" as *u8) >= 0 { 568 if gl_find(yb, yn, 0, "helper.nx" as *u8) >= 0 { xfile_ok = 1 } 569 } 570 gv_check("T14 the index carries the imported file's declarations with THEIR OWN path and line, not the expanded unit's" as *u8, xfile_ok, ctr) 571 572 return gv_verdict("LSP-GATE" as *u8, ctr, "LN11 language server" as *u8) 573}