You are browsing as a guest. Sign up (or log in) to start making projects!

22m 42s logged

Seven bugs, and the ones that were never reachable

Last devlog promised a depth counter for the crash that hit all three hosts. I
built it, then kept reading instead of building, and the audit found six more.

THE COUNTER

A script that recurses too deep does not raise an error. It kills the process.
On a Minecraft server that means every player drops and the log has not one line
about it - no Java exception, no hs_err file, exit code 127.

The tempting fix is a bigger stack, but that only moves the wall: with -Xss16m
the JVM dies at ~5000 frames instead of ~500, the same way. So the interpreter
counts instead. Every script call goes through one function, and past the limit
it raises an ordinary Error - catchable with try/catch, readable through
rs_last_error.

Picking the number mattered more than writing it. My notes said “4000”, my
measurements said that was useless: native and wasm hit the wall near 1000
frames, a default JVM thread at ~500, because each script frame costs about
1.7 KB of native stack. A limit above the wall protects nobody. So: 800, and the
JNI bridge drops it to 400 because it knows what stack it stands on. The counter
also has to be restored no matter HOW a call exits - return, return signal, or
exception. My restore lines covered two of those three paths; a destructor
covers all three and cannot be forgotten.

A CRASH ANY SCRIPT COULD TRIGGER

"a,b".split(5)

That was enough to kill the host. split assumed its argument was a string,
dereferenced a checked cast without checking it, and got a null pointer. Eight
builtins had the same line copied into them. In a standalone language that is a
segfault. In an embedded one, any script author can take down the server with a
typo. It is now a type error naming the method.

OPERATORS THAT WERE NEVER REACHABLE

The lexer produced tokens for & | ^ « ». The interpreter had the arithmetic
for all five. Every test passed. But no rule in the parser ever read those
tokens, so “flags & 4” got “expression expected, found ‘&’”, and the code in the
other two layers had never once run. Three layers agreeing on a feature is not
the same as three layers connected by it. The fix was four precedence levels,
ordered like Python and C - no new keywords, so the language stays at 29 of 30.

THE SMALL ONES

throw Error(“amount missing”) reached the host as “”: the CLI path pulled
out the message field, the embedding path did not. 2 ** 64 wrapped silently to
0. 1 « 64 was undefined behaviour - on x86 the CPU takes the shift count modulo
64, so it quietly evaluates to 1. And an exception thrown from a
single-expression lambda left the interpreter in the lambda’s scope, so the
catch block ran against the wrong variables.

WHERE IT STANDS

The C API suite is 20 checks now, five written for these bugs; the wasm suite 7;
11 examples still run; zero warnings. The guard is verified on native, wasm and
JNI separately, and in each one the process was still alive after the limit was
hit. Three problems stay open, in the README rather than hidden: an exception
escaping a host function leaks stack, string += is quadratic, and a script
returning a string to rs_call still yields 0.

ON THE AI SPLIT

This changes what I said last time, so I am saying it plainly. Until now AI had
not touched src/. This round it did: I asked it to audit the interpreter and fix
what it found, and the C++ in these seven fixes is AI-written from that audit.
The language itself - lexer, parser, resolver, interpreter, REPL, C API, string
channel - is still my design and was written by me. Host bindings and demo apps
were already AI-written from my designs, as I said last devlog. Logged hours are
my editor time only.

0
20

Comments 0

No comments yet. Be the first!