code wiki / (root) / nx_contentdiff_iterator_candidate_t295.nx

nx_contentdiff_iterator_candidate_t295.nx source

↩ module page · 514 lines · 24960 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" as *u8 } 306 if s == CDL_SYM_LOSS { return "ONE-WAY-LOSS (the candidate drops runs and adds none -- a REGRESSION)" as *u8 } 307 if s == CDL_SYM_GAIN { return "ONE-WAY-GAIN (the candidate is a strict SUPERSET -- a gift, not litter)" as *u8 } 308 return "BIDIRECTIONAL (runs moved BOTH ways -- a rename, a re-encode or a build-flavour change, NOT a plain loss; read the two permils and run a matched-vintage control)" 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. 392func cds_elf(c:*NxCdStructured)->i64{ 393 let b:*u8=c.buf;let n:i64=c.bytes 394 if n<64{return CDS_INVALID} 395 if b[0]!=(127 as u8)||b[1]!=(69 as u8)||b[2]!=(76 as u8)||b[3]!=(70 as u8){return CDS_UNKNOWN} 396 if b[4]!=(2 as u8)||b[5]!=(1 as u8)||b[6]!=(1 as u8){return CDS_UNKNOWN} 397 if nx_elf_get_u16_le(b,52)!=64{return CDS_UNKNOWN} 398 let table:i64=nx_elf_get_u64_le(b,40);let stride:i64=nx_elf_get_u16_le(b,58) 399 let count:i64=nx_elf_get_u16_le(b,60);let names:i64=nx_elf_get_u16_le(b,62) 400 if count==0{return CDS_UNKNOWN};if stride!=64{return CDS_UNKNOWN} 401 if names>=count{return CDS_INVALID} 402 if cds_span(n,table,count*stride)==0{return CDS_INVALID} 403 let sn:i64=table+names*stride 404 if nx_elf_get_u32_le(b,sn+4)!=3{return CDS_INVALID} 405 let noff:i64=nx_elf_get_u64_le(b,sn+24);let nsize:i64=nx_elf_get_u64_le(b,sn+32) 406 if cds_span(n,noff,nsize)==0{return CDS_INVALID} 407 var i:i64=0 408 while i<count{ 409 let s:i64=table+i*stride;let name:i64=nx_elf_get_u32_le(b,s) 410 if name>=nsize{return CDS_INVALID} 411 var z:i64=0;while name+z<nsize{if b[noff+name+z]==0 as u8{break};z=z+1} 412 if name+z>=nsize{return CDS_INVALID} 413 let off:i64=nx_elf_get_u64_le(b,s+24);let size:i64=nx_elf_get_u64_le(b,s+32) 414 let ty:i64=nx_elf_get_u32_le(b,s+4) 415 if ty!=8{if cds_span(n,off,size)==0{return CDS_INVALID}} 416 if z==11{ 417 if cdl_bytes_eq(b+noff+name,z,".debug_line",11)==1{ 418 if ty!=1{return CDS_UNKNOWN} 419 if (nx_elf_get_u64_le(b,s+8)&2050)!=0{return CDS_UNKNOWN} 420 if off<64{return CDS_INVALID} 421 if off<table+count*stride{if table<off+size{return CDS_INVALID}} 422 var j:i64=0;while j<count{ 423 if j!=i{let other:i64=table+j*stride;let ot:i64=nx_elf_get_u32_le(b,other+4) 424 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) 425 if cds_span(n,oo,oz)==0{return CDS_INVALID} 426 if oz>0{if off<oo+oz{if oo<off+size{return CDS_INVALID}}} 427 } 428 };j=j+1 429 } 430 let rc:i64=cds_line(c,off,size);if rc!=0{return rc};c.sections=c.sections+1 431 } 432 } 433 i=i+1 434 } 435 return 0 436} 437func cds_run(c:*NxCdStructured,start:i64)->i64{ 438 var n:i64=0;while start+n<c.bytes{ 439 if n>0{if c.marks[start+n]!=0 as u8{break}} 440 if cdl_is_print(c.buf[start+n] as i64)==0{break};n=n+1 441 };return n 442} 443func cds_file_present(a:*NxCdStructured,at:i64,n:i64,b:*NxCdStructured)->i64{ 444 var j:i64=0;while j<b.bytes{ 445 if b.marks[j]==CDS_FILE as u8{ 446 let z:i64=cdl_runlen(b.buf,b.bytes,j) 447 if z==n{if cdl_bytes_eq(a.buf+at,n,b.buf+j,z)==1{return 1}} 448 };j=j+1 449 };return 0 450} 451// out adds structured table evidence separate from raw content evidence. 452struct NxCdResult {state:i64,runs:i64,checked:i64,lost:i64,source_removed:i64,source_added:i64} 453 454struct NxCdSpan {offset:i64,length:i64,kind:i64} 455// Single segmented traversal for measurement, classification and enumeration. 456// Requires successfully validated contexts from cds_elf/cds_measure. 457func cds_next_span(c:*NxCdStructured,cursor:*i64,s:*NxCdSpan)->i64{ 458 while cursor[0]<c.bytes{ 459 let at:i64=cursor[0];let n:i64=cds_run(c,at) 460 if n>0{cursor[0]=at+n;s.offset=at;s.length=n;s.kind=c.marks[at] as i64;return 1} 461 cursor[0]=at+1 462 };return 0 463} 464func cds_span_missing(a:*NxCdStructured,b:*NxCdStructured,s:*NxCdSpan,maxlen:i64)->i64{ 465 var n:i64=s.length;if maxlen>0{if n>maxlen{n=maxlen}} 466 return 1-cdl_contains(b.buf,b.bytes,a.buf,s.offset,n) 467} 468func cds_source_missing(a:*NxCdStructured,b:*NxCdStructured,s:*NxCdSpan)->i64{ 469 if s.kind!=CDS_FILE{return 0};return 1-cds_file_present(a,s.offset,s.length,b) 470} 471const CDS_CLASS_SOURCE:i64=1 472const CDS_CLASS_IDENT:i64=2 473const CDS_CLASS_SECTION:i64=3 474const CDS_CLASS_OTHER:i64=4 475func cds_span_class(a:*NxCdStructured,s:*NxCdSpan)->i64{ 476 if s.kind==CDS_FILE{return CDS_CLASS_SOURCE} 477 if cdl_is_srcpath(a.buf,s.offset,s.length)==1{return CDS_CLASS_SOURCE} 478 if cdl_is_ident(a.buf,s.offset,s.length)==1{return CDS_CLASS_IDENT} 479 if cdl_is_sectname(a.buf,s.offset,s.length)==1{return CDS_CLASS_SECTION} 480 return CDS_CLASS_OTHER 481} 482// Exact permil without the potentially overflowing lost*1000 product. 483func cds_permil(lost:i64,checked:i64)->i64{ 484 if lost<0||checked<0||lost>checked{return CDS_INVALID};if checked==0{return 0} 485 var remainder:i64=0;var value:i64=0;var i:i64=0 486 while i<1000{ 487 if remainder>=checked-lost{remainder=remainder-(checked-lost);value=value+1}else{remainder=remainder+lost};i=i+1 488 };return value 489} 490 491func cds_measure(a:*NxCdStructured,b:*NxCdStructured,prm:*i64,out:*NxCdResult)->i64{ 492 out.state=0;out.runs=0;out.checked=0;out.lost=0;out.source_removed=0;out.source_added=0 493 if prm[CDL_P_MINLEN]<=0||prm[CDL_P_MAXSAMPLES]<0||prm[CDL_P_MAXTOKLEN]<0{out.state=CDS_INVALID;return out.state} 494 let ar:i64=cds_elf(a);if ar!=0{out.state=ar;return ar} 495 let br:i64=cds_elf(b);if br!=0{out.state=br;return br} 496 var i:i64=0;var span:NxCdSpan 497 while cds_next_span(a,&i,&span)==1{ 498 if span.length>=prm[CDL_P_MINLEN]{out.runs=out.runs+1} 499 out.source_removed=out.source_removed+cds_source_missing(a,b,&span) 500 } 501 i=0;while cds_next_span(b,&i,&span)==1{out.source_added=out.source_added+cds_source_missing(b,a,&span)} 502 var stride:i64=1;let maxs:i64=prm[CDL_P_MAXSAMPLES] 503 if maxs>0{stride=out.runs/maxs};if stride<1{stride=1} 504 var idx:i64=0;i=0 505 while cds_next_span(a,&i,&span)==1{ 506 if span.length>=prm[CDL_P_MINLEN]{ 507 var take:i64=0;if idx%stride==0{take=1};if maxs>0{if out.checked>=maxs{take=0}} 508 if take==1{ 509 out.lost=out.lost+cds_span_missing(a,b,&span,prm[CDL_P_MAXTOKLEN]);out.checked=out.checked+1 510 };idx=idx+1 511 } 512 } 513 return 0 514}