Skip to content

Bump WebKit: console functions release their ThrowScope before the console hook - #44950

Draft
robobun wants to merge 1 commit into
mainfrom
robobun/69b487cb/console-assert-exception-check
Draft

robobun wants to merge 1 commit into
mainfrom
robobun/69b487cb/console-assert-exception-check

Conversation

@robobun

@robobun robobun commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • A failing console.assert aborts a debug or ASAN build under BUN_JSC_validateExceptionChecks=1 (CI's ASAN lane sets it): But the exception was unchecked as of this scope: consoleProtoFuncAssert @ runtime/ConsoleObject.cpp:218.
  • consoleProtoFuncAssert (JavaScriptCore) holds a ThrowScope across Bun's console hook and returns with no check or release. The hook can throw and has its own scope since Add jsc-exception-lint and fix the missing exception checks it finds #40410.
  • Eight more console functions have this shape (11 exits), inert while their hooks declare no scope.

Fix

Background

  • Under the validator, a ThrowScope destructor marks a check as needed unless it returns to JIT code. The next unreleased scope asserts on that mark.
  • Rejected: removing the hook's scope in Bun (hides the 12 exits), and RETURN_IF_EXCEPTION after each call (a load and a branch in release code, scope.release() is free).

Downsides

  • console.assert no longer polls VM traps. Its loop does (check_traps). A Worker spinning in console.assert(true) terminates in 10 of 10 runs at both pins.
  • None found in release code: assert shrinks from 522 to 404 bytes, the other eight are identical.
Notes

Builds. "Current pin" is 0c06faadf65bf8e8c8ad3a5a8aca83e1e9ed653f. The preview is autobuild-preview-pr-790-cc38fc87: 1 commit on top of the pin, Source/JavaScriptCore/runtime/ConsoleObject.cpp +11/-3, no other file. The preview release has the same 42 assets as the release of the pin. All runs below are debug ASAN builds of this branch on Linux x64 that differ only in the WebKit build.

Fail-before on the current pin, one of the four rows:

error: expect(received).toEqual(expected)
  {
-   "exitCode": 0,
-   "stdout": "returned",
-   "unchecked": [],
+   "exitCode": 134,
+   "stdout": "",
+   "unchecked": [
+     "This scope can throw a JS exception: messageWithTypeAndLevel @ unified/../../../src/jsc/bindings/ConsoleObject.cpp:38",
+     "But the exception was unchecked as of this scope: consoleProtoFuncAssert @ vendor/WebKit/Source/JavaScriptCore/runtime/ConsoleObject.cpp:218",
+   ],
  }
(fail) console.assert with a falsy condition
 4 pass
 4 fail

With the preview build: 8 pass, 0 fail.

Forms. Each run alone under the validator: console.assert(false), console.assert(), console.assert(false, "x"), console.assert(0, {a:1}), console.assert(null, "%s:%d", "a", 1), console.assert("", new Error("e")), Reflect.apply(console.assert, console, [false, "x"]), [0].forEach(console.assert), JSON.stringify({ toJSON: console.assert }). 9 of 9 exit 134 on the current pin. 0 of 9 on the preview. console.assert(true) exits 0 on both.

The 11 exits that no test can hold today. count, countReset, profile (2), profileEnd (2), takeHeapSnapshot (2), time, timeLog, timeEnd. Their Bun hooks declare no ThrowScope, so nothing marks a check after they return.

  • scripts/jsc-exception-lint on the JavaScriptCore file, with summaries for ConsoleClient.cpp, ScriptArguments.cpp and ScriptCallStackFactory.cpp: 12 findings before (all unchecked-exit, one for each exit), 0 after.
  • Local experiment, not in this PR: a ThrowScope with RELEASE_AND_RETURN in each of the eight Bun hooks. One snippet for each exit, each alone under the validator. 12 of 12 abort on the current pin, each at its own scope (consoleProtoFuncCount @ ConsoleObject.cpp:235, consoleProtoFuncTime @ :326, and so on). 0 of 12 abort on the preview.

Simulated throws for each call under the validator (gdb hit count on JSC::ThrowScope::simulateThrow, 1,001 calls minus 101 calls, divided by 900). console.count("x"): 0 before and after. console.log("x"): 0 and 0. console.timeLog("l", {}): 5 and 5. console.assert(true): 0 and 0. console.assert(false): abort at the first call before, 0 after. In a validator build each of the 11 sibling call sites now also stores one flag byte (m_isReleased).

Release code. ConsoleObject.cpp of the pin and of the WebKit PR, each compiled as one file with clang 23.1.2 and the flags of the bun-webkit-linux-amd64-lto build (-O3, without -flto). consoleProtoFuncAssert: 522 to 404 bytes, 149 to 119 instructions. The path of console.assert(true) through it: 55 to 46 instructions, 8 to 7 conditional branches. The other 26 functions of the file: 0 differing objdump lines. I did not make a release build of Bun with the preview.

VM traps. RETURN_IF_EXCEPTION also handles pending VM traps (termination, the debugger, the watchdog). The removed one in assert was such a poll. for (let i = 0; i < n; i++) console.assert(true) compiles to loop_hint, check_traps, ..., call_ignore_result, so the loop polls on each iteration. Worker test: the worker posts one message and spins in for (;;) console.assert(true), the parent waits 300 ms and calls terminate(). Interleaved, 10 runs for each build: 10 of 10 terminate on both. Median 380 ms (241 to 491) on the current pin, 430 ms (237 to 550) on the preview. The difference is inside the spread.

Other suites on the preview build, with BUN_JSC_validateExceptionChecks=1. test/js/web/console/, test/js/bun/console/ and test/js/bun/jsc/exception-checks.test.ts: 109 pass, 1 skip, 2 todo, 0 fail. Node's test/js/node/test/parallel/test-console-*.js: 16 of 16 exit 0.

Not changed.


[policy-decision:webkit] gate passed · iteration 0 · 2 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/js/bun/jsc/exception-checks.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/bun/jsc/exception-checks.test.ts
bun test v1.4.4 (367d939d9)

test/js/bun/jsc/exception-checks.test.ts:
(pass) Bun.deepEquals with one argument [1279.79ms]
(pass) console.assert with a falsy condition [1043.65ms]
(pass) process.exitCode assigned a rope string [1109.72ms]
(pass) process.kill with an unknown rope signal name [1100.83ms]
(pass) process.umask with a rope string [1143.90ms]
(pass) console.assert called by native code [472.93ms]
(pass) console.assert with a falsy condition and a message [664.78ms]
(pass) console.assert with a message whose custom inspect function throws [1455.06ms]

 8 pass
 0 fail
 8 expect() calls
Ran 8 tests across 1 file. [11.28s]
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts             |  2 +-
 test/js/bun/jsc/exception-checks.test.ts | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                      reads  edits  tests
scripts/build/deps/webkit.ts                  1      1      7
test/js/bun/jsc/exception-checks.test.ts      1      1      7

… caller

A failing console.assert aborted a debug or ASAN build under
BUN_JSC_validateExceptionChecks=1:

    ERROR: Unchecked JS exception:
        This scope can throw a JS exception: messageWithTypeAndLevel @ src/jsc/bindings/ConsoleObject.cpp:38
        But the exception was unchecked as of this scope: consoleProtoFuncAssert @ vendor/WebKit/Source/JavaScriptCore/runtime/ConsoleObject.cpp:218

oven-sh/WebKit#790 changes the console host functions in JavaScriptCore.
assert holds no ThrowScope. The eight other functions that hold one
release it before they call the ConsoleClient hook.

WEBKIT_VERSION points at the preview build of that PR. Move it to the
merged commit before this merges.

test/js/bun/jsc/exception-checks.test.ts gets four console.assert rows.
@robobun

robobun commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 5:23 PM PT - Oct 10th, 2026

❌ @robobun, your commit f7dc2c2 has 1 failures in Build #124390 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 44950

That installs a local version of the PR into your bun-44950 executable, so you can run:

bun-44950 --bun

@robobun

robobun commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator Author

Status

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant