Skip to content

jsc: TypedArray indexed access at 4294967295 on a 2**32-length view - #35876

Open
robobun wants to merge 5 commits into
mainfrom
farm/d4d0d8bc/typedarray-uint32max-index
Open

jsc: TypedArray indexed access at 4294967295 on a 2**32-length view#35876
robobun wants to merge 5 commits into
mainfrom
farm/d4d0d8bc/typedarray-uint32max-index

Conversation

@robobun

@robobun robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Problem

const u = new Uint8Array(2**32);
u.length;                        // 4294967296
u[4294967294];                   // 0
u[4294967295];                   // undefined   (V8: 0)
u[4294967295] = 42;
u[4294967295];                   // undefined   (V8: 42)
u.at(4294967295);                // undefined   (V8: 42)
4294967295 in u;                 // true        ([[HasProperty]] disagrees with [[Get]])
Object.hasOwn(u, "4294967295");  // false       (V8: true)
Object.getOwnPropertyDescriptor(u, 4294967295);  // undefined
Object.defineProperty(u, 4294967295, { value: 1, writable: true, enumerable: true, configurable: true });
  // TypeError: Attempting to store canonical numeric string property on a typed array
new DataView(u.buffer).getUint8(4294967295);     // 0 (DataView path is fine)

IsValidIntegerIndex(O, index) only requires 0 <= index < O.[[ArrayLength]]. A Uint8Array can have length up to MAX_ARRAY_BUFFER_SIZE (2**32 on 64-bit), so 4294967295 is a valid index there, and V8 treats it as one.

Cause

JSGenericTypedArrayView::{getOwnPropertySlot, put, defineOwnProperty, deleteProperty}(PropertyName) call parseIndex(), which is capped at MAX_ARRAY_INDEX = 0xFFFFFFFE because a regular JS Array's length is a uint32 and the largest index is length - 1. For "4294967295" parseIndex returns nullopt, and the isCanonicalNumericIndexString fallback treats it as always out of bounds. The unsigned-index overloads (getOwnPropertySlotByIndex etc.) already compare against the full size_t length via inBounds(), which is why 4294967295 in u (routed through getUInt32hasProperty(unsigned)) returns true while u[4294967295] returns undefined.

Fix

oven-sh/WebKit#348 adds parseTypedArrayIndex(PropertyName) in PropertyName.h that extends parseIndex() to the full uint32 range (UINT32_MAX is the only extra value, handled as a literal compare after parseIndex returns nullopt) and routes the four JSGenericTypedArrayView PropertyName overloads through it.

TypedArray structures are shared per type, not per length, so once getOwnPropertySlot("4294967295") becomes length-dependent the structure-keyed caches that previously memoised "always absent" must be told not to: HasOwnPropertyCache::tryAdd widens its index guard to parseTypedArrayIndex, and the isTypedArrayType && isCanonicalNumericIndexString short-circuits in generateConditions / prepareChainForCaching / PolyProtoAccessChain::tryCreate return invalid instead of a valid empty condition set when parseTypedArrayIndex accepts the key, so no Miss/InMiss IC is installed for it on a TypedArray. Without those guards, Object.hasOwn(short, "4294967295"); Object.hasOwn(long, "4294967295") trips ASSERT(*result == hasOwnProperty(...)) in ObjectPrototype.cpp on assert-enabled builds, and a warmed IC returns the stale miss in release builds.

The DFG/FTL inline fast paths speculate Int32 for the index and OSR-exit or fall to the C++ slow path for 0xFFFFFFFF, so no JIT change is needed.

This PR bumps WEBKIT_VERSION to the autobuild-preview-pr-348-c106f67d preview build and adds test/js/bun/jsc/typedarray-uint32max-index.test.ts, which spawns a child that allocates a 2**32-byte Uint8Array and exercises [[Get]], [[Set]], .at, in, Object.hasOwn, Object.getOwnPropertyDescriptor, Object.defineProperty, Reflect.set and Reflect.deleteProperty at index 4294967295, the same key on a short view (still out of bounds), the adjacent canonical numeric strings "4294967296" / "-0" (still undefined), Int8Array/Uint8ClampedArray views over the same buffer, and the hasOwn(small)→hasOwn(big) / warmed-IC probe(small)→probe(big) orderings that exercise the cache guards. The child skips cleanly if the allocation is refused.

