Skip to content

Stop a property lookup after a static-table PropertyCallback builder throws - #475

Open
robobun wants to merge 1 commit into
mainfrom
farm/d6221457/stop-lookup-after-throwing-static-reify
Open

Stop a property lookup after a static-table PropertyCallback builder throws#475
robobun wants to merge 1 commit into
mainfrom
farm/d6221457/stop-lookup-after-throwing-static-reify

Conversation

@robobun

@robobun robobun commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Problem

In this fork a static-table PropertyCallback builder can run JS and throw. setUpStaticFunctionSlot (Lookup.cpp) then reports the slot as not found so that the caller observes the exception. Every caller treats that miss like an ordinary miss and continues the lookup with the exception pending:

  • JSObject::getPropertySlot (JSObject.h) and JSObject::getNonIndexPropertySlot (JSObjectInlines.h) walk on to the prototype. If the prototype overrides getOwnPropertySlot (a Proxy, a host object), its getOwnPropertySlot runs with the exception pending. With validateExceptionChecks=1 this aborts: "Unchecked JS exception ... unchecked as of this scope: getNonIndexPropertySlot @ JSObjectInlines.h:286" (or getOwnPropertySlotCommon @ ProxyObject.cpp when the walk started in getNonIndexPropertySlot). getPropertySlot also takes the prototype from the Structure* it loaded before the builder ran. If the builder transitioned the object before it threw, that asserts in Structure::storedPrototype (StructureInlinesLight.h:56), which is what JSObject::getPropertySlot: reload the structure before the prototype step #390 is about.
  • The megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths (JITOperations.cpp) walk to the end of the chain and record the miss in the MegamorphicCache for the base object's structure. A builder that throws stores nothing, so the structure does not change. Every later megamorphic access of that property on that object then returns undefined (false for in) and never runs the builder again. This reproduces on release builds of Bun 1.4.0: after one read of Bun.sql that throws, every megamorphic o.sql site returns undefined for Bun, while a fresh site returns the function.
  • JSObject::getOwnStaticPropertySlot goes on to the parent class tables.
  • Two verification-only gaps of the same kind, found while testing this. getNonIndexPropertySlot owns a ThrowScope but never checks after the own-property step, so a builder that succeeds on that path (first read of Bun.sql through a function whose prototype is Bun) aborts under validateExceptionChecks=1 when the loop's scope is destroyed. JSObject::reifyAllStaticProperties runs builders back to back, so the first builder's scope is reported as unchecked when the second builder declares its own: ({...Bun}) aborts under the validator even though nothing throws. That is why Bun keeps the test file for its Bun object out of its validator lane.

Fix

After the own-property step on an object whose type has a static property table, check for a pending exception and stop the lookup. That is the only case in which getOwnNonIndexPropertySlot can run a builder, so objects without a static table take the same path as before. In the loops that own a scope (getNonIndexPropertySlot and the four megamorphic helpers) the check is the same RETURN_IF_EXCEPTION they already make after an overridden getOwnPropertySlot, made on a hit as well, which covers the succeeding-builder case. getPropertySlot has no scope of its own, so it checks with exceptionForInspection() on a miss and leaves the check to its caller, as the plain-prototype case does today.

reifyAllStaticProperties gets a TopExceptionScope and checks it after each builder. Not a ThrowScope: that would simulate a throw to callers such as JSObject::deleteProperty that do not check, which is what #306 removed. A TopExceptionScope is what JSBoundFunction::nameSlow uses in the same situation. A real exception still stays pending for the caller exactly as before.

Relation to #390: that PR reloads the structure before the prototype step of getPropertySlot. With this change a throwing builder never reaches that step, so the case #390 fixes becomes unreachable and its test passes with this change alone. The reload is still correct as hardening. Either land #390 first and I rebase this (the two hunks are a few lines apart), or close it as superseded by this.

Verification

