Skip to content

inspector: bump WebKit so Console.enable replay survives validateExceptionChecks - #36472

Open
robobun wants to merge 5 commits into
mainfrom
farm/b3cb6d6c/inspector-console-enable-exception-checks
Open

inspector: bump WebKit so Console.enable replay survives validateExceptionChecks#36472
robobun wants to merge 5 commits into
mainfrom
farm/b3cb6d6c/inspector-console-enable-exception-checks

Conversation

@robobun

@robobun robobun commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Reproduction

BUN_JSC_validateExceptionChecks=1 bun-debug --inspect-wait=127.0.0.1:0 \
  -e 'console.log("A"); console.log("B"); setInterval(()=>{},1000);'
# connect ws, send Inspector.enable + Inspector.initialized,
# wait for A/B on stdout, then send Console.enable -> child SIGABRTs
ERROR: Unchecked JS exception:
    This scope can throw a JS exception: getOwnNonIndexPropertyNames @ runtime/JSObject.cpp:2826
        (ExceptionScope::m_recursionDepth was 2)
    But the exception was unchecked as of this scope: get @ JSObjectInlines.h:133
        (ExceptionScope::m_recursionDepth was 2)
ASSERTION FAILED: exception check validation failed

This is pre-existing on main and was surfaced by #36456, which currently strips the validation env vars from its spawned inspectee to work around it.

Cause

jsToInspectorValue (Source/JavaScriptCore/inspector/InjectedScriptBase.cpp) calls getOwnPropertyNames and then object.get() on each property with no exception check between them. The call chain is

InspectorConsoleAgent::enable()
  -> ConsoleMessage::addToFrontend
    -> InjectedScript::wrapObject
      -> toInspectorValue
        -> jsToInspectorValue

The live addConsoleMessage path (agent already enabled, called from the console.log native) does not trip this: with JS on the stack m_vm.topEntryFrame is between the inner ThrowScope and its m_previousScope, so the destructor sets willBeHandleByLLIntOrJIT = true and skips simulateThrow(). Console.enable's replay runs from backend dispatch with no JS frame, topEntryFrame is null, the destructor in getOwnNonIndexPropertyNames simulates a throw, and the next ThrowScope constructor in JSObject::get sees it unchecked.

Fix

oven-sh/WebKit#376 declares a ThrowScope in the object branch of jsToInspectorValue with RETURN_IF_EXCEPTION after getIndex, getOwnPropertyNames, get, and the recursive calls, and gives toInspectorValue (the JSLockHolder entry point) a TopExceptionScope that clears any non-termination exception before returning, so nothing leaks to the scope-free inspector callers that already treat a null return as failure.

This PR bumps WEBKIT_VERSION to that change's preview build and adds test/cli/inspect/inspect-exception-checks.test.ts, which connects to --inspect-wait with validateExceptionChecks=1, lets two console.log calls buffer, then sends Console.enable and asserts the buffered messages are replayed without the inspectee aborting.

Verification

fail-before (debug build, current WebKit pin 34c01d1339)
$ ./build/debug/bun-debug test test/cli/inspect/inspect-exception-checks.test.ts
error: inspectee aborted under validateExceptionChecks (reply={"closed":{"code":1006,"reason":"Connection ended"}}):
ERROR: Unchecked JS exception:
    This scope can throw a JS exception: getOwnNonIndexPropertyNames @ vendor/WebKit/Source/JavaScriptCore/runtime/JSObject.cpp:2826
    But the exception was unchecked as of this scope: get @ .../JSObjectInlines.h:133
ASSERTION FAILED: exception check validation failed
(fail) Console.enable replay of buffered messages does not trip exception-check validation [851.71ms]
pass-after (debug build, local WebKit with the fix)
$ ./build/debug-local/bun-debug test test/cli/inspect/inspect-exception-checks.test.ts
(pass) Console.enable replay of buffered messages does not trip exception-check validation [760.52ms]
 1 pass
 0 fail

The fix lives in scripts/build/deps/webkit.ts (not src/), so a src/-stashing fail-before check sees the new WebKit in both arms; the fail-before evidence above is against a build with the current pin.

Merge gate

WEBKIT_VERSION is currently pinned to autobuild-preview-pr-376-e68eb1fd so CI can exercise the fix before oven-sh/WebKit#376 merges. Preview tags point at PR-branch commits that are not on WebKit main and have no retention guarantee, so before merging this PR, oven-sh/WebKit#376 must be merged first and WEBKIT_VERSION here swapped to the resulting 40-hex main sha.

The preview also carries oven-sh/WebKit#333 (CodeBlock execution-count aging, default-off) which sits between the current pin and WebKit main.