Also in this range (549170099226..c106f67d)

  • oven-sh/WebKit#328 inspector: release throw scope before tail-calling impl in injected-script prototype host functions
  • oven-sh/WebKit#317 LiteralParser: throw RangeError on OOM when copying a JSON string value
  • oven-sh/WebKit#331 SignalsWin: fix VEH return value and register behind AddressSanitizer's handler
  • oven-sh/WebKit#332 Heap: make minEdenToOldGenerationRatio a JSC option (same default; behaviour unchanged)
  • oven-sh/WebKit#347 getAsyncStackTrace: unwrap InternalFieldTuple in reaction context

Verification

$ USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/typedarray-uint32max-index.test.ts
(fail)    # initialGetNum undefined, afterDefine "threw: TypeError", hasOwn false, ...

$ bun bd test test/js/bun/jsc/typedarray-uint32max-index.test.ts
(pass)

Node 26.3.0 on the same fixture prints the direct-access values exactly. For the warmed-IC ordering Node returns undefined/false (V8 shares the stale-miss behaviour), which is a V8 bug against IsValidIntegerIndex; this change gives the spec answer there.

Found while fixing Buffer.writeUInt8 rejecting offset 4294967295 (#35867 works around it at the Buffer layer; this closes the engine-level gap).

Once oven-sh/WebKit#348 merges, WEBKIT_VERSION should be repointed at the merged main sha (the preview release is deleted at that point).


[decide:webkit] gate passed · iteration 0 · 2 files touched

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

Debug/ASAN (expected pass):
$ bun bd test 'test/js/bun/jsc/typedarray-uint32max-index.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "test/js/bun/jsc/typedarray-uint32max-index.test.ts"
bun test v1.4.0 (e83092d93)

test/js/bun/jsc/typedarray-uint32max-index.test.ts:
(pass) TypedArray indexed access at 4294967295 on a 2**32-length view [420.38ms]

 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [2.79s]
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                       |   2 +-
 test/js/bun/jsc/typedarray-uint32max-index.test.ts | 146 +++++++++++++++++++++
 2 files changed, 147 insertions(+), 1 deletion(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                                reads  edits  tests
scripts/build/deps/webkit.ts                            1      2      0
test/js/bun/jsc/typedarray-uint32max-index.test.ts      2      6      0

Bump WEBKIT_VERSION to the oven-sh/WebKit#348 preview build and add a
regression test. parseIndex() is capped at MAX_ARRAY_INDEX (0xFFFFFFFE)
for regular Arrays, but a 1-byte TypedArray can have length 2**32, so
index 0xFFFFFFFF is valid. The PropertyName overloads of
getOwnPropertySlot/put/defineOwnProperty/deleteProperty now accept it.
@robobun

robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Status: build #82196 finished 195 passed / 1 failed. The new test typedarray-uint32max-index.test.ts passed on every lane. The single failure is binary-size (+512 to +576 KB across targets), which is the cost of the WebKit 549170099226..c106f67d range (five PRs besides this one) rather than the 41-line change in oven-sh/WebKit#348 itself; a maintainer can add [skip size check] or reorder the landing if that is preferred. Six [flaky] annotations (complex-workspace, test-http-agent-keepalive, no-orphans, require-cache, 20144, fetch-leak) are pre-existing, unrelated, and retried green.

oven-sh/WebKit#348 preview build autobuild-preview-pr-348-c106f67d is published (39/39 green); bun bd test against it passes. Self-review of the first revision found that making getOwnPropertySlot("4294967295") length-dependent let HasOwnPropertyCache and the GetBy/InBy Miss IC memoise a stale "absent" on the shared Uint8Array structure; c106f67d widens those guards and the test covers the hasOwn(small)→hasOwn(big) and warmed-IC probe(small)→probe(big) orderings. A second self-review pass on c106f67d surfaced nothing further.

Once oven-sh/WebKit#348 merges, WEBKIT_VERSION should be repointed at the merged main sha.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 12 minutes

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

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 11875df5-7044-4845-b582-64f21e2d0719

📥 Commits

Reviewing files that changed from the base of the PR and between 44f6469 and e83092d.

📒 Files selected for processing (2)
  • scripts/build/deps/webkit.ts
  • test/js/bun/jsc/typedarray-uint32max-index.test.ts

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

Comment thread test/js/bun/jsc/typedarray-uint32max-index.test.ts
robobun added 2 commits July 26, 2026 04:38
Object.hasOwn / get_by_id / in_by_id can memoise "4294967295 is absent"
on the shared TypedArray structure and then return that stale answer for
a 2**32-length view. Repoint WEBKIT_VERSION at the c106f67d preview
(oven-sh/WebKit#348 revised) which widens HasOwnPropertyCache::tryAdd and
the three TypedArray canonical-numeric-string short-circuits in
generateConditions / prepareChainForCaching / PolyProtoAccessChain to
refuse caching when parseTypedArrayIndex() accepts the key. Cover the
hasOwn(small) -> hasOwn(big) and warmed-IC(small) -> probe(big) orderings
in the test.
@robobun

robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 3:58 AM PT - Jul 26th, 2026

@robobun, your commit e83092d has 1 failures in Build #82196 (All Failures):

  • 📦 Binary size — 11 over 0.50 MB
  • targetthis build canary: main #79916
    sizeΔ
    bun-darwin-aarch6458.11 MB57.58 MB+548.8 KB
    bun-darwin-x6463.47 MB62.95 MB+528.5 KB
    bun-linux-aarch6470.98 MB70.42 MB+576.0 KB
    bun-linux-x6472.47 MB71.95 MB+528.0 KB
    bun-linux-aarch64-musl64.88 MB64.32 MB+576.0 KB
    bun-linux-x64-musl66.96 MB66.45 MB+528.0 KB
    bun-linux-aarch64-android78.47 MB77.97 MB+512.0 KB
    bun-linux-x64-android80.60 MB80.10 MB+513.2 KB
    bun-freebsd-x6483.06 MB82.56 MB+512.0 KB
    bun-freebsd-aarch6484.82 MB84.31 MB+528.0 KB
    bun-windows-x6480.25 MB79.70 MB+566.0 KB
    bun-windows-aarch6470.86 MB70.34 MB+538.5 KB

    Add [skip size check] to the commit message if this increase is intentional.


🧪   To try this PR locally:

bunx bun-pr 35876

That installs a local version of the PR into your bun-35876 executable, so you can run:

bun-35876 --bun

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found in the test (the earlier ASAN_OPTIONS nit is now applied), but this is a JSC engine bump — it points WEBKIT_VERSION at a preview build of an unmerged WebKit PR that changes TypedArray property lookup and the structure-keyed IC/HasOwnPropertyCache guards, and the range also pulls in five unrelated oven-sh/WebKit PRs. That's engine-critical and the actual code lives in oven-sh/WebKit#348, so a maintainer should sign off (and the preview tag needs repointing once #348 lands).

What was reviewed

  • The new test: subprocess pipe draining, assertion ordering, JSON round-trip of undefined in toEqual, ASAN skip path — all check out.
  • Spec expectations at index 4294967295 (gopd.configurable: true, deleteInBounds: false, smallDelete: true) match ECMA-262 IntegerIndexedElement semantics.
  • prebuiltDestDir / prebuiltUrl handling of the autobuild- prefix — the existing branch already covers it.
Extended reasoning...

Overview

Two files: a one-line WEBKIT_VERSION bump in scripts/build/deps/webkit.ts (from a merged-main sha to the preview tag autobuild-preview-pr-348-c106f67d) and a new 146-line test at test/js/bun/jsc/typedarray-uint32max-index.test.ts. The behavioural change itself — making index 4294967295 reachable on a 2**32-length TypedArray via parseTypedArrayIndex and widening the HasOwnPropertyCache / IC miss-condition guards — lives entirely in oven-sh/WebKit#348, not in this diff.

Security risks

None introduced by the files in this repo. The engine-side change widens a bounds check by exactly one value (UINT32_MAX) on the property-name path only; the unsigned-index path already handled it via inBounds(). No new untrusted-input parsing here. The test allocates 4 GiB in a child process with a graceful skip; no network, no filesystem writes outside the temp allocation.

Level of scrutiny

High. WebKit bumps are the highest-blast-radius change in this repo: this one alters property lookup on a core JS type and simultaneously touches four IC/cache sites (HasOwnPropertyCache::tryAdd, generateConditions, prepareChainForCaching, PolyProtoAccessChain::tryCreate) whose correctness I cannot verify from this diff. The bump also carries five other WebKit PRs (#317, #328, #331, #332, #347) in the same range. The version is pinned to a preview release that the description says will be deleted once the WebKit PR merges — so merging this as-is would leave main pointing at an ephemeral artifact. All of that is squarely maintainer territory.

Other factors

  • My earlier inline nit (ASAN allocator_may_return_null=1 so the 4 GiB skip path works) was applied in a901bb3 and the thread is resolved.
  • The test is well-constructed against REVIEW.md: pipes drained concurrently via Promise.all, stderr asserted before parsing stdout, exit code asserted last, exact-value toEqual on the whole result object, variant matrix covered (numeric vs string key, .at, in, hasOwn, gOPD, defineProperty, Reflect.set/deleteProperty, short-view negative case, adjacent canonical numeric strings, Int8/Uint8Clamped views, and the small→big cache-ordering probes).
  • I checked that JSON.stringify dropping the undefined-valued keys (oneBeyond, minusZero, smallGet) is fine because toEqual treats missing and undefined as equivalent.
  • webkit.ts already special-cases autobuild--prefixed versions in both the release-tag URL and prebuiltDestDir, so the preview tag resolves without further changes.

Given the engine-critical scope, the bundled unrelated WebKit changes, and the temporary preview pin, I'm deferring rather than approving.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found in the test (the ASAN allocator_may_return_null nit from the earlier pass is applied). Deferring to a human for the WebKit bump itself: it points at a preview-build tag that will be deleted once oven-sh/WebKit#348 merges, and the range pulls in five other JSC changes (#317/#328/#331/#332/#347) beyond the TypedArray fix — that's an engine-level dependency bump touching property-access ICs and cache guards, which warrants maintainer sign-off.

What was reviewed

  • Test fixture: subprocess pipe draining, ASAN_OPTIONS skip path, expected values against IsValidIntegerIndex (incl. deleteInBounds: false, smallDelete: true, gopd.configurable: true).
  • webkit.ts: the autobuild- tag is handled by the existing prebuiltUrl/prebuiltDestDir branches, so no build-script breakage.
  • IC/HasOwnPropertyCache ordering assertions match the widened guards described for c106f67d.
Extended reasoning...

Overview

Two files: a one-line WEBKIT_VERSION bump in scripts/build/deps/webkit.ts from a merged-main sha to autobuild-preview-pr-348-c106f67d, and a new 146-line test at test/js/bun/jsc/typedarray-uint32max-index.test.ts. The behavioural change lives entirely in oven-sh/WebKit#348 (adds parseTypedArrayIndex, routes four JSGenericTypedArrayView PropertyName overloads through it, and widens the HasOwnPropertyCache::tryAdd / generateConditions / prepareChainForCaching / PolyProtoAccessChain::tryCreate guards so a length-dependent answer at key "4294967295" isn't memoised on the shared TypedArray structure). The bump also carries five unrelated WebKit PRs in the 549170099226..c106f67d range.

Security risks

None identified in the bun-side diff. The engine change tightens spec conformance for a boundary index rather than relaxing any check; no untrusted-input parsing or auth surface is touched here. The bundled WebKit changes (LiteralParser OOM → RangeError, SignalsWin VEH ordering, inspector throw-scope release, a Heap option, async-stack unwrapping) are not reviewable from this diff and are part of why a human should look.

Level of scrutiny

High. A WebKit version bump is a JS-engine dependency change: the modified paths (getOwnPropertySlot/put/defineOwnProperty/deleteProperty, IC miss-condition generation, HasOwnPropertyCache) are core to every property access. The author already found one correctness bug in their own first revision (stale structure-keyed miss cache), which underlines how subtle this area is. Additionally, WEBKIT_VERSION is being pointed at a preview release tag that the description says will be deleted once the upstream PR merges — a maintainer should decide whether to land against the preview or wait for the merged sha.

Other factors

  • My earlier inline nit (ASAN allocator_may_return_null=1 so the 4 GiB skip path works) was applied in a901bb3 and the thread is resolved; the current diff carries the fix.
  • The test is well-constructed per REVIEW.md conventions: spawned subprocess with concurrent stdout/stderr/exited drain, bunEnv spread, exact-value toEqual on a JSON payload, negative cases ("4294967296", "-0", short view), sibling element types, and the small→big cache-ordering probes. JSON.stringify drops undefined keys, but toEqual treats missing and undefined as equivalent, so the oneBeyond/minusZero/smallGet assertions hold.
  • scripts/build/deps/webkit.ts already special-cases autobuild- prefixes in both prebuiltUrl (tag passthrough) and prebuiltDestDir (uses the full slug instead of a 16-char slice), so the preview tag doesn't break the build machinery.
  • No CODEOWNERS or outstanding human review comments; CI was retriggered after the preview tarballs published but the build result isn't visible in the timeline yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant