Check exceptions between static property builders, in the JSONL parse loop and in the JSONP fast path
RO
2mo ago+18+18+01234567890123456789-6-6-0123456789
Problem
- Three JSC paths run a second throwing operation, or return from a ThrowScope, while the previous throwing call has not been checked. A debug build aborts on each under
validateExceptionChecks=1:ERROR: Unchecked JS exceptionfromJSC::VM::verifyExceptionCheckNeedIsSatisfied. JSObject::reifyAllStaticProperties(JSObject.cpp:2978) andsetUpStaticFunctionSlot(Lookup.cpp:73) readvm.exceptionForInspection()after aPropertyCallbackbuilder. That read is not a check, so a builder with its own ThrowScope leaves the check pending and the next builder's scope constructor asserts. Bun'snode:moduleandBun.sqllazy properties are built this way (bun'stest/no-validate-exceptions.txtlists 8 tests for this).LiteralParser::tryStreamingParse(LiteralParser.cpp:1985) callsparseRecursivelyper line with no scope and no check.Interpreter::executeProgram's JSONPdoGetlambda (Interpreter.cpp:1135) callsPropertySlot::getValueaftergetPropertySlotwith no check.
Fix
- The two reify sites use
vm.exception(), which is the check the builder's scope needs. Behavior is unchanged: both already returned early on a pending exception. tryStreamingParsedeclares a ThrowScope and checks after each value. An out-of-memory or stack-overflow exception is no longer reported asStatus::Errorwith the exception still pending; the caller (Bun.JSONL.parsein bun, which checks right afterstreamingJSONParse) reports it.doGetchecks betweengetPropertySlotandgetValue. Both call sites already check right afterdoGet.- Verified: read against the validator's rules in
ThrowScope.cpp/VM::verifyExceptionCheckNeedIsSatisfied. I could not build JSC in the environment this was written in; CI is the build check. The bun side reproduces the first case withBUN_JSC_validateExceptionChecks=1 bun -e 'Bun.sql'on a debug build.
Background
BUN_JSC_validateExceptionChecks=1makes every ThrowScope destructor setVM::m_needExceptionCheck. The next ThrowScope constructor or non-released destructor asserts if the bit is still set. OnlyVM::exception()(andclearException) clears it.exceptionForInspection()readsm_exceptionwithout touching the bit, for code that explicitly must not count as a check.- A static hash table
PropertyCallbackbuilder has the signatureJSValue(VM&, JSObject*), so it cannot propagate an exception toreifyStaticProperty. The bun fork already lets it return empty with an exception pending; the two callers check and stop. This change only makes that check visible to the validator.