Files
el/docs/v1/experiments/findings/bugs.md
T
bigmerge c6ba0677f0 log v1 experiments: nineteen cycles, organised by the method that produced them
cycles/    one file per Ishikawa -> scientific method -> Six Sigma loop, named
           for the DEFECT not the fix, carrying the commit record as written at
           the time
findings/  what the cycles produced, cross-cut: live bugs, architecture answers,
           and defects in my own measurement

The organising finding is that predictions which came back FALSE produced every
significant result. Eleven of sixty-one failed, and those eleven found: that the
arity table was not drifted but 40% incomplete; that the AST traversal is
irreducible and only rules and judgments move; that guards could refuse through
the seam after all; and that routing el_bin_lookup through the gate did NOT fix
the SIGSEGV, because the fallback strlen was the hazard -- a wrong fix I would
otherwise have shipped as verified.

One cycle was run without committing predictions first and had to be discarded
as rigged. It is kept, in full, as 18-async-half-expressible.md.
2026-08-17 10:52:17 -05:00

2.5 KiB

Live defects found

Every one compiled clean, ran, and produced a wrong result or a crash with no diagnostic at any layer. All four were present before this session; none was introduced by it.

Silent miscompilations

1. An unannotated let loses its type

let a = str_len("hello")     // no annotation
let b = str_len("hi")
let c = a + b                //  el_str_concat(a, b) on two integers

Compiled clean. Printed nothing where it should print 7. Fixed: an unannotated let takes its type from what its initialiser returns. The return types were already required for dispatch and were simply never consulted at the binding site.

2. Reserved keywords that reserved nothing

let seed = 42
let impl = seed + 1

sealed, activate, seed, protocol, impl were keywords in the lexer and consumed by no parser or codegen path. Using one did not fail to parse — it compiled clean, with zero cc errors, and printed 0 instead of 44. Fixed by removing all five.

3. Instant + Int was never refused

let t: Instant = now()
let u: Instant = t + 3       //  (t + 3), reported clean

Duration + Int was refused — "an Int carries no unit" — while adding a dimensionless number to a point silently moved the instant by an unspecified amount. Three of what? Whatever the representation happens to be. The rule was simply never written.

Security-relevant

4. Annotations are never verified

let x: Int = "hello"; x + 1     →  4343631981   a string POINTER used as an integer
let s: String = 42; println(s)  →  nothing      address 42 dereferenced

The first leaks a raw memory address into program output. The second is an arbitrary-read primitive if the integer is ever attacker-influenced.

5. sha256_hex(<integer>) segfaults

let h: String = sha256_hex(50000)      exit 139, SIGSEGV

Compiled clean. el_bin_lookup checked only a 4096 floor — no alignment, no small-int, no negative — and reads eight bytes backward from the pointer. And the actual crash was one level further on: el_input_len fell through to strlen() on address 50000, because a NULL check does not establish that a slot is a pointer.

Fixed, and the guard is now a gate: el_tagged() is exported in el_runtime.h. geom_of and mfld_of were always correct because their authors knew to call looks_like_heap_obj; el_bin_lookup and el_input_len were wrong because theirs did not, and the function was static, so every sibling translation unit re-derived it.