code wiki / _hdl_build / nx_contentdiff_lib.nx

nx_contentdiff_lib.nx source

↩ module page · 539 lines · 25941 B

1// nx_contentdiff_lib.nx -- ONE CONTENT RULER, TWO BUDGETS. 2// 3// THE RULER (lifted verbatim in semantics from nx_contentdiff.nx, whose reasoning stands): every 4// printable run of >= minlen bytes in the LIVE binary must still be findable somewhere in the 5// CANDIDATE's RAW BYTES. A missing run is a content difference requiring review, not proof of 6// destroyed behavior. Conversely, retained strings do not establish behavioral equivalence. 7// Structured ELF comparison below respects validated DWARF string boundaries; the raw API retains its contract. 8// 9// WHY SIZE CANNOT DO THIS JOB, measured twice by the organ this lib is extracted from: SHRINK is not 10// regression (16 live organs had LARGER .prev files yet the smaller live binaries were string 11// SUPERSETS), and GROWTH is not improvement (nx_law_warden GREW 161951 -> 186696 bytes while losing 12// three whole detectors). A gate keyed on bytes is wrong about half the time and cannot tell you which 13// half. Re-measured on the live deploy root 2026-08-06: 22 of 89 staged artifacts with a live 14// counterpart would drop live runs, and several of those are LARGER than what they replace. 15// 16// WHY IT IS A LIB (2026-08-06): the ruler existed only inside nx_contentdiff's main() -- a CLI oracle 17// with no deployed binary, no registry row, and therefore no consumer. The debt that named it 18// (1785531571) asked for it to be wired into /api/promote; nothing could, because there was nothing 19// importable to wire. A ruler that only a main() can reach is not a capability the fleet has. 20// 21// TWO BUDGETS, ONE IMPLEMENTATION (Rule 15). The CLI oracle runs to completion and can afford to check 22// EVERY run; the promote guard runs inside a live deploy call and must bound its work. Those are 23// different BUDGETS, not different rulers -- so the budget is a PARAMETER and the semantics are shared. 24// Two rulers that can disagree about the same question is the defect this file exists to prevent. 25// 26// DIALECT: reachable from nx_mgmt_data -> nx_mgmt_api, so plain-if (NO else), no empty string literals, 27// <=6 params. 28// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 29import "nx_syscalls.nx" 30import "nx_elf_read.nx" 31 32const CDL_PRINT_LO: i64 = 32 33const CDL_PRINT_HI: i64 = 126 34 35// prm[] -- the BUDGET 36const CDL_P_MINLEN: i64 = 0 // shortest printable run that counts as capability 37const CDL_P_MAXSAMPLES: i64 = 1 // 0 = check every run (oracle); N = sample evenly across the file 38const CDL_P_MAXTOKLEN: i64 = 2 // 0 = search the whole run; N = cap the searched prefix 39const CDL_P_SLOTS: i64 = 4 40 41// out[] -- the MEASUREMENT 42const CDL_O_RUNS: i64 = 0 // qualifying runs found in live 43const CDL_O_CHECKED: i64 = 1 // runs actually searched for 44const CDL_O_LOST: i64 = 2 // runs absent from the candidate 45// 6, raised from 4 on 2026-09-03 for the two flavour slots. RAISING THIS IS SAFE FOR EVERY EXISTING 46// CONSUMER: callers size their scratch as 8 * CDL_O_SLOTS, so a larger value only enlarges an allocation, 47// and cdl_lost still writes only slots 0..2 -- the flavour slots are written by cdl_lost_srcpath alone. 48const CDL_O_SLOTS: i64 = 6 49 50func cdl_is_print(c: i64) -> i64 { 51 if c < CDL_PRINT_LO { return 0 } 52 if c > CDL_PRINT_HI { return 0 } 53 return 1 54} 55 56// length of the printable run starting at `start` (0 when that byte is not printable) 57func cdl_runlen(b: *u8, n: i64, start: i64) -> i64 { 58 var k: i64 = 0 59 var go: i64 = 1 60 while go == 1 { 61 if (start + k) >= n { go = 0 } 62 if go == 1 { 63 if cdl_is_print(b[start + k] as i64) == 0 { go = 0 } 64 if go == 1 { k = k + 1 } 65 } 66 } 67 return k 68} 69 70// 1 when the nl bytes at ndl[ns..] appear anywhere in hay[0..hn) 71func cdl_contains(hay: *u8, hn: i64, ndl: *u8, ns: i64, nl: i64) -> i64 { 72 if nl <= 0 { return 1 } 73 if nl > hn { return 0 } 74 let first: u8 = ndl[ns] 75 let last: i64 = hn - nl 76 var i: i64 = 0 77 while i <= last { 78 if hay[i] == first { 79 var k: i64 = 1 80 var ok: i64 = 1 81 while k < nl { 82 if hay[i + k] != ndl[ns + k] { ok = 0; k = nl } 83 if ok == 1 { k = k + 1 } 84 } 85 if ok == 1 { return 1 } 86 } 87 i = i + 1 88 } 89 return 0 90} 91 92// 1 when the two buffers are byte-identical. EQUAL SIZE IS NOT EQUAL CONTENT, and that gap is precisely 93// where a mutation-class defect lives: an ==/!= operator swap preserves the byte count and every printable 94// string, so it is invisible to a size screen AND to the run ruler above. This is the one comparison that 95// separates "a harmless re-stage of the same bytes" from "something changed that nothing else can see". 96func cdl_bytes_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 97 if an != bn { return 0 } 98 var i: i64 = 0 99 while i < an { 100 if a[i] != b[i] { return 0 } 101 i = i + 1 102 } 103 return 1 104} 105 106// how many qualifying runs live carries (NOT deduped -- occurrence counts, as the oracle declares) 107func cdl_count_runs(b: *u8, n: i64, minlen: i64) -> i64 { 108 var cnt: i64 = 0 109 var i: i64 = 0 110 while i < n { 111 let r: i64 = cdl_runlen(b, n, i) 112 if r >= minlen { cnt = cnt + 1 } 113 if r > 0 { i = i + r } 114 if r == 0 { i = i + 1 } 115 } 116 return cnt 117} 118 119// THE MEASURE. Returns loss in permil; fills out[] with the raw counts so a caller can show its work. 120// FAIL-CLOSED: an unreadable/empty candidate against a non-empty live is TOTAL loss (1000), never clean. 121// Sampling (when budgeted) strides EVENLY across the whole binary, so it never reads only the head. 122func cdl_lost(live: *u8, ln: i64, cand: *u8, cn: i64, prm: *i64, out: *i64) -> i64 { 123 out[CDL_O_RUNS] = 0 124 out[CDL_O_CHECKED] = 0 125 out[CDL_O_LOST] = 0 126 if ln <= 0 { return 0 } 127 let minlen: i64 = prm[CDL_P_MINLEN] 128 let total: i64 = cdl_count_runs(live, ln, minlen) 129 out[CDL_O_RUNS] = total 130 if total <= 0 { return 0 } 131 if cn <= 0 { out[CDL_O_CHECKED] = total; out[CDL_O_LOST] = total; return 1000 } 132 let maxs: i64 = prm[CDL_P_MAXSAMPLES] 133 let maxtl: i64 = prm[CDL_P_MAXTOKLEN] 134 var stride: i64 = 1 135 if maxs > 0 { stride = total / maxs } 136 if stride < 1 { stride = 1 } 137 var checked: i64 = 0 138 var lost: i64 = 0 139 var idx: i64 = 0 140 var i: i64 = 0 141 while i < ln { 142 let r: i64 = cdl_runlen(live, ln, i) 143 if r >= minlen { 144 var take: i64 = 0 145 if (idx % stride) == 0 { take = 1 } 146 if maxs > 0 { if checked >= maxs { take = 0 } } 147 if take == 1 { 148 var tl: i64 = r 149 if maxtl > 0 { if tl > maxtl { tl = maxtl } } 150 if cdl_contains(cand, cn, live, i, tl) == 0 { lost = lost + 1 } 151 checked = checked + 1 152 } 153 idx = idx + 1 154 } 155 if r > 0 { i = i + r } 156 if r == 0 { i = i + 1 } 157 } 158 out[CDL_O_CHECKED] = checked 159 out[CDL_O_LOST] = lost 160 if checked <= 0 { return 0 } 161 return (1000 * lost) / checked 162} 163 164// ---- THE FLAVOUR AXIS (2026-09-03) ----------------------------------------------------------------- 165// WHY THIS EXISTS, MEASURED THE DAY IT WAS WRITTEN. A --debug build and a release build of the SAME 166// source differ by ~184 permil of printable runs, because -g embeds a `.debug_line` source path per 167// compilation unit. That is roughly 4x the calibrated regression band (ordinary edits measure 8-38 168// permil; every real regression cleared 49+), so this ruler REFUSES any release rebuild of a 169// debug-built binary and reports a catastrophic capability loss THAT DOES NOT EXIST. 170// MEASURED on nx_hostctl: release rebuild lost 332 of 1803 runs (184 permil, RED); the SAME source 171// rebuilt --debug lost 2 of 1803 (1 permil), and those 2 were deliberate deletions. So ~330 of the 332 172// were metadata. 173// ★A DETECTOR WITH FALSE POSITIVES IS WORSE THAN NONE, AND THIS ONE'S FALSE POSITIVE PUSHES THE CALLER 174// STRAIGHT AT `allow_capability_loss=yes` -- i.e. it trains its own users to disarm it. 175// ⛔THE VERDICT IS DELIBERATELY NOT LOOSENED. This is a SEPARATE AXIS: a run can be a source path AND 176// be load-bearing (an organ that prints its own source path), so folding it into the loss count would 177// silently redefine the number every promote is judged against. The partition is ANNOUNCED and the 178// reader decides -- a new bucket that overlaps an existing partition is an axis, never a widened 179// conjunct. 180const CDL_O_SRCPATH: i64 = 3 // of the LOST runs, how many look like a compiler-embedded source path 181const CDL_O_IDENT: i64 = 4 // ... and how many look like a compiler-embedded SYMBOL name 182const CDL_O_SECT: i64 = 5 // ... and how many are ELF/DWARF SECTION names (.debug_info, .shstrtab) 183const CDL_SRC_SFX: *u8 = ".nx" 184const CDL_IDENT_MIN: i64 = 6 // == the ruler's own minimum run length; not an independent knob 185 186// Does this run look like a source path the COMPILER embedded rather than content the program prints? 187// Deliberately NARROW: it must END in ".nx". A message merely mentioning a filename does not qualify, 188// which keeps the class too small to hide a real loss. 189func cdl_is_srcpath(b: *u8, off: i64, len: i64) -> i64 { 190 if len < 4 { return 0 } 191 let e: i64 = off + len 192 if b[e-3] != (46 as u8) { return 0 } 193 if b[e-2] != (110 as u8) { return 0 } 194 if b[e-1] != (120 as u8) { return 0 } 195 return 1 196} 197 198// ⚠THE SOURCE-PATH CLASS ALONE WAS BUILT ON A PREFIX AND REAL DATA REFUTED IT WITHIN THE HOUR. The first 199// cut of this axis classified only runs ending ".nx", because 6 of the 8 lost runs the oracle PRINTS were 200// source paths. The oracle prints 8 of N. Measured on the real case: lost_srcpath=14, lost_other=317. 201// ★I QUOTED "THE PRINTED LIST IS A PREFIX OF ITS OWN COUNT, NEVER THE POPULATION" AND THEN BUILT A 202// DETECTOR ON THE PREFIX. A classifier derived from a sample is a hypothesis about the sample. 203// The build had already named the true class for free: `nxasm_x86: debug-info subprograms=325`, i.e. -g 204// embeds one SYMBOL NAME per function -- and 325 subprograms against 317 unexplained losses is the 205// arithmetic that identifies it. So the axis needs BOTH shapes. 206// Is this run an identifier -- letters, digits and underscore only? That is what a symbol name looks like 207// and what an ordinary message does NOT (messages carry spaces and punctuation). Disjoint from srcpath by 208// construction, since a path carries '/' and '.', so the two classes can never double-count. 209func cdl_is_ident(b: *u8, off: i64, len: i64) -> i64 { 210 if len < CDL_IDENT_MIN { return 0 } 211 var i: i64 = 0 212 while i < len { 213 let c: i64 = b[off + i] as i64 214 var ok: i64 = 0 215 if c == 95 { ok = 1 } 216 if c >= 48 { if c <= 57 { ok = 1 } } 217 if c >= 65 { if c <= 90 { ok = 1 } } 218 if c >= 97 { if c <= 122 { ok = 1 } } 219 if ok == 0 { return 0 } 220 i = i + 1 221 } 222 return 1 223} 224 225// ⚠AND THE PARTITION WAS ONE CLASS SHORT, FOUND BY READING ITS OWN OUTPUT ON A REAL FRONT-DOOR RUN. 226// With srcpath and ident alone, the front door reported lost_other=4 -- and enumerating them showed THREE 227// were `.debug_info`, `.debug_abbrev` and `.shstrtab`: ELF/DWARF SECTION NAMES, which are build metadata 228// exactly like the other two classes but are caught by neither, because they start with '.' so they are 229// not identifier-shaped and do not end in ".nx". ★A PARTITION THAT DUMPS A KNOWN METADATA CLASS INTO 230// "OTHER" INFLATES THE ONE NUMBER A HUMAN IS SUPPOSED TO ADJUDICATE -- the actionable class must contain 231// only what is actually actionable, or it teaches its reader to skim it. 232// Deliberately NARROW: must START with '.', then only lowercase letters, digits, '_' or '.'. A sentence 233// beginning with a full stop does not qualify (it has spaces), and no real message string looks like this. 234func cdl_is_sectname(b: *u8, off: i64, len: i64) -> i64 { 235 if len < CDL_IDENT_MIN { return 0 } 236 if b[off] != (46 as u8) { return 0 } 237 var i: i64 = 1 238 while i < len { 239 let c: i64 = b[off + i] as i64 240 var ok: i64 = 0 241 if c == 95 { ok = 1 } 242 if c == 46 { ok = 1 } 243 if c >= 48 { if c <= 57 { ok = 1 } } 244 if c >= 97 { if c <= 122 { ok = 1 } } 245 if ok == 0 { return 0 } 246 i = i + 1 247 } 248 return 1 249} 250 251// Of the runs LOST from live, how many are source paths and how many are symbol names? Composes 252// cdl_runlen/cdl_contains -- the SAME primitives cdl_lost uses -- so the two can never disagree about what 253// "lost" means. Writes both counts into out[] so a caller can reconcile the partition, and returns their 254// SUM (the runs the build flavour can account for). 255func cdl_lost_srcpath(live: *u8, ln: i64, cand: *u8, cn: i64, minlen: i64, out: *i64) -> i64 { 256 var np: i64 = 0 257 var nd: i64 = 0 258 var ns: i64 = 0 259 var i: i64 = 0 260 while i < ln { 261 let r: i64 = cdl_runlen(live, ln, i) 262 if r >= minlen { 263 if cdl_contains(cand, cn, live, i, r) == 0 { 264 if cdl_is_srcpath(live, i, r) == 1 { np = np + 1 } else { 265 if cdl_is_ident(live, i, r) == 1 { nd = nd + 1 } else { 266 if cdl_is_sectname(live, i, r) == 1 { ns = ns + 1 } 267 } 268 } 269 } 270 } 271 if r > 0 { i = i + r } 272 if r == 0 { i = i + 1 } 273 } 274 if (out as i64) != 0 { 275 out[CDL_O_SRCPATH] = np 276 out[CDL_O_IDENT] = nd 277 out[CDL_O_SECT] = ns 278 } 279 return np + nd + ns 280} 281 282// ---- THE SYMMETRY CLASS ---------------------------------------------------------------------------- 283// NOT A NEW IDEA AND NOT A NEW RULER: nx_staging_guard has run cdl_lost with its arguments SWAPPED since 284// 2026-08-06, with the reasoning in its own comment ("A LOSS DETECTOR THAT COUNTS ONLY WHAT VANISHED 285// CANNOT TELL A RENAME FROM A REMOVAL"). What was missing is that the ORACLE seats run BY HAND never 286// exposed it, so the measurement had to be rediscovered from scratch -- a fix wired into one consumer 287// and not its sibling, where the sibling is the verb whose whole purpose is to tell you what to fix. 288// This just names the four outcomes so both consumers can say the same word. 289// ⛔THRESHOLD-FREE ON PURPOSE. There is no principled cutoff for "asymmetric enough", and inventing one 290// would be a magic number gating a safety decision. The states key only on ZERO vs NONZERO; the two 291// permils are PUBLISHED so the reader judges the degree. Same discipline as nx_spendgate: a ruler that 292// cannot separate novel from duplicate publishes the list, not a verdict. 293const CDL_SYM_IDENTICAL: i64 = 0 // neither side has a run the other lacks 294const CDL_SYM_LOSS: i64 = 1 // only the candidate is missing runs -> a genuine one-way LOSS 295const CDL_SYM_GAIN: i64 = 2 // only live is missing runs -> the candidate is a strict SUPERSET 296const CDL_SYM_BOTH: i64 = 3 // both directions lose -> rename / re-encode / build-flavour change 297 298func cdl_symclass(lost_fwd: i64, lost_rev: i64) -> i64 { 299 if lost_fwd <= 0 { if lost_rev <= 0 { return CDL_SYM_IDENTICAL } return CDL_SYM_GAIN } 300 if lost_rev <= 0 { return CDL_SYM_LOSS } 301 return CDL_SYM_BOTH 302} 303 304func cdl_symname(s: i64) -> *u8 { 305 if s == CDL_SYM_IDENTICAL { return "IDENTICAL-CONTENT (no measured run differences; behavior unmeasured)" as *u8 } 306 if s == CDL_SYM_LOSS { return "ONE-WAY-LOSS (measured runs removed, none added; behavior unmeasured)" as *u8 } 307 if s == CDL_SYM_GAIN { return "ONE-WAY-GAIN (measured runs added, none removed; behavior unmeasured)" as *u8 } 308 return "BIDIRECTIONAL (measured runs added and removed; cause and behavior unmeasured)" as *u8 309} 310 311// Additive structured comparison. Callers must reject nonzero state before reading counts. 312const CDS_INVALID:i64=-201 313const CDS_UNKNOWN:i64=-202 314const CDS_RESOURCE:i64=-203 315const CDS_FILE:i64=1 316const CDS_DIR:i64=2 317const CDS_END:i64=3 318struct NxCdStructured {buf:*u8,bytes:i64,marks:*u8,at:i64,end:i64,error:i64,files:i64,sections:i64} 319func cds_init(c:*NxCdStructured,b:*u8,n:i64)->i64{ 320 c.buf=b;c.bytes=n;c.marks=0 as *u8;c.at=0;c.end=0;c.error=0;c.files=0;c.sections=0 321 if n<=0{return CDS_INVALID};if (b as i64)<=0{return CDS_INVALID} 322 c.marks=sys_mmap_try(n);if (c.marks as i64)<=0{return CDS_RESOURCE};return 0 323} 324func cds_close(c:*NxCdStructured)->i64{if (c.marks as i64)>0{sys_munmap_direct(c.marks,c.bytes)};c.marks=0 as *u8;return 0} 325func cds_span(n:i64,o:i64,z:i64)->i64{if o<0||z<0||o>n{return 0};if z>n-o{return 0};return 1} 326func cds_byte(c:*NxCdStructured)->i64{ 327 if c.error!=0{return -1};if c.at>=c.end{c.error=CDS_INVALID;return -1} 328 let v:i64=c.buf[c.at] as i64;c.at=c.at+1;return v 329} 330// Checked unsigned LEB restricted to representable nonnegative i64 counts/indexes. 331func cds_uleb(c:*NxCdStructured)->i64{ 332 var v:i64=0;var shift:i64=0 333 while c.error==0{ 334 let b:i64=cds_byte(c);if c.error!=0{return -1} 335 let low:i64=b&127 336 if shift>=63{c.error=CDS_INVALID;return -1} 337 v=v|(low<<shift) 338 if (b&128)==0{return v};shift=shift+7 339 } 340 return -1 341} 342func cds_string(c:*NxCdStructured,kind:i64)->i64{ 343 let begin:i64=c.at 344 while c.at<c.end{ 345 let v:i64=cds_byte(c);if c.error!=0{return c.error} 346 if v==0{if c.at-1==begin{c.error=CDS_UNKNOWN;return c.error};c.marks[begin]=kind as u8;c.marks[c.at-1]=CDS_END as u8;return 0} 347 if v<32||v>126{c.error=CDS_UNKNOWN;return c.error} 348 } 349 c.error=CDS_INVALID;return c.error 350} 351func cds_line(c:*NxCdStructured,off:i64,size:i64)->i64{ 352 let sectionend:i64=off+size;var unit:i64=off 353 while unit<sectionend{ 354 if cds_span(sectionend,unit,12)==0{return CDS_INVALID} 355 let length:i64=nx_elf_get_u32_le(c.buf,unit) 356 if length>=0xfffffff0{return CDS_UNKNOWN} 357 if cds_span(sectionend,unit+4,length)==0{return CDS_INVALID} 358 let unitend:i64=unit+4+length 359 if length<8{return CDS_INVALID} 360 if nx_elf_get_u16_le(c.buf,unit+4)!=5{return CDS_UNKNOWN} 361 if c.buf[unit+6]!=(8 as u8)||c.buf[unit+7]!=(0 as u8){return CDS_UNKNOWN} 362 let hlen:i64=nx_elf_get_u32_le(c.buf,unit+8) 363 if cds_span(unitend,unit+12,hlen)==0{return CDS_INVALID} 364 c.at=unit+12;c.end=c.at+hlen 365 if hlen<6{return CDS_INVALID} 366 let mininst:i64=cds_byte(c);let maxops:i64=cds_byte(c);let stmt:i64=cds_byte(c) 367 cds_byte(c);let range:i64=cds_byte(c);let opbase:i64=cds_byte(c) 368 if mininst==0||maxops==0||stmt>1||range==0||opbase==0{return CDS_INVALID} 369 if cds_span(c.end,c.at,opbase-1)==0{return CDS_INVALID};c.at=c.at+opbase-1 370 // Accepted schema is explicit: inline directory path; inline file path + ULEB directory index. 371 if cds_byte(c)!=1{return CDS_UNKNOWN} 372 if cds_uleb(c)!=1{return CDS_UNKNOWN};if cds_uleb(c)!=8{return CDS_UNKNOWN} 373 let dirs:i64=cds_uleb(c);if c.error!=0{return c.error} 374 if dirs>c.end-c.at{return CDS_INVALID} 375 var i:i64=0;while i<dirs{if cds_string(c,CDS_DIR)!=0{return c.error};i=i+1} 376 if cds_byte(c)!=2{return CDS_UNKNOWN} 377 if cds_uleb(c)!=1{return CDS_UNKNOWN};if cds_uleb(c)!=8{return CDS_UNKNOWN} 378 if cds_uleb(c)!=2{return CDS_UNKNOWN};if cds_uleb(c)!=15{return CDS_UNKNOWN} 379 let files:i64=cds_uleb(c);if c.error!=0{return c.error} 380 if files>(c.end-c.at)/2{return CDS_INVALID} 381 i=0;while i<files{ 382 if cds_string(c,CDS_FILE)!=0{return c.error} 383 let directory:i64=cds_uleb(c);if c.error!=0{return c.error} 384 if directory>=dirs{return CDS_INVALID};c.files=c.files+1;i=i+1 385 } 386 if c.at!=c.end{return CDS_INVALID} 387 unit=unitend 388 } 389 if unit!=sectionend{return CDS_INVALID};return 0 390} 391// Reuses existing ELF LE primitives only AFTER proving each enclosing extent. 392// ELF64 program-header sizes/offsets follow the ELF ABI, not capacity policy. 393// No section table means no structured DWARF source identities, not an invalid ELF. 394func cds_sectionless(c:*NxCdStructured)->i64{ 395 let b:*u8=c.buf;let n:i64=c.bytes 396 if nx_elf_get_u64_le(b,40)!=0{return CDS_UNKNOWN} 397 if nx_elf_get_u16_le(b,60)!=0||nx_elf_get_u16_le(b,62)!=0{return CDS_INVALID} 398 let po:i64=nx_elf_get_u64_le(b,32);let ps:i64=nx_elf_get_u16_le(b,54);let pc:i64=nx_elf_get_u16_le(b,56) 399 if pc==65535{return CDS_UNKNOWN} 400 if pc==0{return CDS_UNKNOWN} 401 if ps!=56{return CDS_UNKNOWN} 402 if po<64{return CDS_INVALID} 403 if cds_span(n,po,pc*ps)==0{return CDS_INVALID} 404 var i:i64=0 405 while i<pc{ 406 let p:i64=po+i*ps;let kind:i64=nx_elf_get_u32_le(b,p) 407 if kind!=0{ 408 let off:i64=nx_elf_get_u64_le(b,p+8);let size:i64=nx_elf_get_u64_le(b,p+32) 409 if cds_span(n,off,size)==0{return CDS_INVALID} 410 if kind==1{let memory:i64=nx_elf_get_u64_le(b,p+40);if memory<0||size>memory{return CDS_INVALID}} 411 } 412 i=i+1 413 } 414 return 0 415} 416 417func cds_elf(c:*NxCdStructured)->i64{ 418 let b:*u8=c.buf;let n:i64=c.bytes 419 if n<64{return CDS_INVALID} 420 if b[0]!=(127 as u8)||b[1]!=(69 as u8)||b[2]!=(76 as u8)||b[3]!=(70 as u8){return CDS_UNKNOWN} 421 if b[4]!=(2 as u8)||b[5]!=(1 as u8)||b[6]!=(1 as u8){return CDS_UNKNOWN} 422 if nx_elf_get_u16_le(b,52)!=64{return CDS_UNKNOWN} 423 let table:i64=nx_elf_get_u64_le(b,40);let stride:i64=nx_elf_get_u16_le(b,58) 424 let count:i64=nx_elf_get_u16_le(b,60);let names:i64=nx_elf_get_u16_le(b,62) 425 if count==0{return cds_sectionless(c)};if stride!=64{return CDS_UNKNOWN} 426 if names>=count{return CDS_INVALID} 427 if cds_span(n,table,count*stride)==0{return CDS_INVALID} 428 let sn:i64=table+names*stride 429 if nx_elf_get_u32_le(b,sn+4)!=3{return CDS_INVALID} 430 let noff:i64=nx_elf_get_u64_le(b,sn+24);let nsize:i64=nx_elf_get_u64_le(b,sn+32) 431 if cds_span(n,noff,nsize)==0{return CDS_INVALID} 432 var i:i64=0 433 while i<count{ 434 let s:i64=table+i*stride;let name:i64=nx_elf_get_u32_le(b,s) 435 if name>=nsize{return CDS_INVALID} 436 var z:i64=0;while name+z<nsize{if b[noff+name+z]==0 as u8{break};z=z+1} 437 if name+z>=nsize{return CDS_INVALID} 438 let off:i64=nx_elf_get_u64_le(b,s+24);let size:i64=nx_elf_get_u64_le(b,s+32) 439 let ty:i64=nx_elf_get_u32_le(b,s+4) 440 if ty!=8{if cds_span(n,off,size)==0{return CDS_INVALID}} 441 if z==11{ 442 if cdl_bytes_eq(b+noff+name,z,".debug_line",11)==1{ 443 if ty!=1{return CDS_UNKNOWN} 444 if (nx_elf_get_u64_le(b,s+8)&2050)!=0{return CDS_UNKNOWN} 445 if off<64{return CDS_INVALID} 446 if off<table+count*stride{if table<off+size{return CDS_INVALID}} 447 var j:i64=0;while j<count{ 448 if j!=i{let other:i64=table+j*stride;let ot:i64=nx_elf_get_u32_le(b,other+4) 449 if ot!=8{let oo:i64=nx_elf_get_u64_le(b,other+24);let oz:i64=nx_elf_get_u64_le(b,other+32) 450 if cds_span(n,oo,oz)==0{return CDS_INVALID} 451 if oz>0{if off<oo+oz{if oo<off+size{return CDS_INVALID}}} 452 } 453 };j=j+1 454 } 455 let rc:i64=cds_line(c,off,size);if rc!=0{return rc};c.sections=c.sections+1 456 } 457 } 458 i=i+1 459 } 460 return 0 461} 462func cds_run(c:*NxCdStructured,start:i64)->i64{ 463 var n:i64=0;while start+n<c.bytes{ 464 if n>0{if c.marks[start+n]!=0 as u8{break}} 465 if cdl_is_print(c.buf[start+n] as i64)==0{break};n=n+1 466 };return n 467} 468func cds_file_present(a:*NxCdStructured,at:i64,n:i64,b:*NxCdStructured)->i64{ 469 var j:i64=0;while j<b.bytes{ 470 if b.marks[j]==CDS_FILE as u8{ 471 let z:i64=cdl_runlen(b.buf,b.bytes,j) 472 if z==n{if cdl_bytes_eq(a.buf+at,n,b.buf+j,z)==1{return 1}} 473 };j=j+1 474 };return 0 475} 476// out adds structured table evidence separate from raw content evidence. 477struct NxCdResult {state:i64,runs:i64,checked:i64,lost:i64,source_removed:i64,source_added:i64} 478 479struct NxCdSpan {offset:i64,length:i64,kind:i64} 480// Single segmented traversal for measurement, classification and enumeration. 481// Requires successfully validated contexts from cds_elf/cds_measure. 482func cds_next_span(c:*NxCdStructured,cursor:*i64,s:*NxCdSpan)->i64{ 483 while cursor[0]<c.bytes{ 484 let at:i64=cursor[0];let n:i64=cds_run(c,at) 485 if n>0{cursor[0]=at+n;s.offset=at;s.length=n;s.kind=c.marks[at] as i64;return 1} 486 cursor[0]=at+1 487 };return 0 488} 489func cds_span_missing(a:*NxCdStructured,b:*NxCdStructured,s:*NxCdSpan,maxlen:i64)->i64{ 490 var n:i64=s.length;if maxlen>0{if n>maxlen{n=maxlen}} 491 return 1-cdl_contains(b.buf,b.bytes,a.buf,s.offset,n) 492} 493func cds_source_missing(a:*NxCdStructured,b:*NxCdStructured,s:*NxCdSpan)->i64{ 494 if s.kind!=CDS_FILE{return 0};return 1-cds_file_present(a,s.offset,s.length,b) 495} 496const CDS_CLASS_SOURCE:i64=1 497const CDS_CLASS_IDENT:i64=2 498const CDS_CLASS_SECTION:i64=3 499const CDS_CLASS_OTHER:i64=4 500func cds_span_class(a:*NxCdStructured,s:*NxCdSpan)->i64{ 501 if s.kind==CDS_FILE{return CDS_CLASS_SOURCE} 502 if cdl_is_srcpath(a.buf,s.offset,s.length)==1{return CDS_CLASS_SOURCE} 503 if cdl_is_ident(a.buf,s.offset,s.length)==1{return CDS_CLASS_IDENT} 504 if cdl_is_sectname(a.buf,s.offset,s.length)==1{return CDS_CLASS_SECTION} 505 return CDS_CLASS_OTHER 506} 507// Exact permil without the potentially overflowing lost*1000 product. 508func cds_permil(lost:i64,checked:i64)->i64{ 509 if lost<0||checked<0||lost>checked{return CDS_INVALID};if checked==0{return 0} 510 var remainder:i64=0;var value:i64=0;var i:i64=0 511 while i<1000{ 512 if remainder>=checked-lost{remainder=remainder-(checked-lost);value=value+1}else{remainder=remainder+lost};i=i+1 513 };return value 514} 515 516func cds_measure(a:*NxCdStructured,b:*NxCdStructured,prm:*i64,out:*NxCdResult)->i64{ 517 out.state=0;out.runs=0;out.checked=0;out.lost=0;out.source_removed=0;out.source_added=0 518 if prm[CDL_P_MINLEN]<=0||prm[CDL_P_MAXSAMPLES]<0||prm[CDL_P_MAXTOKLEN]<0{out.state=CDS_INVALID;return out.state} 519 let ar:i64=cds_elf(a);if ar!=0{out.state=ar;return ar} 520 let br:i64=cds_elf(b);if br!=0{out.state=br;return br} 521 var i:i64=0;var span:NxCdSpan 522 while cds_next_span(a,&i,&span)==1{ 523 if span.length>=prm[CDL_P_MINLEN]{out.runs=out.runs+1} 524 out.source_removed=out.source_removed+cds_source_missing(a,b,&span) 525 } 526 i=0;while cds_next_span(b,&i,&span)==1{out.source_added=out.source_added+cds_source_missing(b,a,&span)} 527 var stride:i64=1;let maxs:i64=prm[CDL_P_MAXSAMPLES] 528 if maxs>0{stride=out.runs/maxs};if stride<1{stride=1} 529 var idx:i64=0;i=0 530 while cds_next_span(a,&i,&span)==1{ 531 if span.length>=prm[CDL_P_MINLEN]{ 532 var take:i64=0;if idx%stride==0{take=1};if maxs>0{if out.checked>=maxs{take=0}} 533 if take==1{ 534 out.lost=out.lost+cds_span_missing(a,b,&span,prm[CDL_P_MAXTOKLEN]);out.checked=out.checked+1 535 };idx=idx+1 536 } 537 } 538 return 0 539}