[decide:webkit] gate passed · iteration 1 · 3 files touched

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

Debug/ASAN (expected pass):
$ bun bd test 'test/cli/inspect/inspect-exception-checks.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/cli/inspect/inspect-exception-checks.test.ts
bun test v1.4.0 (61f207cf4)

test/cli/inspect/inspect-exception-checks.test.ts:
(pass) Console.enable replay of buffered messages does not trip exception-check validation [678.54ms]

 1 pass
 0 fail
 2 expect() calls
Ran 1 test across 1 file. [2.66s]
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                      |   4 +-
 test/cli/inspect/inspect-exception-checks.test.ts | 112 ++++++++++++++++++++++
 test/no-validate-leaksan.txt                      |   1 +
 3 files changed, 116 insertions(+), 1 deletion(-)

gate history · 2 passed · 1 rejected · iteration 1

evidence per changed file
file                                               reads  edits  tests
scripts/build/deps/webkit.ts                           1      1      0
test/cli/inspect/inspect-exception-checks.test.ts      0      1      0
test/no-validate-leaksan.txt                           1      1      0

…ptionChecks

jsToInspectorValue calls getOwnPropertyNames then object.get() with no
exception check between them. InspectorConsoleAgent::enable()'s replay of
buffered console messages runs from backend dispatch with no JS frame on
the stack, so the inner ThrowScope destructor in getOwnNonIndexPropertyNames
simulates a throw and the next ThrowScope constructor in JSObject::get sees
it unchecked. The live addConsoleMessage path has a topEntryFrame and skips
the simulated throw.

oven-sh/WebKit#376 adds a ThrowScope with RETURN_IF_EXCEPTION to the object
branch of jsToInspectorValue and a TopExceptionScope to toInspectorValue.
This bumps WEBKIT_VERSION to that PR's preview build and adds a test that
connects to --inspect-wait with validateExceptionChecks=1, lets two
console.log calls buffer, then sends Console.enable and asserts the
buffered messages are replayed without aborting.

A lint test in webkit-prebuilt-url.test.ts fails while WEBKIT_VERSION is a
preview tag so this cannot merge until oven-sh/WebKit#376 is merged and the
pin swapped to the resulting main sha.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 253100f5-bbca-4629-bcb7-1df43ec5e429

📥 Commits

Reviewing files that changed from the base of the PR and between c08f665 and 61f207c.

📒 Files selected for processing (3)
  • scripts/build/deps/webkit.ts
  • test/cli/inspect/inspect-exception-checks.test.ts
  • test/no-validate-leaksan.txt

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find bugs in the diff, but this is a WebKit version bump that (a) is currently pinned to a preview tag that the PR itself says must be swapped for a real sha before merge, (b) picks up oven-sh/WebKit#333 in addition to #376, and (c) has the new webkit-prebuilt-url.test.ts merge-gate test intentionally failing until that swap happens. A human should coordinate the upstream merge and re-pin.

What was reviewed:

  • The new inspector test — checked that stdout/stderr are drained concurrently, every failure path (ws close/error, child exit) resolves the awaited promises, and cleanup happens via await using + finally.
  • resolveUrl/rejectUrl ordering — reject after the stderr loop is harmless because the URL is resolved on first match and later reject is a no-op on a settled promise.
  • The lint test correctly rejects the current autobuild-preview- pin, so CI enforces the stated merge gate.
Extended reasoning...

Overview

This PR bumps WEBKIT_VERSION in scripts/build/deps/webkit.ts from a 40-hex sha to autobuild-preview-pr-376-e68eb1fd, adds a debug/ASAN-only regression test in test/cli/inspect/inspect.test.ts that exercises Console.enable replay under BUN_JSC_validateExceptionChecks=1, and adds a source-lint test that fails whenever WEBKIT_VERSION is an autobuild-preview-* tag. The actual fix lives in oven-sh/WebKit#376; nothing in src/ changes here.

Security risks

None identified. The change is a dependency pin plus test code. The test spawns a local inspectee on 127.0.0.1:0 and connects over loopback WebSocket — no external network, no credential handling.

Level of scrutiny

High — even though the Bun-side diff is small, a WebKit bump changes the JS engine for every build. The PR description notes the preview also carries oven-sh/WebKit#333 (CodeBlock execution-count aging), so this is not a single-commit bump. The PR is also explicitly not merge-ready as written: the author states the preview pin must be replaced with the post-merge sha, and the new lint test enforces that by failing on the current pin. That coordination (merge oven-sh/WebKit#376, re-pin, verify CI) is a human decision.

Other factors

