Enter a TRAP handler when the variable table is full

Two separable faults, both on the path a program takes when it is already in
trouble.

`akbasic_trap_set_error_variables()` reached `ER#` and `EL#` through
`akbasic_runtime_global()`, which *creates* a name the program never used -- and
creating one takes a variable slot. So a program that had filled the 128-slot
table could not have its handler entered at all, and because the failure
happened inside the error path rather than raising, nothing was reported and the
program carried on with the failing statement's effect quietly missing. A wrong
answer delivered as a right one, which is worse than an abort.

`akbasic_runtime_reserve_globals()` now creates both at runtime init, where
there is always room, and `clear_variables()` puts them back after `CLR` and
`NEW` empty the table.

Second: `report_and_reraise()` used a plain `PASS` around the report, so a
failure while reporting *replaced* the error the program had actually made --
"Maximum runtime variables reached" in place of the subscript that was out of
range. The secondary failure is now logged and the original is re-raised, which
is what a user needs to hear.

**The reduction in TODO.md no longer reproduces, and not because of this.** The
value-pool fix in the previous commit made a scalar free, so the pool can no
longer be emptied by creating names. The defect was still live through the
variable table: 124 names and a TRAP armed, and the handler was silently
skipped. That is what the new test in tests/trap_verbs.c pins, together with the
invariant -- both globals present before a program runs.

Verified by reverting the fix against the new tests: both fail, and the second
prints the log line the swallow used to eat, "could not report a BASIC error 515
(Out Of Bounds): Maximum runtime variables reached".

TODO.md section 6 item 33, struck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 23:48:56 -04:00
parent 05f241aca1
commit f5a35b4af6
5 changed files with 159 additions and 3 deletions

18
TODO.md
View File

@@ -1969,8 +1969,22 @@ in either corpus runs for minutes, which is why the first of these had never bee
the argument against it is that it makes `moveto` write to the grid, which it currently
does not.
33. **A `TRAP` handler cannot be entered once the value pool is dry, and the error is dropped
in silence.** `akbasic_trap_set_error_variables()` (`src/runtime_trap.c:36`) sets `ER#`
33. ~~**A `TRAP` handler cannot be entered once the value pool is dry, and the error is
dropped in silence.**~~ **Done**, in two halves. `akbasic_runtime_reserve_globals()`
(`src/runtime.c`) creates `ER#` and `EL#` at runtime init and again after `CLR`, so the
dispatch never allocates -- there is nothing left for it to fail at. And
`report_and_reraise()` no longer lets a failure *inside* reporting replace the error the
program actually made: the secondary one is logged where a developer will see it and the
original is re-raised.
**The reduction below no longer reproduces, and not because of this fix.** §6 item 30
made a scalar free, so the value pool can no longer be emptied by creating names and
`ER#`/`EL#` could be created after all. The defect was still there, reachable through the
*variable* table instead: 124 names and a `TRAP` armed, and the handler was silently
skipped while the program carried on. That is what `tests/trap_verbs.c` now pins, along
with the invariant itself -- both globals present before a program runs.
The original report: `akbasic_trap_set_error_variables()` (`src/runtime_trap.c:36`) sets `ER#`
and `EL#` through `akbasic_runtime_global()` (`:51` and `:53`), which *creates* them if
the program never named them. Creating them needs pool slots. So the one error most
likely to leave the pool empty -- item 30's -- is the one error whose handler cannot be