Skip to content

JSObject::getPropertySlot: reload the structure before the prototype step - #390

Open
robobun wants to merge 1 commit into
mainfrom
farm/f76ebd86/getpropertyslot-stale-structure
Open

JSObject::getPropertySlot: reload the structure before the prototype step#390
robobun wants to merge 1 commit into
mainfrom
farm/f76ebd86/getpropertyslot-stale-structure

Conversation

@robobun

@robobun robobun commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

getOwnNonIndexPropertySlot can reify a static-table lazy property, which transitions the object, and still return false when the PropertyCallback builder throws (setUpStaticFunctionSlot reports the slot as not found so the caller observes the exception). The inlined prototype-chain loop in JSObject.h then called storedPrototype(object) on the structure it read before the call, tripping ASSERT(object->structure() == this) at StructureInlinesLight.h:56 on asserts builds. Release builds read the prototype off the stale structure instead.

The megamorphic slow paths in JITOperations.cpp already reload the structure after getOwnNonIndexPropertySlot with the comment "Reload it again since static-class-table can cause transition", and getNonIndexPropertySlot uses getPrototypeDirect() which re-reads it. This applies the same reload to the one caller that reused the stale pointer.

Found by Fuzzilli in Bun (fingerprint StructureInlinesLight.h(56), flaky under REPRL). Deterministic repro in Bun: first read of a lazy static-table property on the Bun object whose builder evaluates JS that first-touches another lazy property on the same object and then throws:

let phase = 0;
globalThis.Error = new Proxy(function () {}, {
  get(target, key, receiver) {
    if (key === "prototype" && phase === 0) {
      phase = 1;
      Bun.semver;   // reify another lazy prop -> transition Bun
      throw "boom"; // leave the builder with a pending exception
    }
    return Reflect.get(target, key, receiver);
  },
});
try { Bun.sql; } catch (e) {}

The class SQLError extends Error statement inside the bun:sql internal module hits the proxy trap while Bun.sql is being reified.

There is a second route to the same line, seen while tracing other fuzzer samples: a static-table lookup performed while an unrelated exception is already pending. reifyStaticProperty adds the property (transition), then setUpStaticFunctionSlot sees vm.exceptionForInspection() and reports a miss, so the loop again reaches storedPrototype() with the structure it read before the call. Bun hit that one through a debug-only exception report inside a builder reading process._fatalException; the Bun side is removing that report, and the reload here covers both routes since they converge on the same prototype step.

@coderabbitai

coderabbitai Bot commented Aug 5, 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: 9bfdd150-5c9b-4e08-a8fc-49d2d922d01b

📥 Commits

Reviewing files that changed from the base of the PR and between eeab040 and 5b4dfb3.

📒 Files selected for processing (1)
  • Source/JavaScriptCore/runtime/JSObject.h

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


Walkthrough

JSObject::getPropertySlot reloads the object structure after own-property lookup. Prototype traversal then uses the updated structure after lazy static-property reification.

Changes

Property lookup

Layer / File(s) Summary
Reload structure before prototype traversal
Source/JavaScriptCore/runtime/JSObject.h
JSObject::getPropertySlot reloads the structure after own-property lookup before reading the stored prototype.

Merge Risk: ⚪ Minimal · up to 5b4df

This localized fix reloads object structure after lazy-property reification, avoiding stale-structure access in the prototype lookup path; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the bug and fix in detail but omits the required Bugzilla link, review line, and formatted changed-file summary. Add the bug title and Bugzilla URL, the required Reviewed by line, and the template-formatted file and function change summary.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change in JSObject::getPropertySlot.
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.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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 didn't find any bugs — the reload matches the established pattern in JITOperations.cpp and fixes the stale-structure assert. Deferring to a human because this touches the ALWAYS_INLINE prototype-chain loop that runs on essentially every property lookup, so the extra structureID().decode() per hop is worth a second pair of eyes on perf.

What was reviewed:

  • Confirmed 4 identical "reload after getOwnNonIndexPropertySlot" sites in JITOperations.cpp (lines 389/730/920/3777) — this is the same fix.
  • Confirmed getNonIndexPropertySlot avoids the issue by re-reading the structure at loop top and using getPrototypeDirect().
  • Verified the assert at StructureInlinesLight.h:56 is exactly object->structure() == this, matching the described failure.
Extended reasoning...

Overview

Single-line fix to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h: after getOwnNonIndexPropertySlot returns false, reload structure = object->structureID().decode() before calling structure->storedPrototype(object). The rest of the diff is a comment explaining why.

