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

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: b37c0679-0e71-4e59-950e-01e36da4fe88

📥 Commits

Reviewing files that changed from the base of the PR and between 14f4d7b and a65ca749a60399679a866b461d2f1f833789dd18.

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

Included review availability: Your plan provides up to 5 included reviews per 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.
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a detailed technical explanation, reproduction case, rationale, related code paths, and testing context. It does not follow the repository template because it omits a Bugzilla… Add the associated Bugzilla URL, a reviewed-by line, the required explanation in the template format, and a list of changed paths and functions. Confirm that the pull request is linked from Bugzilla and has the required labels and assignmen…
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: reloading the structure before the prototype step 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.
Full details: Description check

Explanation

The description provides a detailed technical explanation, reproduction case, rationale, related code paths, and testing context. It does not follow the repository template because it omits a Bugzilla link or bug number, a reviewed-by line, and the required changed-file and function list.

Resolution

Add the associated Bugzilla URL, a reviewed-by line, the required explanation in the template format, and a list of changed paths and functions. Confirm that the pull request is linked from Bugzilla and has the required labels and assignment metadata.


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 (c3a156eade), so it has presumably been building.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
22905e76 autobuild-preview-pr-390-22905e76 2026-08-28 14:40:53 UTC
41813b80 autobuild-preview-pr-390-41813b80 2026-08-28 09:48:51 UTC
ebee9eee autobuild-preview-pr-390-ebee9eee 2026-08-28 08:39:16 UTC
28e2d2a8 autobuild-preview-pr-390-28e2d2a8 2026-08-28 02:55:21 UTC
168fe18a autobuild-preview-pr-390-168fe18a 2026-08-27 23:58:00 UTC
5242a091 autobuild-preview-pr-390-5242a091 2026-08-27 16:48:12 UTC
31409f3e autobuild-preview-pr-390-31409f3e 2026-08-26 22:01:26 UTC
6815686e autobuild-preview-pr-390-6815686e 2026-08-26 06:36:12 UTC
a65ca749 autobuild-preview-pr-390-a65ca749 2026-08-25 22:37:24 UTC
c836313b autobuild-preview-pr-390-c836313b 2026-08-25 09:49:52 UTC
fd0eceb3 autobuild-preview-pr-390-fd0eceb3 2026-08-24 12:16:36 UTC
504873a9 autobuild-preview-pr-390-504873a9 2026-08-23 09:19:24 UTC
78b9b769 autobuild-preview-pr-390-78b9b769 2026-08-21 07:10:32 UTC
277189f4 autobuild-preview-pr-390-277189f4 2026-08-19 10:09:22 UTC
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 26, 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 26, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 76882271, the pin bun main uses since oven-sh/bun#40507. This PR is now commit 6815686e: the same 4-line change, applied as the identical JSObject.h blob because the three commits between 1cb96a7b and 76882271 touch only the sampling profiler's stack walk (#395) and the bytecode cache record layout.

The preview build for it is published (autobuild-preview-pr-390-6815686e, 42 assets, run 32931889197 in one attempt). oven-sh/bun#37001 is re-pinned to it as fca84468af, its BunObject.test.ts passes against a debug ASAN build of it, and its CI build 106142 is running.

@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 6815686 to 31409f3 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
…-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 26, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 2da33d53, the pin bun main uses since oven-sh/bun#40570. This PR is now commit 31409f3e: the same 4-line change, applied as the identical JSObject.h blob because the one commit between 76882271 and 2da33d53 (#519) only touches CachedTypes.cpp and CachedTypes.h.

The preview build for it is published (autobuild-preview-pr-390-31409f3e, 42 assets, run 33014716153 in one attempt). oven-sh/bun#37001 is re-pinned to it as b5378dbbbd, its BunObject.test.ts passes against a debug ASAN build of it, and its CI build 106396 is running. The previous round's CI build 106142 passed on every lane.

@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 31409f3 to 5242a09 Compare August 27, 2026 09:49

