code wiki / (root) / nx_stubmain_lib.nx

nx_stubmain_lib.nx source

↩ module page · 210 lines · 9169 B

1// nx_stubmain_lib.nx -- THE STUB-MAIN RULER. One predicate, so the writer and the reader of this 2// classification cannot drift apart. 3// 4// THE DEFECT IT NAMES, MEASURED 2026-09-03. buildroot/runtime/nx_https_get.nx ends with 5// `// Compile-only smoke.` and `func main() -> i64 { return 0 }`. Nothing is reachable from that main, 6// so the compiler DEAD-STRIPS the entire TLS closure: /api/build returns action=BUILT src_stable=1 with 7// no error and stages 14,880 bytes against a LIVE 468,929-byte binary. Any seat running the ordinary 8// build-then-promote loop replaces the estate's sovereign HTTPS client with a binary that returns 0 -- 9// silently, off a GREEN receipt. 10// 11// ★THE SHARP PART, AND THE REASON THIS NEEDS ITS OWN RULER: nx_catalog ALREADY detects libraries, by 12// "no top-level main()" -- nx_https_fetch reads `KIND LIB (a binary is NOT expected for this one)` and 13// is therefore safe from this whole class. nx_https_get EVADES that detector PRECISELY BECAUSE IT HAS 14// A MAIN. A stub one. So the lib detector is defeated by the very convention that lets a library 15// compile standalone, and the catalogue then reports the name as LIVE because an .elf sits beside it. 16// THREE STATES, NEVER TWO -- collapsing them is the whole bug: 17// SM_NO_MAIN a library by construction. nx_catalog already handles it. NOT this ruler's subject. 18// SM_STUB_MAIN a main that returns a constant and calls nothing. Compiles, links nothing, ships a no-op. 19// SM_REAL_MAIN a program. 20// 21// WHAT IT DELIBERATELY DOES NOT DECIDE. Being SM_STUB_MAIN is NOT by itself a defect: it is a deliberate 22// estate convention ("compile-only smoke") and a census over 12,067 sources found 114 of them, of which 23// every one checked was correctly unpromoted. The ARMED condition is the CONJUNCTION -- stub main AND a 24// promoted binary somebody calls -- and the promoted half is nx_artifactdrift's to answer, because that 25// organ already walks the serving root and already resolves sources through PS_SRCROOT_A then 26// PS_SRCROOT_B in builder order. This lib exists so that adoption is one predicate call and NOT a second 27// tree walker: the estate has one drift census and must keep having one. 28// 29// NO SIZE THRESHOLD, ON PURPOSE. The tempting rule is "refuse a candidate much smaller than live", but 30// that is a magic number (rule 11) and it fails in both directions -- a legitimate toolchain shrink of 31// 304 permil is on record, and a stub of a SMALL program is not much smaller than the real thing. The 32// structural fact (main calls nothing) is the signal; size is at most a triage hint. 33// 34// license_tier: ORIGINAL 35import "nx_syscalls.nx" 36 37const SM_NO_MAIN: i64 = 0 38const SM_STUB_MAIN: i64 = 1 39const SM_REAL_MAIN: i64 = 2 40const SM_UNREADABLE: i64 = 0 - 1 41 42const SM_NL: i64 = 10 43const SM_CR: i64 = 13 44const SM_SP: i64 = 32 45const SM_TAB: i64 = 9 46const SM_SLASH: i64 = 47 47const SM_LBRACE: i64 = 123 48const SM_RBRACE: i64 = 125 49const SM_MINUS: i64 = 45 50const SM_D0: i64 = 48 51const SM_D9: i64 = 57 52 53func sm_is_space(c: i64) -> i64 { 54 if c == SM_SP { return 1 } 55 if c == SM_TAB { return 1 } 56 return 0 57} 58 59func sm_streq_at(buf: *u8, s: i64, e: i64, lit: *u8) -> i64 { 60 var k: i64 = 0 61 while lit[k] != (0 as u8) { 62 if s + k >= e { return 0 } 63 if buf[s + k] != lit[k] { return 0 } 64 k = k + 1 65 } 66 return 1 67} 68 69func sm_find(buf: *u8, s: i64, e: i64, lit: *u8) -> i64 { 70 var ll: i64 = 0 71 while lit[ll] != (0 as u8) { ll = ll + 1 } 72 if ll == 0 { return 0 - 1 } 73 var i: i64 = s 74 while i + ll <= e { 75 if sm_streq_at(buf, i, e, lit) == 1 { return i } 76 i = i + 1 77 } 78 return 0 - 1 79} 80 81// Find `lit` ONLY where it begins a LINE (index 0, or immediately after a newline). Returns its start 82// or -1. 83// 84// ⚠THIS EXISTS BECAUSE THE FIRST DRAFT DID NOT HAVE IT AND THIS GATE'S LIVE CONTROLS CAUGHT IT. 85// A plain substring search matched `func main` inside a COMMENT -- including the comment in THIS FILE's 86// own header, which quotes `func main() -> i64 { return 0 }` while explaining the defect. The ruler then 87// classified itself, and nx_https_fetch, as STUB when both are libraries with no main at all. That is 88// the estate's recorded law arriving in person: A RULE THAT CANNOT TELL CODE FROM THE COMMENT DESCRIBING 89// IT WILL FLAG EVERY EXPLANATION OF THE BUG IT HUNTS. Line-anchoring is sufficient and cheap here 90// because NishiLang top-level declarations begin at column 0 and a comment line begins with '//', so no 91// comment can present a line-anchored `func main`. 92func sm_find_line_start(buf: *u8, n: i64, lit: *u8) -> i64 { 93 var i: i64 = 0 94 while i < n { 95 var at_line_start: i64 = 0 96 if i == 0 { at_line_start = 1 } else { 97 if ((buf[i - 1] as i64) & 255) == SM_NL { at_line_start = 1 } 98 } 99 if at_line_start == 1 { 100 if sm_streq_at(buf, i, n, lit) == 1 { return i } 101 } 102 i = i + 1 103 } 104 return 0 - 1 105} 106 107// Index just past the first '{' at or after s, or -1. 108func sm_open_brace(buf: *u8, s: i64, e: i64) -> i64 { 109 var i: i64 = s 110 while i < e { 111 if ((buf[i] as i64) & 255) == SM_LBRACE { return i + 1 } 112 i = i + 1 113 } 114 return 0 - 1 115} 116 117// Matching '}' for a body that starts at s (depth already 1). Returns its index, or -1. 118// Brace-counted rather than "first } at column 0", because a nested block would end the body early 119// and make a REAL main read as a stub -- a false ARMED is worse than none, it teaches distrust. 120func sm_close_brace(buf: *u8, s: i64, e: i64) -> i64 { 121 var depth: i64 = 1 122 var i: i64 = s 123 while i < e { 124 let c: i64 = (buf[i] as i64) & 255 125 if c == SM_LBRACE { depth = depth + 1 } 126 if c == SM_RBRACE { 127 depth = depth - 1 128 if depth == 0 { return i } 129 } 130 i = i + 1 131 } 132 return 0 - 1 133} 134 135// Classify the body [s,e) of a main. A stub body is a SINGLE statement `return <integer>` once 136// comments and blank lines are removed. Anything else -- any call, any second statement -- is real. 137func sm_body_is_stub(buf: *u8, s: i64, e: i64) -> i64 { 138 var i: i64 = s 139 var saw_return: i64 = 0 140 var saw_digit: i64 = 0 141 var other: i64 = 0 142 while i < e { 143 let c: i64 = (buf[i] as i64) & 255 144 if sm_is_space(c) == 1 { i = i + 1 } else { 145 if c == SM_NL { i = i + 1 } else { 146 if c == SM_CR { i = i + 1 } else { 147 // a comment runs to end of line and is not a statement 148 if c == SM_SLASH { 149 if i + 1 < e { 150 if ((buf[i + 1] as i64) & 255) == SM_SLASH { 151 var j: i64 = i 152 while j < e { 153 if ((buf[j] as i64) & 255) == SM_NL { i = j + 1; j = e } else { j = j + 1 } 154 } 155 if i <= s { i = e } 156 } else { other = other + 1; i = i + 1 } 157 } else { other = other + 1; i = i + 1 } 158 } else { 159 if sm_streq_at(buf, i, e, "return" as *u8) == 1 { 160 saw_return = saw_return + 1 161 i = i + 6 162 } else { 163 if c == SM_MINUS { i = i + 1 } else { 164 if c >= SM_D0 { 165 if c <= SM_D9 { saw_digit = 1; i = i + 1 } else { other = other + 1; i = i + 1 } 166 } else { other = other + 1; i = i + 1 } 167 } 168 } 169 } 170 } 171 } 172 } 173 } 174 if other > 0 { return 0 } 175 if saw_return != 1 { return 0 } 176 if saw_digit == 0 { return 0 } 177 return 1 178} 179 180// THE RULER. Classify a source buffer. Three states, never two. 181func sm_classify(buf: *u8, n: i64) -> i64 { 182 if n <= 0 { return SM_UNREADABLE } 183 let at: i64 = sm_find_line_start(buf, n, "func main" as *u8) 184 if at < 0 { return SM_NO_MAIN } 185 let bs: i64 = sm_open_brace(buf, at, n) 186 if bs < 0 { return SM_NO_MAIN } 187 let be: i64 = sm_close_brace(buf, bs, n) 188 if be < 0 { return SM_UNREADABLE } 189 if sm_body_is_stub(buf, bs, be) == 1 { return SM_STUB_MAIN } 190 return SM_REAL_MAIN 191} 192 193// Convenience: classify a source FILE. Composes sys_read_file, which sizes from the file and cannot 194// short-read -- a capped reader here would classify a truncated head and call a real main a stub. 195func sm_classify_path(path: *u8) -> i64 { 196 let lp: *i64 = sys_mmap(8) as *i64 197 lp[0] = 0 198 let buf: *u8 = sys_read_file(path, lp) 199 if (buf as i64) == 0 { return SM_UNREADABLE } 200 return sm_classify(buf, lp[0]) 201} 202 203// Is this source ARMED given that a promoted binary exists for its name? The promoted half is the 204// CALLER's to establish (nx_artifactdrift already knows it); this keeps the two halves in one place so 205// no consumer invents its own conjunction. 206func sm_is_armed(cls: i64, promoted_exists: i64) -> i64 { 207 if cls != SM_STUB_MAIN { return 0 } 208 if promoted_exists != 1 { return 0 } 209 return 1 210}