Correctness

The bug is real and well-explained: getOwnNonIndexPropertySlotgetOwnStaticPropertySlot can reify a lazy static-table property, transitioning the object to a new Structure, and still return false if the PropertyCallback throws. The old code then passed the stale structure to storedPrototype(object), which asserts object->structure() == this (StructureInlinesLight.h:56). I verified that JITOperations.cpp already applies exactly this reload in four places with the comment "Reload it again since static-class-table can cause transition", and that the sibling getNonIndexPropertySlot in JSObjectInlines.h re-reads the structure each iteration and uses getPrototypeDirect(), so it never held a stale pointer. The fix is the minimal, idiomatic version of the same defense.

Security risks

None introduced. If anything this closes a stale-pointer read in release builds (reading the prototype off a superseded Structure). No new attacker-controlled inputs, no new allocations.

Level of scrutiny

High. getPropertySlot is one of the hottest paths in JSC — it is ALWAYS_INLINE and executes for every non-cached property lookup that walks the prototype chain. Adding a second structureID().decode() per loop iteration is cheap (a load + mask, same as the one at the top of the loop), and the compiler may even be able to keep it in a register across the non-throwing common case, but any change to this loop deserves a human sign-off on the perf side and a decision on whether to upstream it to WebKit.

Other factors

No prior human reviews on the PR; only a coderabbit rate-limit notice. The bug hunting system found no issues. The PR description includes a deterministic repro and a Fuzzilli fingerprint. The change is already the current HEAD of the checkout (c3a156e), so it has presumably been building.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
5b4dfb37 autobuild-preview-pr-390-5b4dfb37 2026-08-18 04:57:30 UTC
d0e68f3d autobuild-preview-pr-390-d0e68f3d 2026-08-17 00:37:37 UTC
9add1ce5 autobuild-preview-pr-390-9add1ce5 2026-08-16 19:44:27 UTC
686a7577 autobuild-preview-pr-390-686a7577 2026-08-14 19:48:34 UTC
8cb37abf autobuild-preview-pr-390-8cb37abf 2026-08-14 15:09:02 UTC
37d85e8f autobuild-preview-pr-390-37d85e8f 2026-08-14 08:56:40 UTC
0c51423a autobuild-preview-pr-390-0c51423a 2026-08-10 09:47:16 UTC
1394aa19 autobuild-preview-pr-390-1394aa19 2026-08-10 00:38:56 UTC
01c983d8 autobuild-preview-pr-390-01c983d8 2026-08-08 01:11:55 UTC
c3a156ea autobuild-preview-pr-390-c3a156ea 2026-08-06 00:04:44 UTC

@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from c3a156e to 01c983d Compare August 8, 2026 00:31

@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. The reload matches the established pattern in JITOperations.cpp and getNonIndexPropertySlot. Because getPropertySlot is the hottest property-lookup path in JSC and this adds a decode per prototype step, a quick human sign-off on the perf/correctness trade-off would still be worthwhile.

What was reviewed:

  • Confirmed the 4 identical structure = object->structure() reloads in JITOperations.cpp:389/730/920/3777 that this mirrors.
  • Confirmed getNonIndexPropertySlot already re-reads via getPrototypeDirect(), so this brings parity rather than diverging.
  • Checked that storedPrototype(object) is the only downstream use of structure after the call — the reload covers it fully.
Extended reasoning...

Overview

Single-line functional change (plus a 3-line comment) in JSObject::getPropertySlot (JSObject.h): after getOwnNonIndexPropertySlot returns false, re-read structure = object->structureID().decode() before calling structure->storedPrototype(object). This fixes a stale-structure read when a static-table lazy property's PropertyCallback reifies (transitioning the object) and then throws, causing getOwnNonIndexPropertySlot to return false with the object already transitioned. Fuzzilli found the resulting ASSERT(object->structure() == this) at StructureInlinesLight.h:56.

Security risks

None introduced. The change replaces a potentially-stale pointer read with a fresh one, which is strictly safer. No new user-controlled inputs, no allocation, no GC interaction beyond what already existed.

Level of scrutiny

High. JSObject::getPropertySlot is ALWAYS_INLINE and per the in-file comment is "very hot in variable lookup" — it's on the critical path for essentially every non-cached property access in the interpreter and JIT slow paths. Even a one-line change here can affect engine-wide performance. The extra structureID().decode() per prototype-chain step is cheap (one load + mask/lookup) and mirrors what the sibling getNonIndexPropertySlot already pays via getPrototypeDirect(), so it's very likely fine — but that's a judgment a human maintainer should make explicitly, not an automated reviewer.