@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 27, 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 27, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 72597399, the pin bun main uses since oven-sh/bun#40270. This PR is now commit 5242a091: the same 4-line change, applied as the identical JSObject.h blob because the two commits between 2da33d53 and 72597399 (#505, #521) touch only the bytecode cache, the parser's source provider cache and SymbolImpl.cpp.

The normal preview run (33060346632) built 42 of 43 jobs: the Windows arm64 job fails in the Scoop install step for every run on this repo since Aug 27, including main's (#523 and #524 fix it). I dispatched the Preview Build workflow from the #523 branch with pr_number=390 (run 33090184977), which ran that workflow file against this PR's head, and autobuild-preview-pr-390-5242a091 is now published with 42 assets. oven-sh/bun#37001 is re-pinned to it as 2ca8cf7b79, its BunObject.test.ts passes against a debug ASAN build of it, and its CI build 106926 is running.

@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 5242a09 to 168fe18 Compare August 27, 2026 23:22

@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
…-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 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 0bb01ed5, the pin bun main uses since oven-sh/bun#40643. This PR is now commit 168fe18a: the same 4-line change, applied as the identical JSObject.h blob because the commits between 72597399 and 0bb01ed5 (module loading for ahead-of-time embedders, provideModule, and #523) do not touch that file.

With #523 merged the normal preview run works again: autobuild-preview-pr-390-168fe18a is published with 42 assets (run 33126064536, one attempt). oven-sh/bun#37001 is re-pinned to it as 1a42296843, its BunObject.test.ts passes against a debug ASAN build of it, and its CI build 107113 is running.

@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 168fe18 to 28e2d2a Compare August 28, 2026 02:07

@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
…-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 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto f5deafe0, the pin bun main uses since oven-sh/bun#40674. This PR is now commit 28e2d2a8: the same 4-line change, applied as the identical JSObject.h blob because the two commits between 0bb01ed5 and f5deafe0 only touch the module loader.

The preview build for it is published (autobuild-preview-pr-390-28e2d2a8, 42 assets, run 33134953237 in one attempt). oven-sh/bun#37001 is re-pinned to it as 8b5d2fcb09, its BunObject.test.ts passes against a debug ASAN build of it, and its CI build 107205 is running. Bun's fuzzer still hits the storedPrototype assertion on main builds with the stock pin (most recently on bun commit 43fad9bc61 with cb61607f), which is the abort this PR removes.

@robobun
robobun force-pushed the farm/f76ebd86/getpropertyslot-stale-structure branch from 28e2d2a to ebee9ee Compare August 28, 2026 08:10

@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
…-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 ebee9ee to 41813b8 Compare August 28, 2026 08:46

@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
…-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 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased twice: onto 1817c3c3 (the #528 upstream merge, as ebee9eee) and then onto c4ddc0cf, the pin bun main uses since oven-sh/bun#40677. This PR is now commit 41813b80: the same 4-line change, applied as the identical JSObject.h blob because neither range touches that file (nor Lookup.cpp, Lookup.h, JSObjectInlines.h or StructureInlinesLight.h).

The preview build for it is published (autobuild-preview-pr-390-41813b80, 42 assets, run 33156681183 in one attempt). oven-sh/bun#37001 is re-pinned to it as e1aee47412 and its BunObject.test.ts passes against a debug ASAN build of it.

…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 41813b8 to 22905e7 Compare August 28, 2026 14:08

@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
…-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 28, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto ceb9f90f, the pin bun main uses since oven-sh/bun#40767. This PR is now commit 22905e76: the same 4-line change, applied as the identical JSObject.h blob because the four commits between c4ddc0cf and ceb9f90f (#530, #531) touch only the DFG integer range optimization, the bytecode cache constant decode, RegExp and JSString.h.

The preview build for it is published (autobuild-preview-pr-390-22905e76, 42 assets, run 33178685903 in one attempt). oven-sh/bun#37001 is re-pinned to it as 33d0c02b6f, its BunObject.test.ts passes against a debug ASAN build of it, and its CI build 107725 is running.

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