Rebased onto b7f217b4 (#477), aea1f010 (#330), c148a12d (#494), the 8c4fd56347 upstream merge (cb61607f), 1cb96a7b (#513), 76882271, 2da33d53 (#519), 72597399 (#521), 0bb01ed5, f5deafe0, 1817c3c3 (#528, the 6b879687ee upstream merge), c4ddc0cf (#527) and ceb9f90f (#530, #531) as Bun moved its pin; the diff is unchanged and applied without edits each time. The preview built from the current head (autobuild-preview-pr-475-94c5a2d5, all lanes green on the first attempt) passes the Bun test file below on Linux x64 debug ASAN, plain and under the validator, plus Bun's ffi-ptr-non-view-cell-arg fixture for #477. Bun debug builds against this PR's preview release (autobuild-preview-pr-475-43f67cb4) pass the Bun test file below on Linux x64 debug ASAN and on Windows x64 debug, plain and under the validator. The same Windows build against 0f966e81 fails five of its tests, including the one that exercised the transition-then-throw case through a $ builder that reifies another property first. Before the release existed, the same was checked against a debug ASAN JSC built locally from this branch and linked into a Bun debug build. Bun tests (oven-sh/bun#39703): a Proxy prototype behind Bun with validateExceptionChecks=1 through both walk loops, the transition-then-throw case from #390, megamorphic get_by_id, get_by_val, in_by_id and in_by_val sites, and the whole file, which spreads Bun, under the validator. All of these fail on a Bun build against the current pin 0f966e81 with the symptoms above (the megamorphic one on release Bun too) and pass against this branch. test/js/bun/util, test/js/bun/jsc, globals (which deletes globals, the deleteProperty route into reifyAllStaticProperties), namespace-prototype-pollution and the sql adapter tests pass against this branch, the first four also under the validator. Spreading process, globalThis and import.meta runs clean under the validator as well.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bf5ef1f1-01f1-4763-a235-d729ceb0e371

📥 Commits

Reviewing files that changed from the base of the PR and between ceb9f90 and 94c5a2d.

📒 Files selected for processing (4)
  • Source/JavaScriptCore/jit/JITOperations.cpp
  • Source/JavaScriptCore/runtime/JSObject.cpp
  • Source/JavaScriptCore/runtime/JSObject.h
  • Source/JavaScriptCore/runtime/JSObjectInlines.h

Included review availability: Your plan provides up to 5 included reviews per hour; 0 remain after this review.


Walkthrough

Changes

Static Property Exception Propagation

Layer / File(s) Summary
Static property reification exception handling
Source/JavaScriptCore/runtime/JSObject.cpp
reifyAllStaticProperties establishes a top-level exception scope and preserves exceptions raised by property callbacks.
Static lookup exception propagation
Source/JavaScriptCore/runtime/JSObject.cpp, Source/JavaScriptCore/runtime/JSObject.h, Source/JavaScriptCore/runtime/JSObjectInlines.h
Static-property and prototype-chain lookups stop when a callback leaves a VM exception pending.
Megamorphic access exception guards
Source/JavaScriptCore/jit/JITOperations.cpp
Megamorphic get and in operations check for pending exceptions before caching lookup results.

Merge Risk: ⚪ Minimal · up to 94c5a

The change stops property lookup after a static-property builder throws and preserves the pending exception across affected lookup paths; reported verification passes, and no actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: stopping property lookup when a static-table PropertyCallback builder throws.
Description check ✅ Passed The description is detailed and directly related to the change. It explains the problem, fix, affected code paths, relationship to related issues, and verification results. It does not include the tem…
Full details: Description check

Explanation

The description is detailed and directly related to the change. It explains the problem, fix, affected code paths, relationship to related issues, and verification results. It does not include the template's Bugzilla URL, reviewer line, or path/function summary, but the core required information is present.

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


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.

I reviewed this PR and didn't find any bugs. Because it touches the core property-lookup hot paths (getPropertySlot, getNonIndexPropertySlot) and four megamorphic JIT slow paths, and the author notes local verification against a Bun build is still in progress, a human look would still be worthwhile.

Checked: the new checks are gated on hasStaticPropertyTable so objects without a static table take the same path as before; getPropertySlot uses exceptionForInspection() (no scope) consistently with its existing contract; the JITOperations checks sit after the structure reload and before initAsMiss, so they can't poison the megamorphic cache on the throw path.

Extended reasoning...

Overview

The PR adds pending-exception checks after a property-lookup miss on an object whose type has a static property table, in six locations: JSObject::getPropertySlot (JSObject.h), JSObject::getNonIndexPropertySlot (JSObjectInlines.h), JSObject::getOwnStaticPropertySlot (JSObject.cpp), and the four megamorphic slow paths getByIdMegamorphic / getByValMegamorphic / inByIdMegamorphic / inByValMegamorphic (JITOperations.cpp). This is a Bun-fork-specific fix: upstream, static-table PropertyCallback builders don't throw, but in this fork they can (Lookup.cpp:73 already handles this by returning false), and callers were treating that false as an ordinary miss.

Security risks

None identified. The change is purely additive — it inserts early-return-on-exception checks. It does not remove any check, change any cache-write condition on the success path, or alter object layout. The failure mode without this fix (stale megamorphic-cache miss) is a correctness bug, not a memory-safety issue.

Level of scrutiny

High. getPropertySlot and getNonIndexPropertySlot are ALWAYS_INLINE and sit on the hottest path in the engine; the megamorphic slow paths feed the megamorphic cache that JIT-compiled code reads directly. The change is small and defensive, and the hasStaticPropertyTable gate keeps the common path unchanged, but this is exactly the kind of code where a maintainer should confirm the placement (e.g., that exceptionForInspection() is the right primitive in getPropertySlot given it has no throw scope, and that no caller relies on the loop reaching the end-of-chain parseIndex fallback after a static-table miss).

Other factors

  • The PR description is thorough and the reasoning matches the code in setUpStaticFunctionSlot.
  • The author explicitly states verification against a local Bun build is still in progress and a Bun-side test PR is forthcoming.
  • No prior human review on the timeline.
  • The pattern mirrors the existing RETURN_IF_EXCEPTION after the overriding-getOwnPropertySlot branch in getNonIndexPropertySlot, which gives some confidence in correctness.

@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.

I reviewed this PR and didn't find any bugs. Because it adds exception checks to the core property-lookup hot paths (getPropertySlot, getNonIndexPropertySlot, getOwnStaticPropertySlot) and the four megamorphic JIT slow paths — code where correctness and performance both matter — a human look would still be worthwhile.

Checked that the new checks are gated on hasStaticPropertyTable so ordinary objects take the same path as before.
Checked that RETURN_IF_EXCEPTION(scope, { }) in the megamorphic helpers returns before the miss is recorded in MegamorphicCache and before the fallback getPropertySlot call.
Checked that getPropertySlot uses exceptionForInspection() (it has no ThrowScope) and only on a miss, matching the existing contract with its callers.
Checked that getNonIndexPropertySlot now observes the builder's scope on a hit as well, covering the validateExceptionChecks abort described in the PR.

Extended reasoning...

Overview

This PR adds pending-exception checks after the own-property step in six property-lookup paths, all conditioned on the object's type having a static property table:

  • JSObject::getPropertySlot (JSObject.h) — the general prototype-chain walk
  • JSObject::getNonIndexPropertySlot (JSObjectInlines.h) — the non-index variant
  • JSObject::getOwnStaticPropertySlot (JSObject.cpp) — the class-info table walk
  • getByIdMegamorphic, getByValMegamorphic, inByIdMegamorphic, inByValMegamorphic (JITOperations.cpp) — the megamorphic-cache slow paths

The motivation is a Bun-fork-specific behavior: static-table PropertyCallback builders can run JS and throw, and setUpStaticFunctionSlot reports that as a miss. Without these checks, the miss is treated as ordinary and the lookup continues with an exception pending — which trips validateExceptionChecks, and in the megamorphic paths poisons the cache for the base structure so later reads permanently return undefined.

Security risks

None identified. The change adds early-return guards; it does not introduce new data flows, allocation, or user-controlled input handling. The only observable effect is that a lookup stops sooner when an exception is already pending.

Level of scrutiny

High. getPropertySlot and getNonIndexPropertySlot are among the hottest functions in the engine, inlined into essentially every property access. The megamorphic helpers directly manage the MegamorphicCache. A subtle mistake here could break property-lookup semantics for all JS execution, and the added branch (even gated on hasStaticPropertyTable) sits in an ALWAYS_INLINE function replicated across many call sites. Someone familiar with JSC's exception-scope discipline and this fork's builder semantics should confirm the placement of each check and that the exceptionForInspection() use in getPropertySlot is the right choice for a scope-less function.

Other factors

The PR description is thorough, references the related #390 structure-reload change, and reports verification against a debug ASAN build with targeted Bun tests exercising both walk loops and all four megamorphic ops. The change follows the existing RETURN_IF_EXCEPTION pattern already used after the overriding-getOwnPropertySlot branch in the same loops. Still, the combination of hot-path placement, subtle exception-scope semantics, and megamorphic-cache interaction makes this a change that benefits from a maintainer's eyes rather than automated approval.

@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.

I reviewed this PR and didn't find any bugs. Because it modifies core property-lookup hot paths (getPropertySlot, getNonIndexPropertySlot, the four megamorphic JIT slow paths) and changes exception-scope handling in reifyAllStaticProperties, a human look would still be worthwhile.

What was reviewed:

  • The hasStaticPropertyTable guard on each new check — confirmed getOwnNonIndexPropertySlot only reaches a builder via getOwnStaticPropertySlot under that flag, so plain objects skip the check.
  • The four megamorphic helpers — each already owns a ThrowScope, and RETURN_IF_EXCEPTION(scope, { }) matches the existing return-value convention in each.
  • getPropertySlot's use of exceptionForInspection() without a scope — consistent with its existing scope-less design; callers (e.g. JSObject::get) already RETURN_IF_EXCEPTION after it.
Extended reasoning...

Overview

This PR adds pending-exception checks after the own-property step of property lookup on objects whose type carries a static property table, so that a PropertyCallback builder that throws stops the lookup instead of being treated as an ordinary miss. It touches six lookup loops: JSObject::getPropertySlot (JSObject.h), JSObject::getNonIndexPropertySlot (JSObjectInlines.h), JSObject::getOwnStaticPropertySlot and reifyAllStaticProperties (JSObject.cpp), and the four megamorphic get_by_id/get_by_val/in_by_id/in_by_val slow paths (JITOperations.cpp). It also swaps vm.exceptionForInspection() for a TopExceptionScope in reifyAllStaticProperties so each builder's scope is observed before the next runs.

Security risks

None identified. The change adds early-outs on pending exceptions and does not introduce new data flows, allocations, or user-controlled inputs. The megamorphic-cache fix prevents recording a stale miss, which is a correctness improvement rather than a new surface.

Level of scrutiny

High. getPropertySlot and getNonIndexPropertySlot are ALWAYS_INLINE and sit on the hottest property-access path in the engine; the megamorphic helpers feed the JIT's MegamorphicCache. The new branches are gated on TypeInfo::hasStaticPropertyTable(inlineTypeFlags()), which keeps ordinary objects on the old path, but the perf and exception-scope-validation implications of touching these loops warrant a maintainer's eye. The TopExceptionScope change in reifyAllStaticProperties also alters scope-simulation behaviour under validateExceptionChecks=1 and deserves confirmation from someone familiar with that machinery.

Other factors

The PR description reports verification against a debug ASAN build with validateExceptionChecks=1 and Bun test suites, and the reasoning for each guard is well-documented in comments. The change is internally consistent across all six sites and mirrors the existing RETURN_IF_EXCEPTION pattern already used after overridden getOwnPropertySlot calls. No prior human reviews or outstanding comments on the PR.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
94c5a2d5 autobuild-preview-pr-475-94c5a2d5 2026-08-28 14:29:24 UTC
3945a1fc autobuild-preview-pr-475-3945a1fc 2026-08-28 10:10:12 UTC
960b8356 autobuild-preview-pr-475-960b8356 2026-08-28 08:33:33 UTC
49296ecc autobuild-preview-pr-475-49296ecc 2026-08-28 02:27:11 UTC
1e46058f autobuild-preview-pr-475-1e46058f 2026-08-28 01:23:58 UTC
898102d9 autobuild-preview-pr-475-898102d9 2026-08-27 19:01:15 UTC
693e78de autobuild-preview-pr-475-693e78de 2026-08-26 22:18:51 UTC
d92bce58 autobuild-preview-pr-475-d92bce58 2026-08-26 05:51:49 UTC
2501e6c0 autobuild-preview-pr-475-2501e6c0 2026-08-25 22:57:50 UTC
7d1bf312 autobuild-preview-pr-475-7d1bf312 2026-08-25 10:39:55 UTC
594c6828 autobuild-preview-pr-475-594c6828 2026-08-24 13:07:24 UTC
cb224872 autobuild-preview-pr-475-cb224872 2026-08-23 08:29:20 UTC
3438defc autobuild-preview-pr-475-3438defc 2026-08-21 04:16:49 UTC
43f67cb4 autobuild-preview-pr-475-43f67cb4 2026-08-20 10:12:19 UTC

@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.

I reviewed this PR and didn't find any bugs. Because it touches core JSC property-lookup hot paths (getPropertySlot, getNonIndexPropertySlot, the four megamorphic JIT slow paths) and exception-scope semantics, and there's an open sequencing decision with #390, a human look would still be worthwhile.

What was reviewed:

  • The new exception checks are gated on hasStaticPropertyTable, so plain-object lookups take the same path as before.
  • RETURN_IF_EXCEPTION(scope, { }) in the megamorphic helpers returns an empty JSValue, matching the existing overriding-branch checks in each helper.
  • TopExceptionScope in reifyAllStaticProperties avoids simulating a throw to deleteProperty, consistent with the reasoning in the description and the prior #306 change.
Extended reasoning...

Overview

The PR adds pending-exception checks after the own-property step of property lookup on objects with static property tables, in six places: JSObject::getPropertySlot (JSObject.h), JSObject::getNonIndexPropertySlot (JSObjectInlines.h), JSObject::getOwnStaticPropertySlot and reifyAllStaticProperties (JSObject.cpp), and the four megamorphic get_by_id/get_by_val/in_by_id/in_by_val slow paths (JITOperations.cpp). It also swaps the raw vm.exceptionForInspection() in reifyAllStaticProperties for a TopExceptionScope so each builder's scope is observed before the next runs.

Security risks

No new attack surface. The change adds early-exit checks on paths that already run arbitrary JS via PropertyCallback builders; it does not introduce new JS entry points, new cache writes, or new memory access. The megamorphic-cache fix prevents recording a spurious miss, which is a correctness improvement rather than a risk.

Level of scrutiny

High. These are among the hottest code paths in the engine — ALWAYS_INLINE prototype-walk loops and JIT slow-path helpers that feed the megamorphic cache. The diff is small and each hunk is a guarded early return, but the surrounding code is subtle (structure reloads after builder-driven transitions, ThrowScope vs TopExceptionScope validator semantics), and the PR description explicitly asks for a sequencing decision relative to #390. That combination should get a human maintainer's sign-off.

Other factors

The description is thorough with concrete verification against preview builds on Linux ASAN and Windows debug, and a companion Bun test PR (oven-sh/bun#39703). The bug-hunting system found nothing. I checked that the new hasStaticPropertyTable gate keeps the added branch off the common plain-object path, that the { } return in the megamorphic helpers matches what those functions already return on exception in the overriding branch, and that the getNonIndexPropertySlot change also runs on a hit (covering the succeeding-builder validator case the description mentions). Nothing looked wrong, but given the criticality of these paths and the open #390 interaction, deferring to a human is the right call.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 21, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
BUN_JSC_validateExceptionChecks=1. The WebKit change checks for the
exception after the own-property step on static-table objects and after
each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw: through Bun
and through a function that inherits from Bun, behind a Proxy prototype
and under BUN_JSC_validateExceptionChecks=1; with a builder that reifies
another property of Bun first; and from megamorphic get_by_id, get_by_val,
in_by_id and in_by_val sites, where the second access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 21, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 3438def to cb22487 Compare August 23, 2026 07:45

@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.

I reviewed this PR and didn't find any bugs. Because it touches the hottest property-lookup paths in JSC (getPropertySlot, getNonIndexPropertySlot, four megamorphic JIT slow paths) and exception-scope semantics, a human look would still be worthwhile.

What was reviewed:

  • The hasStaticPropertyTable guard is a cheap inline-flag test, so objects without static tables take the same path as before.
  • RETURN_IF_EXCEPTION placement in the megamorphic helpers is before the cache write, so a throwing builder no longer poisons the MegamorphicCache.
  • TopExceptionScope in reifyAllStaticProperties matches the JSBoundFunction::nameSlow pattern and avoids simulating a throw to deleteProperty (the #306 concern).
  • getPropertySlot uses exceptionForInspection() (no scope of its own) only on the miss path, consistent with how its callers already check afterwards.
Extended reasoning...

Overview

The PR adds exception checks after the own-property step in six property-lookup call sites when the object's type carries a static property table, so that a PropertyCallback builder that throws stops the lookup instead of continuing up the prototype chain or being recorded as a miss in the megamorphic cache. It also swaps vm.exceptionForInspection() for a TopExceptionScope in reifyAllStaticProperties so each builder's scope is observed before the next one runs. Files touched: JSObject.h, JSObjectInlines.h, JSObject.cpp, JITOperations.cpp.

Security risks

None identified. The change tightens exception propagation; it does not relax any check, expose data, or alter permissions/auth. The added branches are guarded by an inline type-flag bit and only fire on objects with static tables.

Level of scrutiny

High. JSObject::getPropertySlot and getNonIndexPropertySlot are ALWAYS_INLINE and sit on essentially every property access in the engine; the four *Megamorphic helpers are JIT slow paths whose caching behavior affects release-build correctness. Exception-scope discipline (ThrowScope vs TopExceptionScope vs exceptionForInspection) is subtle and interacts with validateExceptionChecks. This is core-engine, performance-sensitive code where a maintainer familiar with JSC's exception validator and the history around #306/#390 should sign off.

Other factors

The PR description is thorough and documents extensive verification against Bun debug/ASAN builds on Linux and Windows, plus targeted tests in oven-sh/bun#39703. The added checks follow existing patterns already used in the same functions (e.g. the RETURN_IF_EXCEPTION after the overriding-getOwnPropertySlot branch in getNonIndexPropertySlot). The interaction with #390 (whether to land it first, rebase over it, or close it as superseded) is a coordination decision the author explicitly left to a maintainer. No prior human or bot review comments to address.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 23, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from cb22487 to 594c682 Compare August 24, 2026 11:18

@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.

I reviewed this PR and didn't find any bugs. Because it modifies the core property-lookup hot paths (getPropertySlot, getNonIndexPropertySlot, getOwnStaticPropertySlot) and the four megamorphic JIT slow paths, and involves subtle exception-scope semantics, a human look would still be worthwhile — including the open question of whether #390 should land first or be superseded.

What was reviewed:

  • Each new check is gated on hasStaticPropertyTable, so objects without a static table follow the same code path as before.
  • The megamorphic helpers bail with RETURN_IF_EXCEPTION before the miss can be recorded in the cache, and after the structure reload so no stale state is used.
  • getNonIndexPropertySlot now checks the scope on both hit and miss, matching the overriding-getOwnPropertySlot branch and covering the succeeding-builder validator case.
  • reifyAllStaticProperties uses TopExceptionScope (not ThrowScope) so deleteProperty callers that don't check afterwards keep the same behaviour as the prior exceptionForInspection() early-return.
Extended reasoning...

Overview

The PR adds pending-exception checks after the own-property step in every prototype-walk loop that can reach a static-table PropertyCallback builder: JSObject::getPropertySlot (JSObject.h), JSObject::getNonIndexPropertySlot (JSObjectInlines.h), JSObject::getOwnStaticPropertySlot (JSObject.cpp), and the four megamorphic get_by_id / get_by_val / in_by_id / in_by_val slow paths in JITOperations.cpp. It also swaps the raw vm.exceptionForInspection() check in reifyAllStaticProperties for a TopExceptionScope so the validator sees each builder's scope as observed before the next one runs.

Security risks

None identified. The change adds early-exit paths on pending exceptions; it does not remove any check, does not change caching behaviour when no exception is pending, and does not touch auth, permissions, or memory layout. The megamorphic-cache poisoning it fixes is a correctness issue (stale undefined returned) rather than an exploitable disclosure.

Level of scrutiny

High. getPropertySlot and getNonIndexPropertySlot are on the critical path for essentially every non-indexed property access in the engine, and the megamorphic helpers back the JIT's property-access ICs. Exception-scope discipline in JSC (ThrowScope vs CatchScope vs TopExceptionScope, exceptionForInspection() vs RETURN_IF_EXCEPTION) is subtle enough that a reviewer familiar with the validator's rules should confirm the choices — especially the deliberate use of exceptionForInspection() in getPropertySlot (which owns no scope) versus RETURN_IF_EXCEPTION(scope, ...) in the loops that do. The new checks are guarded by TypeInfo::hasStaticPropertyTable(object->inlineTypeFlags()), so the common no-static-table fast path is a single flag test with no additional VM access; that should keep the hot-path cost negligible, but it's worth a human eye given how hot these functions are.

Other factors

The description is unusually thorough, includes cross-platform (Linux ASAN, Windows debug) verification against preview builds, and references companion Bun tests (oven-sh/bun#39703) covering each failure mode. The author also flags an open coordination question with #390 — whether to land that first and rebase, or close it as superseded — which is a decision a maintainer should make. No prior human review is on the thread; coderabbit was rate-limited and did not review.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 24, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 594c682 to 7d1bf31 Compare August 25, 2026 09:25

@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.

I reviewed this PR and the automated bug-hunt found no issues. Because it touches the hot getPropertySlot/getNonIndexPropertySlot walks and four megamorphic JIT slow paths with subtle exception-scope semantics, a human look is still worthwhile.

What was reviewed:

  • The new checks are gated on hasStaticPropertyTable, so plain-object lookups skip the exception load.
  • getNonIndexPropertySlot checks on hit as well (matches the overriding branch) for the succeeding-builder validator case; getPropertySlot uses exceptionForInspection() on miss only, since it has no scope.
  • The megamorphic paths bail before the miss reaches the MegamorphicCache, and reifyAllStaticProperties uses TopExceptionScope rather than ThrowScope so deleteProperty callers still don't need to check.
Extended reasoning...

Overview

This PR changes exception handling in JSC's property-lookup fast paths so that when a Bun static-table PropertyCallback builder throws (which setUpStaticFunctionSlot reports as a miss), the caller stops the lookup instead of continuing up the prototype chain or recording a spurious miss. It touches JSObject::getPropertySlot (JSObject.h), JSObject::getNonIndexPropertySlot (JSObjectInlines.h), getOwnStaticPropertySlot and reifyAllStaticProperties (JSObject.cpp), and the four megamorphic get_by_id/get_by_val/in_by_id/in_by_val helpers in JITOperations.cpp.

Security risks

None identified. The change adds early-outs on pending exceptions and does not introduce new data flow, allocation, or caching. The megamorphic-cache change strictly removes a case where a stale miss was recorded, which is a correctness fix rather than a new attack surface.

Level of scrutiny

High. getPropertySlot is ALWAYS_INLINE and explicitly documented as very hot in variable lookup; adding a branch there has performance implications even though it is gated on hasStaticPropertyTable and marked [[unlikely]]. The distinction between ThrowScope, TopExceptionScope, and raw vm.exceptionForInspection() is subtle and load-bearing (the description references #306 for why reifyAllStaticProperties must not use a ThrowScope). The four JITOperations hunks are mechanically identical but sit on the boundary between runtime state and the JIT's megamorphic cache. This is core-VM logic where a maintainer familiar with the fork's builder semantics and the exception validator should sign off.

Other factors

The description is thorough and cites concrete validator failures, a release-mode reproduction of the megamorphic-cache poisoning, and cross-platform test runs against preview builds. It also notes an interaction with #390 that a maintainer should decide on (rebase vs. supersede). No prior human or bot review is on the thread yet.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 7d1bf31 to 2501e6c Compare August 25, 2026 21:59

@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.

I reviewed this PR and didn't find any bugs. Because it touches the core property-lookup hot paths (getPropertySlot, getNonIndexPropertySlot, reifyAllStaticProperties) and the four megamorphic JIT slow paths, and involves subtle exception-scope semantics plus an open coordination point with #390, a human look would still be worthwhile.

What was reviewed

  • The hasStaticPropertyTable guard on each new check — objects without a static table take the unchanged fast path, so no new branch on the common case.
  • RETURN_IF_EXCEPTION placement in the megamorphic helpers — it fires before the miss is written to MegamorphicCache and before hasProperty is consumed, so a throwing builder can't poison the cache or short-circuit as a hit.
  • getNonIndexPropertySlot checking on hit as well as miss, which the PR body says is needed for the succeeding-builder validator case; getPropertySlot only checks on miss (no scope of its own), which matches the description.
  • DECLARE_TOP_EXCEPTION_SCOPE in reifyAllStaticProperties — matches the JSBoundFunction::nameSlow pattern and preserves the #306 property that deleteProperty callers don't get a simulated throw.
Extended reasoning...

Overview

The PR adds pending-exception checks after the own-property step in six property-lookup call sites (JSObject::getPropertySlot, JSObject::getNonIndexPropertySlot, JSObject::getOwnStaticPropertySlot, and the four megamorphic get_by_id/get_by_val/in_by_id/in_by_val slow paths in JITOperations.cpp), and switches reifyAllStaticProperties from a raw vm.exceptionForInspection() check to a TopExceptionScope so each builder's scope is observed before the next one runs. All checks are gated on TypeInfo::hasStaticPropertyTable(inlineTypeFlags()), which is the only condition under which getOwnNonIndexPropertySlot can invoke a PropertyCallback builder, so plain objects and arrays are unaffected.

Security risks

None identified. The change tightens exception handling — it stops a lookup earlier when an exception is already pending, rather than continuing into user-observable operations (Proxy traps, host getOwnPropertySlot) with a pending exception. It also prevents a poisoned-cache correctness bug where a throwing builder would cause later megamorphic reads to permanently return undefined. No new attack surface.

Level of scrutiny

High. getPropertySlot and getNonIndexPropertySlot are among the hottest inline paths in the VM, and the megamorphic helpers back the JIT's polymorphic property-access slow paths. Any mistake here affects semantics or performance of essentially all JS property reads. The change is small and each hunk follows an existing pattern (the same RETURN_IF_EXCEPTION the overriding-getOwnPropertySlot branch already uses), but the interaction between ThrowScope, CatchScope, TopExceptionScope, and exceptionForInspection() under validateExceptionChecks=1 is subtle enough that a maintainer familiar with the fork's exception-scope conventions and with #306/#390 should confirm the choices — particularly using exceptionForInspection() (rather than a scope) in getPropertySlot and getOwnStaticPropertySlot, and the deliberate asymmetry where getNonIndexPropertySlot checks on hit but getPropertySlot only checks on miss.

Other factors

The description is unusually thorough and cites concrete verification against Bun debug/ASAN builds on Linux and Windows, under the exception validator, with a linked Bun-side test PR. All 42 preview-build lanes are green. There is an explicit open question about ordering relative to #390 (rebase vs. supersede) that a human should decide. No prior reviewer comments to address. Given the criticality of the touched paths and the coordination point, deferring to human review is the right call even though no defects were found.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 25, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 2501e6c to d92bce5 Compare August 26, 2026 04:51

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 26, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from d92bce5 to 693e78d Compare August 26, 2026 21:18

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 26, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 693e78d to 898102d Compare August 27, 2026 09:53

@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.

Code review found no issues

No high-confidence issues detected in this change.

@robobun

robobun commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Preview builds are currently blocked for every PR in this repository: the Build / Windows (windows-11-arm, Debug, arm64, bun-webkit-windows-arm64-debug, ARM64) lane fails after about 7 seconds in its Install LLVM and Ninja (ARM64) step (the Scoop / LLVM download step in build-reusable.yml), before checkout. Because of fail-fast the Build / release job is skipped, so no autobuild-preview-* release gets published.

Seen on this PR's run 33060595263 (attempt 1 and a re-run of the failed job, both 7 seconds) and on runs for unrelated branches started since about 09:49Z today: 33060284775, 33060269980, 33060589499 and 33060629908 all fail the same lane at the same step. The other 42 lanes of this PR's run are green and their artifacts are on the run, so once that step works again a rerun-failed-jobs on 33060595263 should only need to rebuild the Windows ARM64 lane. I cannot read the step log from my environment (the log storage host is not reachable), so I do not know whether it is the Scoop install, the LLVM 21.1.8 woa64 download, or the 7-Zip path that fails on the current windows-11-arm runner image.

@robobun

robobun commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up on the preview blocker: the windows-11-arm failure is deterministic for every run that uses the workflow on main. Its Install LLVM and Ninja (ARM64) step relies on the Scoop installer (irm get.scoop.sh | iex), which stopped installing anything when piped into the runner's pwsh after ScoopInstaller/Install#136, so scoop install ninja fails within seconds. No preview built from main's workflow has passed that lane since about 09:48Z today (run 33060595263 for this PR: 6 attempts, 42 of 43 lanes green each time). Two fixes are open: #523 (drop Scoop from the arm64 job) and #524 (run the Scoop installer as a script file); the runs on their branches pass the lane.

Once either lands on main, a workflow_dispatch of the preview build for PR 475 (it takes the workflow from main) will publish autobuild-preview-pr-475-898102d9 without a new commit here, and oven-sh/bun#39703 can be re-pinned and pushed.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 27, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 898102d to 1e46058 Compare August 28, 2026 00:35

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 1e46058 to 49296ec Compare August 28, 2026 01:57

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 49296ec to 960b835 Compare August 28, 2026 08:03

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 960b835 to 3945a1f Compare August 28, 2026 09:19

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
…throws

In this fork a static-table PropertyCallback builder can run JS and
throw. setUpStaticFunctionSlot then reports the slot as not found so
that the caller observes the exception. Every caller treated that miss
like an ordinary miss and kept going with the exception pending:

- JSObject::getPropertySlot and JSObject::getNonIndexPropertySlot
  walked on to the prototype. A prototype that overrides
  getOwnPropertySlot (a Proxy, a host object) then ran with the
  exception pending. Exception scope verification reports this as an
  unchecked exception. getPropertySlot also read the prototype off the
  Structure* it had loaded before the builder ran, which asserts in
  Structure::storedPrototype when the builder transitioned the object
  before it threw.
- The megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow
  paths walked to the end of the chain and recorded the miss in the
  MegamorphicCache for the base object's structure. The builder stores
  nothing when it throws, so the structure does not change, and every
  later megamorphic access of that property on that object returned
  undefined (false for `in`) without running the builder again.
- JSObject::getOwnStaticPropertySlot went on to the parent class
  tables.

Check for a pending exception after the own-property step on an object
that has a static property table, which is the only case in which
getOwnNonIndexPropertySlot can run a builder, and stop the lookup
there. Objects without a static table take the same path as before.
In the loops that own a ThrowScope the check is the same
RETURN_IF_EXCEPTION they already make after an overridden
getOwnPropertySlot, and it is made on a hit as well: under exception
scope verification, getNonIndexPropertySlot's own scope otherwise
reports the scope of a builder that succeeded as unchecked.
getPropertySlot has no scope of its own and checks with
exceptionForInspection() on a miss, leaving the check to its caller as
the plain-prototype case does today.

reifyAllStaticProperties runs builders back to back and has the same
verification problem between two of them: the first builder's scope is
reported as unchecked when the second one declares its scope, so
spreading or Object.assign'ing such an object aborts under
validateExceptionChecks even though nothing threw. Give it a
TopExceptionScope (not a ThrowScope, which would simulate a throw to
callers such as JSObject::deleteProperty that do not check, see #306)
and check it after each builder. A real exception still stays pending
for the caller as before.
@robobun
robobun force-pushed the farm/d6221457/stop-lookup-after-throwing-static-reify branch from 3945a1f to 94c5a2d Compare August 28, 2026 13:57
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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.

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…a lazy property whose builder threw

A read of an unreified static-table property whose PropertyCallback
builder throws is reported as a miss by setUpStaticFunctionSlot. The
prototype walk loops in JSObject::getPropertySlot and
JSObject::getNonIndexPropertySlot went on to the prototype with the
exception pending (and getPropertySlot used the structure from before the
builder ran, which asserts when the builder transitioned the object), and
the megamorphic get_by_id, get_by_val, in_by_id and in_by_val slow paths
recorded the miss for the object's structure, so every later megamorphic
access of that property returned undefined (false for `in`) without
running the builder again. reifyAllStaticProperties ran builders back to
back without checking between them, which made spreading Bun abort under
the exception check validator of debug builds. The WebKit change checks
for the exception after the own-property step on static-table objects and
after each builder in reifyAllStaticProperties.

The tests read Bun.sql with the sql builder made to throw. One reads it
through Bun and one through a function that inherits from Bun, both
behind a Proxy prototype and with the validator enabled. One makes the
builder reify another property of Bun first. One reads from megamorphic
get_by_id, get_by_val, in_by_id and in_by_val sites, where the second
access has to throw again.
The process.env pre-read in the test above them worked around the
structure assertion on Windows and is removed, and the file leaves
test/no-validate-exceptions.txt now that spreading Bun is clean under the
validator.
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