Other factors

  • The pattern is well-precedented: JITOperations.cpp has four identical post-getOwnNonIndexPropertySlot reloads with the comment "Reload it again since static-class-table can cause transition", and getNonIndexPropertySlot re-reads the structure via getPrototypeDirect(). This PR closes the one remaining caller that reused the pre-call pointer.
  • No test added in-tree; the repro is Bun-specific (relies on the Bun object's lazy static properties). That's reasonable for this fork but worth a human ack.
  • No prior human or bot reviews on the thread other than a rate-limited CodeRabbit notice and a preview-build comment.

Given the criticality of the code path, deferring to a human for final sign-off rather than auto-approving.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 8, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 01c983d to 1394aa1 Compare August 10, 2026 00:01

@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. The one-line reload matches the established pattern in JITOperations.cpp (four identical structure = object->structure() reloads after getOwnNonIndexPropertySlot) and getNonIndexPropertySlot (which re-reads via getPrototypeDirect()), and it resolves the ASSERT(object->structure() == this) at StructureInlinesLight.h:56. Because JSObject::getPropertySlot is the ALWAYS_INLINE hot path for property lookup in core JSC, a human sign-off is still worthwhile.

Checked that the reload uses the same object->structureID().decode() idiom as the load at the top of the loop, so no new failure mode is introduced.
Confirmed getNonIndexPropertySlot (the other branch of this loop) already re-reads the structure via getPrototypeDirect(), so it does not need the same fix.
Verified the checkNullStructure guard is not needed on the reload — the object had a valid structure two lines earlier and transitions never install a null one.

Extended reasoning...

Overview

This PR adds a single line to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h: after getOwnNonIndexPropertySlot returns false, it reloads structure = object->structureID().decode() before calling structure->storedPrototype(object). The rationale is that getOwnNonIndexPropertySlotgetOwnStaticPropertySlot can reify a lazy static-table property, transitioning the object to a new Structure, and still return false if the PropertyCallback builder throws. The old code then passed the stale Structure pointer to storedPrototype(object), which asserts object->structure() == this (StructureInlinesLight.h:56).

Correctness

The fix is clearly correct and follows established precedent exactly. Four call sites in JITOperations.cpp (lines ~389, 730, 920, 3777) perform the identical reload with the comment "Reload it again since static-class-table can cause transition". The sibling getNonIndexPropertySlot in JSObjectInlines.h avoids the bug by calling object->getPrototypeDirect(), which reads structure() fresh. This PR brings the last remaining caller in line. The reload is strictly more defensive than the prior code — there is no scenario where re-reading the object's current structure produces a worse result than using a possibly-stale one. I also confirmed the checkNullStructure template guard need not be repeated: the object had a non-null structure immediately prior and structure transitions never install null.

Security risks

None introduced by this change. If anything, it removes a latent hazard: in release builds the old code read the prototype off a stale Structure, which for mono-proto structures would still return the same stored prototype (structure transitions preserve it), and for poly-proto reads object->getDirect(knownPolyProtoOffset) which doesn't depend on the stale pointer — so the practical release-build impact was likely benign, but the assert crash in debug/asserts builds was real and reproducible.

Level of scrutiny

High. JSObject::getPropertySlot is an ALWAYS_INLINE function on the hottest property-lookup path in JavaScriptCore — the header comment itself notes it is "very hot in variable lookup". Any change here affects every non-overridden property access in the engine and is inlined into many translation units. Even a one-line change on this path warrants a human maintainer's eyes, particularly regarding the (likely negligible) perf cost of one extra structureID().decode() per prototype-chain hop.

Other factors

The PR description is thorough, includes a deterministic Bun repro, and cites the exact assert location and the fuzzer fingerprint. The change is already merged to main (commit 1394aa1). No test is added in-tree, but the repro is Bun-specific (relies on the Bun global's lazy properties). No outstanding reviewer comments. Given the criticality of the code path, I'm deferring rather than auto-approving, though I have high confidence the change is correct.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 10, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 1394aa1 to 0c51423 Compare August 10, 2026 09:11

@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 — the reload matches the established pattern in JITOperations.cpp and fixes the stale-structure assert. Because it adds a structure re-decode on every iteration of the hottest property-lookup loop in JSC, a human sign-off on the perf/correctness tradeoff is still worthwhile.

Checked that storedPrototype(object) at StructureInlinesLight.h:56 asserts object->structure() == this, which the stale pointer would violate.
Confirmed JITOperations.cpp reloads the structure after getOwnNonIndexPropertySlot in four places for the same reason.
Verified getNonIndexPropertySlot (the other prototype-walk path) already re-reads via getPrototypeDirect(), so this was the one remaining stale caller.

Extended reasoning...

Overview

This PR adds a single line (plus a comment) to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h. After getOwnNonIndexPropertySlot returns false, it reloads structure = object->structureID().decode() before calling structure->storedPrototype(object) to advance the prototype-chain walk. The rationale is that reifying a static-table lazy property inside getOwnNonIndexPropertySlot can transition the object and still return false (when the PropertyCallback builder throws), leaving the local structure pointer stale. The stale pointer trips ASSERT(object->structure() == this) at StructureInlinesLight.h:56 in debug builds and reads the prototype off the wrong structure in release builds.

Security risks

None introduced by this change. The fix is strictly defensive — it re-reads fresh state instead of trusting a cached pointer across a call that can mutate the object. If anything, it closes a potential type-confusion / stale-read hazard in release builds where the prototype was being read off an outdated structure.

Level of scrutiny

High. JSObject::getPropertySlot is one of the hottest functions in JavaScriptCore — the header itself notes it is ALWAYS_INLINE because it "makes a big difference since this is function very hot in variable lookup." Every non-overridden property access on every object flows through this loop. Adding a structureID().decode() on each prototype-chain step is almost certainly cheap (and matches what JITOperations.cpp already does four times over), but any change on this path deserves a JSC maintainer's eye for perf implications and to confirm there is no cheaper placement (e.g., only reloading when hasStaticPropertyTable was true).

Other factors

  • The fix follows clear precedent: JITOperations.cpp reloads the structure after getOwnNonIndexPropertySlot in four separate places with the comment "Reload it again since static-class-table can cause transition", and getNonIndexPropertySlot already avoids the issue via getPrototypeDirect().
  • Found by Fuzzilli with a deterministic repro provided in the description, so the failure mode is well-understood.
  • No test added in this repo (the repro depends on Bun-specific lazy properties on the Bun object).
  • The change is semantically correct and minimal; my hesitation to auto-approve is purely about the criticality of the code path, not about doubts in the fix itself.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 10, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 0c51423 to 37d85e8 Compare August 14, 2026 08:27

@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. The one-line reload mirrors the established pattern in JITOperations.cpp and is clearly correct, but since it sits in JSObject::getPropertySlot — the ALWAYS_INLINE hot path for every property lookup in the VM — a human sign-off on the extra structureID().decode() per prototype step would still be worthwhile.

What was reviewed:

  • Confirmed the reload matches the four identical "Reload it again since static-class-table can cause transition" sites in JITOperations.cpp and that getNonIndexPropertySlot already avoids the stale pointer via getPrototypeDirect().
  • Checked that storedPrototype(object) on the reloaded structure is safe (the assert it fixes is exactly object->structure() == this).
  • Verified the added decode is the same cheap load already done at the top of each loop iteration, so no new failure modes are introduced.
Extended reasoning...

Overview

This PR adds a single line (plus a three-line comment) to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h. After getOwnNonIndexPropertySlot returns false, the loop now reloads structure = object->structureID().decode() before calling structure->storedPrototype(object). This fixes a Fuzzilli-discovered assertion at StructureInlinesLight.h:56 where reifying a lazy static property transitions the object but the builder throws, leaving the loop holding a stale Structure* when it steps to the prototype.

Security risks

None introduced by the change itself — it is strictly a defensive re-read of state the object already owns. The bug it fixes is arguably the security-relevant part: in release builds the old code read the prototype off a stale structure, which is the kind of type-confusion primitive fuzzers look for. The fix closes that, and the reload cannot make things worse than re-reading the current structure.

Level of scrutiny

High. getPropertySlot is ALWAYS_INLINE and on the hottest path in the engine — every non-indexed property access that walks the prototype chain goes through it. Even a one-line change here deserves a human look for perf implications (an extra structureID().decode() per prototype hop) and to confirm there is no subtler interaction with the checkNullStructure / debugLLIntGetById template variants. The correctness argument is strong and the pattern is copied verbatim from four sites in JITOperations.cpp, so the review should be quick, but it should be a human's call.

Other factors

  • The PR description is thorough, with a deterministic repro and an explanation of both routes to the assert.
  • getNonIndexPropertySlot (the sibling loop this delegates to when overridesGetOwnPropertySlot is set) already avoids the bug by calling object->getPrototypeDirect(), which re-reads the structure — so this change brings the two loops into agreement rather than introducing a novel approach.
  • No test is added; the repro depends on Bun-specific lazy builders (Bun.sql / Bun.semver), so a JSC-only regression test would be hard to write.
  • Preview builds succeeded across four commits, indicating it compiles cleanly on all CI targets.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 14, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 37d85e8 to 8cb37ab Compare August 14, 2026 14:38

@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. The reload matches the four identical structure = object->structure() reloads after getOwnNonIndexPropertySlot in JITOperations.cpp and resolves the object->structure() == this assert in Structure::storedPrototype. Since getPropertySlot is the ALWAYS_INLINE prototype-chain fast path for essentially every uncached property read, a human look at the extra per-iteration structure decode would still be worthwhile.

Checked: the sibling loop getNonIndexPropertySlot already re-reads via object->getPrototypeDirect() so it isn't affected; the checkNullStructure guard sits before the call so the reloaded value going straight into storedPrototype matches the original null-handling; and the four JITOperations.cpp callers already reload, so this was the last stale-pointer holder.

Extended reasoning...

Overview

This PR adds a single line to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h: after getOwnNonIndexPropertySlot returns false, it reloads structure = object->structureID().decode() before calling structure->storedPrototype(object). The stated reason is that reifying a static-table lazy property inside getOwnNonIndexPropertySlot can transition the object and still return false (when the PropertyCallback builder throws), leaving the local structure stale and tripping ASSERT(object->structure() == this) at StructureInlinesLight.h:56.

I verified the precedent: JITOperations.cpp performs this exact reload in four separate megamorphic slow-path loops (lines 389, 730, 920, 3777) with the comment "Reload it again since static-class-table can cause transition". I also confirmed getNonIndexPropertySlot in JSObjectInlines.h reads the prototype via object->getPrototypeDirect(), which re-fetches the structure, so it never had this issue. This PR brings the one remaining caller in line.

Security risks

None identified. The change reads the object's own current structure ID rather than a cached local — strictly more up-to-date data on the same object. It cannot introduce a type confusion or OOB; if anything it removes a stale-structure read that in release builds would fetch the prototype off the pre-transition structure.

Level of scrutiny

High. JSObject::getPropertySlot is ALWAYS_INLINE and sits on the generic property-lookup path taken by LLInt/baseline slow paths and C++ runtime callers for every uncached obj.prop read. Even a one-line addition here executes across the entire prototype chain for a large fraction of property accesses. The fix itself is mechanically obvious and mirrors established engine practice, but the location is performance-critical enough that it doesn't fit the "simple, mechanical, obvious" bar for auto-approval — a human familiar with JSC perf characteristics should confirm the extra decode per iteration is acceptable (it almost certainly is: same field just loaded, hot in cache, and JITOperations already does it).

Other factors

The PR description is unusually thorough — it includes the fuzzer fingerprint, a deterministic Bun repro, and a second route to the same failure. No test is added in this repo (the repro depends on Bun's lazy Bun.sql/Bun.semver builders). No prior human review comments are on the thread. Given the hot-path location I'm deferring rather than approving, but I see nothing wrong with the change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 14, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 8cb37ab to 686a757 Compare August 14, 2026 19:19
@coderabbitai

coderabbitai Bot commented Aug 14, 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.

I reviewed this PR and didn't find any bugs. The reload matches the established pattern in JITOperations.cpp and is clearly correct, but since getPropertySlot is the ALWAYS_INLINE hot path for every prototype-chain property lookup in JSC, a human sign-off on adding a load here would still be worthwhile.

Checked that structureID().decode() is the same cheap re-read used at the top of the loop, and that Structure::storedPrototype(object) at StructureInlinesLight.h:56 is the assert this fixes.
Confirmed the four JITOperations.cpp sites already reload with the cited comment, and getNonIndexPropertySlot re-reads the structure each iteration, so this was the one remaining stale-pointer caller.
Verified the reload cannot change behavior in the no-transition case — it just re-derives the same pointer.

Extended reasoning...

Overview

This PR adds a single-line structure reload in JSObject::getPropertySlot (Source/JavaScriptCore/runtime/JSObject.h) between the getOwnNonIndexPropertySlot miss and the structure->storedPrototype(object) prototype step, plus a three-line comment. The bug: getOwnNonIndexPropertySlot can reify a static-table lazy property (transitioning the object) and still return false when the PropertyCallback builder throws, leaving the loop holding a stale Structure* that trips ASSERT(object->structure() == this) at StructureInlinesLight.h:56 in debug and reads the prototype off the old structure in release.

Security risks

None. This is a defensive pointer refresh; it can only make the code more correct. There is no new attacker-controlled input, no allocation, no bounds change. The pre-fix release-build behavior (reading the prototype off the stale structure) was the riskier state.

Level of scrutiny

High — not because the change is complex, but because JSObject::getPropertySlot is ALWAYS_INLINE and, per the surrounding comment, "very hot in variable lookup". Every prototype-chain miss in the interpreter goes through this line. Adding even one load here is the kind of thing JSC maintainers historically weigh carefully. The PR description cites the four identical reloads already present in JITOperations.cpp ("Reload it again since static-class-table can cause transition"), which I confirmed, so there is direct precedent that this cost is acceptable — but a human should ratify applying that precedent to the hottest generic path.

Other factors

The fix is trivially correct: object->structureID().decode() re-reads a field already hot in cache and is identical to the load at the top of the loop. getNonIndexPropertySlot in JSObjectInlines.h already re-reads the structure each iteration, so this brings getPropertySlot in line with its sibling. The PR includes a deterministic repro and a well-argued root cause. The bug-hunting system found nothing. I am deferring solely on the "touches critical code paths" criterion, not on any correctness concern.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 14, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 686a757 to 9add1ce Compare August 16, 2026 19:06

@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. The one-line structure reload mirrors the established pattern in JITOperations.cpp ("Reload it again since static-class-table can cause transition") and fixes the ASSERT(object->structure() == this) trip at StructureInlinesLight.h:56. Because it sits on JSObject::getPropertySlot — the hot inlined prototype-walk path used by essentially every property miss — a human look is still worthwhile, mainly to confirm the extra structureID().decode() per prototype hop is acceptable.

What was reviewed:

  • Confirmed the reload matches the four identical reloads in JITOperations.cpp:389/730/920/3777 and that getNonIndexPropertySlot already re-reads via getPrototypeDirect().
  • Confirmed Structure::storedPrototype(object) asserts object->structure() == this, so the stale-pointer path described in the PR is real.
  • Checked that the reload only affects the local structure used for storedPrototype(); no other consumer of the pre-reload value remains in the loop body.
Extended reasoning...

Overview

The PR adds a single line (plus a comment) to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h: after getOwnNonIndexPropertySlot returns false, it reloads structure = object->structureID().decode() before calling structure->storedPrototype(object). The rationale is that getOwnNonIndexPropertySlotgetOwnStaticPropertySlot can reify a lazy static-table property (transitioning the object's structure) and still return false when the PropertyCallback builder throws, leaving the loop holding a stale Structure* and tripping ASSERT(object->structure() == this) at StructureInlinesLight.h:56 (or, in release, reading the prototype off the stale structure).

I verified the claim: JITOperations.cpp already performs this exact reload in four megamorphic slow paths with the comment "Reload it again since static-class-table can cause transition", and the sibling getNonIndexPropertySlot in JSObjectInlines.h avoids the problem by calling object->getPrototypeDirect(), which re-reads the structure. So this change brings the one remaining caller in line with existing practice.

Security risks

None introduced. The change strictly refreshes a local pointer to the current structure before dereferencing it; it cannot expose new state or bypass any check. If anything it closes a (narrow) correctness gap where a stale mono-proto structure could be consulted after a transition.

Level of scrutiny

High. JSObject::getPropertySlot is ALWAYS_INLINE and sits on the hottest generic property-lookup path in JavaScriptCore — every prototype-chain miss in interpreted/baseline code flows through here. Even a one-line change here is inlined into a very large number of call sites and executed on every prototype hop. The change itself is mechanically simple and clearly follows the established JITOperations.cpp pattern, but the location is core-runtime-critical, which is why I'm deferring rather than auto-approving.

Other factors

  • The fix is backed by a Fuzzilli finding with a deterministic repro described in the PR body, and the assert it trips (StructureInlinesLight.h:56) matches exactly.
  • The added structureID().decode() is cheap (a load + StructureID decode), and JITOperations.cpp already pays it on the equivalent paths, so perf impact should be negligible — but that judgment is better confirmed by a human familiar with JSC micro-benchmarks.
  • No behavior change on the common path where getOwnNonIndexPropertySlot doesn't transition: the reload returns the same structure.
  • No prior automated review from this bot on the PR; CodeRabbit had no actionable comments.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 16, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 9add1ce to d0e68f3 Compare August 17, 2026 00:06
@coderabbitai

coderabbitai Bot commented Aug 17, 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.

I reviewed this PR and didn't find any bugs. The reload matches the established pattern in JITOperations.cpp and fixes a real stale-structure read. Because it adds a structure decode on every prototype-chain step inside the ALWAYS_INLINE getPropertySlot — one of the hottest paths in JSC — a human sign-off on the perf trade-off would still be worthwhile.

What was reviewed:

  • Confirmed the four megamorphic slow paths in JITOperations.cpp already do exactly this reload with the same rationale.
  • Confirmed getNonIndexPropertySlot avoids the issue via getPrototypeDirect(), so this was the one remaining caller reusing the pre-call structure.
  • Checked that the reloaded structure cannot be null (transition only), so the earlier checkNullStructure guard need not be repeated.
Extended reasoning...

Overview

This PR adds a single functional line (plus a 3-line comment) to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h. After getOwnNonIndexPropertySlot returns false, it reloads structure = object->structureID().decode() before calling structure->storedPrototype(object). The motivation is that getOwnNonIndexPropertySlot can reify a static-table lazy property (transitioning the object) and still return false when the PropertyCallback builder throws, leaving the local structure pointer stale. That trips ASSERT(object->structure() == this) at StructureInlinesLight.h:56 in debug builds and reads the prototype off the stale structure in release.

Security risks

None introduced. The change makes the code strictly more correct: reading the object's current structure is always at least as safe as reading a stale one. If anything, it closes a (narrow) window where release builds could walk the prototype chain via a stale structure's stored prototype.

Level of scrutiny

High. JSObject::getPropertySlot is ALWAYS_INLINE and sits on the hottest property-lookup path in the engine — the header comment itself notes it is "function very hot in variable lookup". Adding an extra structureID().decode() per prototype-chain iteration is almost certainly negligible (the same field was decoded a few instructions earlier and will be hot in cache), and four call sites in JITOperations.cpp already do the identical reload with the comment "Reload it again since static-class-table can cause transition". Still, for a change to this specific function, a human familiar with JSC perf characteristics should confirm the trade-off is acceptable rather than, say, gating the reload on TypeInfo::hasStaticPropertyTable.

Other factors

  • The fix follows an established pattern with no ambiguity — I verified the four identical reloads in JITOperations.cpp:389/730/920/3777 and that getNonIndexPropertySlot in JSObjectInlines.h sidesteps the issue by using object->getPrototypeDirect() (which re-reads the structure).
  • The reload happens after the checkNullStructure block; since getOwnNonIndexPropertySlot can only transition the structure (never null it), no repeat null check is needed.
  • The PR description includes a deterministic Bun repro and Fuzzilli fingerprint, and preview builds have been produced across multiple revisions.
  • No prior human review on the timeline; only bot comments.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 17, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
Jarred-Sumner pushed a commit to oven-sh/bun that referenced this pull request Aug 17, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
…step

getOwnNonIndexPropertySlot can reify a static-table lazy property, which
transitions the object, and still return false when the PropertyCallback
builder throws (setUpStaticFunctionSlot reports the slot as not found so
the caller sees the exception). The inlined prototype-chain loop then
called storedPrototype(object) on the structure it read before the call,
tripping ASSERT(object->structure() == this) in StructureInlinesLight.h
on asserts builds.

The megamorphic slow paths in JITOperations.cpp already reload the
structure after getOwnNonIndexPropertySlot for exactly this reason, and
getNonIndexPropertySlot uses getPrototypeDirect() which re-reads it. Do
the same in the one caller that reused the stale pointer.

Seen by Fuzzilli in Bun: first read of a lazy Bun property whose builder
runs JS that touches another lazy property on the same object and then
throws, e.g. reading Bun.sql with a hooked Error superclass.
@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from d0e68f3 to 5b4dfb3 Compare August 18, 2026 04:26
@coderabbitai

coderabbitai Bot commented Aug 18, 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.

LGTM — one-line structure reload matching the established JITOperations.cpp pattern.

What was reviewed:

  • Verified the four structure = object->structure() reloads in JITOperations.cpp and that getNonIndexPropertySlot uses getPrototypeDirect() (re-reads structure), so this was the one caller reusing a stale pointer.
  • Confirmed the assert at StructureInlinesLight.h:56 is object->structure() == this, which the reload satisfies.
  • The reload uses object->structureID().decode(), identical to how structure is loaded at the top of the loop; structure isn't used after storedPrototype() in the iteration, so scoping is correct.
Extended reasoning...

Overview

Single functional line added to JSObject::getPropertySlot in Source/JavaScriptCore/runtime/JSObject.h: after getOwnNonIndexPropertySlot returns false, reload structure = object->structureID().decode() before calling structure->storedPrototype(object). Plus a three-line explanatory comment.

Security risks

None introduced. The change strictly narrows a correctness gap — reading the prototype off a stale Structure* after the object transitioned. In release builds the old code could read the wrong prototype (mono/poly-proto decision made on a stale structure); the reload can only make this read more correct, never less. No new inputs, no attacker-controlled data flow changes.

Level of scrutiny

This is a hot inlined path in the core property-lookup machinery, which normally warrants care. However, the change is mechanical and pattern-matches four identical reloads already present in JITOperations.cpp ("Reload it again since static-class-table can cause transition") and mirrors how the sibling getNonIndexPropertySlot in JSObjectInlines.h avoids the issue via getPrototypeDirect(). A single extra structureID().decode() per prototype step is a field load + shift — negligible next to the existing structure->get() table lookup done each iteration. The reload uses the exact same expression as the initial load a few lines above, so there is no new failure mode.

Other factors

The PR is backed by a Fuzzilli-found assertion failure with a deterministic repro and a clear root-cause analysis (reify → transition → builder throws → setUpStaticFunctionSlot reports miss → stale-structure prototype read). Preview builds succeeded across multiple revisions. No prior human reviewer comments are outstanding, and no bugs were flagged by the bug-hunting pass. The checkNullStructure template guard is unaffected — a live object's structureID() is always valid, and structure is not reused after the storedPrototype() call within the iteration.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 18, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 18, 2026
…-lookup

Fuzzilli kept hitting ASSERT(object->structure() == this) in
JSC::Structure::storedPrototype on debug builds (fingerprint
StructureInlinesLight.h(56)). The inlined JSObject::getPropertySlot loop
caches the structure, and reifying a static-table lazy property can
transition the object inside getOwnNonIndexPropertySlot and still return
false when the PropertyCallback builder throws, so the prototype step ran
on the pre-transition structure. Trigger: a first read of Bun.sql whose
module evaluation touches another lazy Bun property and then throws.

oven-sh/WebKit#390 reloads the structure before the prototype step, the
same reload the megamorphic slow paths in JITOperations.cpp already do.
This bumps the WebKit pin to its preview build.

Fixing that surfaced a second bug in the same path: defaultBunSQLObject
and constructBunSQLObject had a BUN_DEBUG-only
reportUncaughtExceptionAtEventLoop call that consumed the pending
exception, so RETURN_IF_EXCEPTION fell through and getObject() ran on an
empty JSValue (null deref), and an empty builder result with no pending
exception would trip RELEASE_ASSERT_NOT_REACHED in
setUpStaticFunctionSlot. Drop the debug reporting; the exception now
propagates to the caller like in release builds.
@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto eeab0404, the pin bun main currently uses (clean cherry-pick, same 4 lines; the upstream merge in that range does not touch getPropertySlot or Lookup.h). Head is 5b4dfb37.

Status on the bun side: the Bun-only half of the original problem (a debug-only exception report in the sql builders) landed separately in oven-sh/bun#29642, so oven-sh/bun#37001 now carries just this change's pin and one regression test. That test still aborts with the storedPrototype assertion on a bun debug build of current main pinned to plain eeab0404, and passes with this branch's preview build, so this is the part that is still needed. Once this merges, bun swaps its pin to the merged sha.

@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

One more data point for review, on whether this reload is the only one needed. Using a builder that re-enters the object (reifies another static property, so the object transitions) and then throws, I went through the ways of reaching the lazy property on a bun debug build of plain eeab0404. Exactly the entry points of this loop abort: a plain get, a call, the in operator, a get through an object that inherits from the static-table object, and a with scope lookup. All of them exit cleanly with this branch. The own-property paths (hasOwnProperty, getOwnPropertyDescriptor, defineProperty), spread and enumeration already behave without this change, and a put or delete on an unreified entry never runs the builder, so I did not find a second site that needs the same treatment.

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