Repository navigation
[JSC] addErrorInfo() drops what the host's stack formatter throws - #774
Conversation
With a host that formats stacks (VM::onComputeErrorInfoJSValue; Bun),
addErrorInfo() runs the formatter while the parser's error object is being
made. The formatter can run Error.prepareStackTrace, so it can throw, and its
ThrowScope asks its callers for an exception check either way. The Function
constructor makes no such check: constructFunctionSkippingEvalEnabledCheck()
gets the error back and throws it, so `new Function("(")` aborts under
--validateExceptionChecks=1, and a throw from Error.prepareStackTrace was
pending while the SyntaxError was thrown over it.
addErrorInfo() now catches around the formatter and clears what it threw,
short of a termination. The caller made the error to throw it: the
SyntaxError is what the program sees, as it already did, through the Function
constructor, eval and vm.Script alike.
There was a problem hiding this comment.
I reviewed this PR and found no bugs; since it changes exception-scope handling in JSC's error-construction path (a CODEOWNERS-covered area with no in-tree test for the host-formatter path), a human look would still be worthwhile.
What was reviewed:
- The restructured branch:
hostComputes == falseand non-Bun builds still reach the samematerializeErrorInfoIfNeeded(vm)call and putDirect fallthrough as before — no behavior change there. - Choice of
DECLARE_TOP_EXCEPTION_SCOPE: this tree has noCatchScope;TopExceptionScopeis the fork's catch idiom (same ascreateTypeErrorCopyin the same file) and nests correctly inside callers' scopes. - Whether the new scope's constructor could itself trip
validateExceptionCheckson an earlier unchecked throw — ruled out, since the host formatter's ownThrowScopeconstructor already performed that same verification before this change. - Whether dropping a throwing
Error.prepareStackTracechanges what programs observe — ruled out: previously the SyntaxError was thrown over the pending exception anyway, so the observable result (SyntaxError) is the same.
Extended reasoning...
The change is a single-file, roughly ten-line edit in Source/JavaScriptCore/runtime/Error.cpp inside the USE(BUN_JSC_ADDITIONS) branch of addErrorInfo(), wrapping the host stack formatter call in a TopExceptionScope and clearing non-termination exceptions before returning. It touches no injection, auth, or data-exposure surface. The code is correct as far as I can verify against TopExceptionScope, ThrowScope, and ErrorInstance::materializeErrorInfoIfNeeded, and the hostComputes==false path is unchanged. Deferring rather than approving because /Source/JavaScriptCore is covered by .github/CODEOWNERS, the change alters exception-propagation semantics in a core engine path, and the only test coverage lives in the Bun repository rather than in this tree.
|
Preview build of 6bd59af: |
What
With a host that formats stacks (
VM::onComputeErrorInfoJSValue; Bun), the fork'saddErrorInfo()runs the formatter while the parser's error object is being made. The formatter can runError.prepareStackTrace, so it can throw, and itsThrowScopeasks its callers for an exception check either way.The Function constructor makes no such check.
constructFunctionSkippingEvalEnabledCheck()gets the error back fromFunctionExecutable::fromGlobalCode()and throws it. So:new Function("(")aborts a build run with--validateExceptionChecks=1(Bun's ASAN lane setsBUN_JSC_validateExceptionChecks=1):when
Error.prepareStackTracethrows, that exception is still pending while the SyntaxError is thrown over it.eval and
vm.Scriptdo check after the error is made, which is why only the Function constructor trips.Fix
addErrorInfo()catches around the formatter and clears what it threw, short of a termination. The caller made the error to throw it, so the SyntaxError is what the program sees.Behaviour does not change. With an
Error.prepareStackTracethat throws,new Function("("),(0, eval)("(")andnew vm.Script("(")each throw the SyntaxError before and after this change (Node 22 does the same).Verification
Bun built against this tree (debug + ASAN, Linux x64), with
BUN_JSC_validateExceptionChecks=1:new Function("(")throws a SyntaxError and no longer aborts, with the default formatter and with anError.prepareStackTracethat throws.new Functionassertions that Upgrade WebKit to dbdca7545d bun#44615 keeps as atest.todointest/js/bun/jsc/webkit-upgrade-dbdca7545d.test.tspass.capture-stack-trace.test.js,inspect-error.test.jsandvm.test.tspass.There is no JSTests case: the
jscshell has no stack formatting hook, so this path does not exist there. The test is the one in Bun, which stops being a todo when Bun's WebKit pin includes this.