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

75 lines
2.5 KiB
Markdown

# 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
```el
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
```el
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
```el
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
```el
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
```el
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.