The new inspector test is well-constructed against the repo's flakiness rules: it awaits observable conditions (URL on stderr, both markers on stdout) rather than sleeping, drains both pipes concurrently, wires ws close/error into the pending-request map so a SIGABRT surfaces as a resolved {closed:...} reply instead of a hang, and uses await using for the child plus try/finally for the socket. The skipIf(!isDebug && !isASAN) gate matches ENABLE_EXCEPTION_SCOPE_VERIFICATION's compile condition. The one intentional oddity — rejectUrl firing unconditionally after the stderr loop — is safe because the promise is already resolved by then in the success case. Given the dependency-bump nature and the explicit merge gate, deferring to a human is the right call.

@robobun

robobun commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

The new test test/cli/inspect/inspect-exception-checks.test.ts passes on the ASAN lane in build #85861. The remaining CI failures there are unrelated flakes that also appear on main's latest builds (test-fs-read-stream-pos.js Windows timeout, spawn-streaming-stdout, terminal-platform-gaps, fetch.stream, napi.test, issue-08965/02499 parallel-batch flakes).

Before merging, oven-sh/WebKit#376 must be merged and WEBKIT_VERSION in scripts/build/deps/webkit.ts swapped from the preview tag to the resulting 40-hex main sha.

robobun and others added 2 commits July 30, 2026 16:05
inspect.test.ts has pre-existing localhost-vs-[::1] failures in the gate
environment and is ASAN-quarantined in CI, so the new test never ran there.
Drop the preview-pin lint (the preview pin is called out in the PR body and
in the webkit.ts comment; the PR will swap to the main sha once
oven-sh/WebKit#376 merges).

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any issues with the test or the leaksan entry, but this PR has an explicit merge gate that hasn't been satisfied: WEBKIT_VERSION is still pinned to the preview tag autobuild-preview-pr-376-e68eb1fd, and the description says oven-sh/WebKit#376 must merge first and the pin swapped to the resulting 40-hex main sha before this lands. A WebKit bump also pulls in oven-sh/WebKit#333 along the way, so a human should sign off on the engine bump.

What was reviewed:

  • The new test awaits observable conditions (URL from stderr, buffered stdout, WS open/reply), wires error/close to fail pending sends, drains both pipes concurrently, and cleans up via await using + finally.
  • Assertions are strong (reply shape via toMatchObject, replayed messages via arrayContaining), and the SIGABRT branch surfaces the captured stderr.
  • no-validate-leaksan.txt entry is placed with the other test/cli/inspect/ files under the Subprocess-finalize category.
Extended reasoning...

Overview

This PR bumps WEBKIT_VERSION in scripts/build/deps/webkit.ts to a preview build of oven-sh/WebKit#376 (which adds RETURN_IF_EXCEPTION checks in jsToInspectorValue and a TopExceptionScope in toInspectorValue), adds a regression test at test/cli/inspect/inspect-exception-checks.test.ts that reproduces the Console.enable replay abort under BUN_JSC_validateExceptionChecks=1, and adds that test to test/no-validate-leaksan.txt alongside the other inspector tests.

Security risks

None. The change is a debug-only exception-scope validation fix inside JSC's inspector layer plus a test that spawns a local child on 127.0.0.1:0. No auth, crypto, or untrusted-input parsing is touched on the Bun side.

Level of scrutiny

High — not because of the diff in this repo (which is small and well-contained), but because the substantive change is a WebKit engine bump. The preview tag is explicitly temporary: the PR description and the robobun comment both state that oven-sh/WebKit#376 must be merged and WEBKIT_VERSION swapped to a 40-hex main sha before this lands. The preview also carries oven-sh/WebKit#333 (CodeBlock execution-count aging), so merging this effectively ships two WebKit changes. That's a maintainer decision, not something a bot should approve.

Other factors

The test itself is solid per the repo's review rules: it uses test.skipIf(!isDebug && !isASAN) matching ENABLE_EXCEPTION_SCOPE_VERIFICATION, spreads bunEnv, uses --inspect-wait=127.0.0.1:0 (no hardcoded port), buffers stderr until the ws:// URL appears, races bufferedPromise against child.exited so an early crash doesn't hang, resolves pending WS sends on close/error rather than throwing in the callback, and asserts both the Console.enable reply shape and the replayed message contents. Cleanup is via await using child plus a finally that closes the socket and kills the child before the assertions that can throw. The prebuiltDestDir logic in webkit.ts already handles autobuild--prefixed versions, so the preview tag flows through the existing build machinery without further changes.

Given the unsatisfied merge gate and the fact that WebKit bumps are engine-level changes, deferring to a human is the right call.

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