Skip to content

inspector: check exceptions in jsToInspectorValue - #376

Open
robobun wants to merge 1 commit into
mainfrom
robobun/inspector-jstoinspectorvalue-exception-checks
Open

inspector: check exceptions in jsToInspectorValue#376
robobun wants to merge 1 commit into
mainfrom
robobun/inspector-jstoinspectorvalue-exception-checks

Conversation

@robobun

@robobun robobun commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

jsToInspectorValue calls getOwnPropertyNames (which reaches getOwnNonIndexPropertyNames and declares a ThrowScope) and then calls object.get() on each property without checking for an exception in between. Under validateExceptionChecks this aborts when the inspector backend dispatches with no JS on the stack:

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)

The path that hits this in Bun is InspectorConsoleAgent::enable() replaying buffered ConsoleMessages: each message calls InjectedScript::wrapObject, whose result is passed to toInspectorValue. Because there is no topEntryFrame (no JS on the stack during backend dispatch), the inner ThrowScope destructor in getOwnNonIndexPropertyNames simulates a throw, and the next ThrowScope constructor in JSObject::get sees it unchecked. The live addConsoleMessage path (already enabled, called from console.log native) does not trip this because a topEntryFrame is present and the destructor skips the simulated throw.

Declare a ThrowScope in the object branch and RETURN_IF_EXCEPTION after getIndex, getOwnPropertyNames, get, and the recursive calls. Give toInspectorValue (the JSLockHolder entry point) a TopExceptionScope and clear any non-termination exception before returning so nothing leaks to the scope-free inspector callers (InjectedScript::wrapObject and friends), which already treat a null return as failure.

jsToInspectorValue calls getOwnPropertyNames (which reaches
getOwnNonIndexPropertyNames and declares a ThrowScope) and then calls
object.get() on each property without checking for an exception in
between. Under validateExceptionChecks this aborts when the inspector
backend dispatches with no JS on the stack, e.g. when
InspectorConsoleAgent::enable() replays buffered console messages: the
inner ThrowScope destructor simulates a throw because there is no
topEntryFrame to attribute it to, and the next ThrowScope constructor
in JSObject::get sees it unchecked.

Declare a ThrowScope in the object branch and RETURN_IF_EXCEPTION after
getIndex, getOwnPropertyNames, get, and the recursive calls. Give
toInspectorValue (the JSLockHolder entry point) a TopExceptionScope and
clear any exception before returning so the simulated throw does not
leak to scope-free inspector callers.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ab365745-7167-4cac-9e78-7b2d9c793fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9fa3d and e68eb1f.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/inspector/InjectedScriptBase.cpp

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

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM — mechanical exception-scope hygiene fix following the established JSC pattern.

What was reviewed:

  • Verified TopExceptionScope / DECLARE_TOP_EXCEPTION_SCOPE / clearExceptionExceptTermination API matches usage here (same pattern as JSMicrotask.cpp, JSPromise.cpp).
  • Checked all toInspectorValue callers (InjectedScript.cpp, InjectedScriptBase::makeCall/makeAsyncCall) already treat a null return as failure, so clearing and returning nullptr is safe.
  • Confirmed RETURN_IF_EXCEPTION placement covers each throwing site (getIndex, getOwnPropertyNames, get, and the recursive calls) named in the validateExceptionChecks trace.
Extended reasoning...

Overview

This PR touches a single file, Source/JavaScriptCore/inspector/InjectedScriptBase.cpp, adding exception-scope discipline to jsToInspectorValue and its public entry point toInspectorValue. In the object branch it declares a ThrowScope and inserts RETURN_IF_EXCEPTION(scope, nullptr) after array.getIndex, getOwnPropertyNames, object.get, and each recursive call. At the entry point it adds a TopExceptionScope (after the existing JSLockHolder) and clears any non-termination exception before returning nullptr, so nothing leaks to inspector callers that have no exception scope of their own.

Security risks

None. This is defensive exception-checking that only changes behavior when a JS exception is already pending (or when validateExceptionChecks simulates one). No new data flows, no untrusted input handling, no auth/permissions surface. The inspector already serializes these values; the only difference is that on exception the function now bails cleanly instead of continuing with a potentially bogus JSValue or aborting under the exception validator.

Level of scrutiny

Low. The change is small (~25 lines net), purely additive, and follows an idiom used pervasively across the codebase — I confirmed identical DECLARE_TOP_EXCEPTION_SCOPE + scope.exception() + clearExceptionExceptTermination() sequences in runtime/JSMicrotask.cpp, runtime/JSPromise.cpp, and runtime/PinballCompletion.cpp. TopExceptionScope inherits exception() from ExceptionScope, so the if (scope.exception()) check compiles in both the verification-enabled and release variants. The VM& declaration was simply hoisted from the non-array object path to the top of the isObject() block so both sub-branches can share the scope.

Other factors

All call sites of toInspectorValue (InjectedScript::wrapObject and siblings in InjectedScript.cpp, plus makeCall/makeAsyncCall in this file) already null-check the result and fall back to an error string, so the new early-nullptr returns integrate without further changes. The PR description precisely matches the observed validateExceptionChecks abort trace and the fix is the canonical remedy for that class of failure. No outstanding reviewer comments; the only timeline entry is a CodeRabbit rate-limit notice.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
e68eb1fd autobuild-preview-pr-376-e68eb1fd 2026-07-30 15:54:51 UTC

robobun added a commit to oven-sh/bun that referenced this pull request Jul 30, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant