Skip to content

TypedArray.prototype.with: keep a BigInt array's elements when argument coercion grows its buffer (WebKit bump for oven-sh/WebKit#601) - #42082

Open
robobun wants to merge 1 commit into
mainfrom
robobun/d6812ecb/typed-array-with-bigint-resizable
Open

robobun wants to merge 1 commit into
mainfrom
robobun/d6812ecb/typed-array-with-bigint-resizable

Conversation

@robobun

@robobun robobun commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • BigInt64Array.prototype.with(index, value) and BigUint64Array.prototype.with lose data when the receiver is a length-tracking view over a resizable ArrayBuffer or growable SharedArrayBuffer and coercing index or value grows that buffer. The returned copy keeps the replaced element and has 0 in every other slot: ta.set([10n, 11n, 12n, 13n]); ta.with(0, { valueOf() { rab.resize(40); return 1n; } }) gives 1,0,0,0. Node and the spec give 1,11,12,13. The ten Number element types, fixed-length views, and shrinking are correct.
  • The cause is in JSC, genericTypedArrayViewProtoFuncWith() (Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeFunctions.h:2107). Its "length changed during coercion" loop copies element k only if canGetIndexQuickly(k), which is inBounds(k) && Adaptor::canConvertToJSQuickly. That flag is false for the BigInt adaptors (making a JSValue allocates a JSBigInt), but the loop reads the native value and never makes a JSValue. Every k != index fell through to the 0 default.

Fix

Background

  • %TypedArray%.prototype.with reads len first, then runs ToIntegerOrInfinity(index) and ToNumber / ToBigInt(value), which can call user code that resizes the buffer. It then re-validates the index, creates the result with the original len, and copies each k != index with Get(O, k).
  • A length-tracking view (new BigInt64Array(rab) with no explicit length) follows its buffer's byte length. After a grow every k < len is still in bounds, so Get returns the stored element. After a shrink the tail reads as undefined: Number arrays store NaN / 0, BigInt arrays throw on ToBigInt(undefined). JSC already threw there, so only the grow direction was wrong.
  • JSC memcpys the source when its length did not move across the coercions and only runs the per-element loop when it did, which is why a same-size resize() inside the hook was fine.
Notes
  • The imported V8 test in JSC (JSTests/stress/v8-harmony-typed-array-with.js, TestParameterConversionGrows) takes this path for every element type but never writes to the array first, so the zeros compared equal. test262 has no case that grows the buffer inside the coercion with non-zero contents either; both suites pass before and after.
  • Upstream WebKit main has the same line.
  • The new test runs in-process (no child): the old engine returns wrong values, it does not crash.

[policy-decision: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/typed-array-with-resizable-buffer.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/bun/jsc/typed-array-with-resizable-buffer.test.ts
bun test v1.4.3 (f42e98025)

test/js/bun/jsc/typed-array-with-resizable-buffer.test.ts:
(pass) Int8Array.prototype.with over a resizable ArrayBuffer > keeps the source elements when coercing value grows the buffer [26.93ms]
(pass) Int8Array.prototype.with over a resizable ArrayBuffer > keeps the source elements when coercing index grows the buffer [5.02ms]
(pass) Int8Array.prototype.with over a resizable ArrayBuffer > negative index, both coercions grow the buffer [5.61ms]
(pass) Int8Array.prototype.with over a resizable ArrayBuffer > length-tracking view with a byte offset [5.94ms]
(pass) Int8Array.prototype.with over a resizable ArrayBuffer > coercing value shrinks the buffer [10.97ms]
(pass) Uint8Array.prototype.with over a resizable ArrayBuffer > keeps the source elements when coercing value grows the buffer [2.66ms]
(pass) Uint8Array.prototype.with over a resizable ArrayBuffer > keeps the source elements when coercing index grows the buffer [1.28ms]
(pass) Uint8Array.prototype.with over a resizable ArrayBuffer > negative index, both coercions grow the buffer [1.69ms]
(pass) Uint8Array.prototype.with over a resizable ArrayBuffer > length-tracking view with a byte offset [2.25ms]
(pass) Uint8Array.prototype.with over a resizable ArrayBuffer > coercing value shrinks the buffer [1.99ms]
(pass) Uint8ClampedArray.prototype.with over a resizable ArrayBuffer > keeps the source elements when coercing value grows the buffer [1.92ms]
(pass) Uint8ClampedArray.prototype.with over a resizable ArrayBuffer > keeps the source elements when coercing index grows the buffer [1.25ms]
(pass) Uint8ClampedArray.prototype.with over a resizable ArrayBuffer > negative index, both coercions grow the buffer [0.93ms]
(pass) Uint8ClampedArray.prototype.with over a resizable ArrayBuffer > length-tracking view with a byte offset [1.24ms]
(pass) Ui
... (truncated)
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                       |   2 +-
 .../jsc/typed-array-with-resizable-buffer.test.ts  | 160 +++++++++++++++++++++
 2 files changed, 161 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      1      7
…st/js/bun/jsc/typed-array-with-resizable-buffer.test.ts      0      1      6

root cause · written by the author bot

The BigInt path of %TypedArray%.prototype.with re-read the length of a length-tracking view after coercing index and value, and when a coercion side effect grew the underlying resizable or growable buffer it allocated a result of the new, larger length but never copied the source elements into it, writing only the replacement element and leaving every other slot zero-initialized. The fix copies the source view's contents into the freshly allocated result before storing the replacement element, matching the non-BigInt path and the spec's requirement that every element at k != index b…

@robobun

robobun commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced on bun 1.4.3 and on current main (WebKit 2e2aa2290fac):

const b = new ArrayBuffer(32, { maxByteLength: 64 });
const v = new BigInt64Array(b);
v.set([10n, 11n, 12n, 13n]);
console.log(v.with(0, { valueOf() { b.resize(40); return 1n; } }).join()); // bun: 1,0,0,0   node: 1,11,12,13

USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/typed-array-with-resizable-buffer.test.ts fails 16 of 120 (the BigInt64Array / BigUint64Array grow cases). bun bd test on this branch: 108 pass, 12 skip, 0 fail.

The engine fix is oven-sh/WebKit#601. This PR pins its preview build and must not merge before that PR lands; I will move the pin to the merged commit.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: d9c587ab-bfc1-4588-97f6-e77411283b6c

📥 Commits

Reviewing files that changed from the base of the PR and between b5ba14b and 2b166da.

📒 Files selected for processing (2)
  • scripts/build/deps/webkit.ts
  • test/js/bun/jsc/typed-array-with-resizable-buffer.test.ts

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


Walkthrough

The change updates the WebKit build reference to a preview release and adds tests for TypedArray.prototype.with with resizable ArrayBuffer and growable SharedArrayBuffer instances.

Changes

WebKit release reference

Layer / File(s) Summary
Update WebKit release reference
scripts/build/deps/webkit.ts
WEBKIT_VERSION now references autobuild-preview-pr-601-2b6703ca.

Resizable typed-array behavior

Layer / File(s) Summary
Add resizable-buffer behavior coverage
test/js/bun/jsc/typed-array-with-resizable-buffer.test.ts
Adds helpers and parameterized tests for numeric and BigInt typed arrays, buffer growth during coercion, negative indices, byte-offset views, fixed-length copies, and documented shrinkage behavior.

Suggested reviewers: jarred-sumner

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 2b166

This updates WebKit for the TypedArray.prototype.with resizable-buffer fix and adds regression coverage. No concrete merge-blocking risk remains in the supplied evidence.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the TypedArray.prototype.with regression, the BigInt element preservation fix, and the related WebKit update. It is specific and directly related to the main change.
Description check ✅ Passed The description provides detailed problem, fix, background, verification steps, test results, and deployment notes. It does not use the exact template headings, but it contains the required informatio…

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

@robobun

robobun commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

The automated review raised no findings, so the diff stays as is. The only open item is the merge order: oven-sh/WebKit#601 lands first, then I move WEBKIT_VERSION from the preview tag to the merged commit. Buildkite is still running on 2b166da.

* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "2e2aa2290fac856d6f451ceacb58f7f5b44dd057";
export const WEBKIT_VERSION = "autobuild-preview-pr-601-2b6703ca";

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.

🔴 WEBKIT_VERSION is pinned to autobuild-preview-pr-601-2b6703ca, an ephemeral preview-PR release tag; merging this would leave main pointing at an artifact that disappears once the WebKit PR closes and that carries unmerged upstream commits. Fix: before merge, swap to the merged oven-sh/WebKit commit SHA (or its autobuild-<sha> tag) and verify prebuilt archives exist for every platform × flavor, per the Dependencies & vendoring rule and .claude/commands/upgrade-webkit.md. The PR description already flags this as a do-not-merge precondition; this comment tracks it as the merge blocker.

Extended reasoning...

.claude/docs/landing-prs.md (Dependencies & vendoring) states: "Never merge a pin to an ephemeral artifact (preview tags, unmerged-PR builds) — swap to the merged upstream SHA and verify prebuilt artifacts exist for every platform × flavor before merge." scripts/build/download.ts:328 already warns that autobuild-preview-pr-* releases only exist while the WebKit PR is open, and .claude/commands/upgrade-webkit.md:34 says the preview tag must be bumped to the merge-commit's autobuild-<sha> before merging the bun PR. If this lands as-is and oven-sh/WebKit#601 is later closed/force-pushed, fresh checkouts and CI will fail to fetch WebKit prebuilts. The base branch pinned a merged SHA (2e2aa2290fac…), so this is a regression the diff introduces. The author notes the same in the PR body; per the vendoring rule it is still a hard merge blocker to track.

Verification: normal — acknowledged in diff: the PR description states "The pin is a preview tag, not a merge commit. Do not merge before oven-sh/WebKit#601 lands. I move the pin to the merged commit then." That claim is accurate but does not resolve the hazard; the pin must be swapped before merge. scripts/build/deps/webkit.ts:6 changes WEBKIT_VERSION from the merged SHA… | normal — acknowledged in diff:…

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, and intended. The preview tag is only here so CI can run the new test against the engine fix before oven-sh/WebKit#601 merges. Once it lands I push a commit that sets WEBKIT_VERSION to the merged 40-character SHA and check that the autobuild-<sha> release carries all prebuilt archives, then resolve this thread. Leaving it open until then as the merge blocker.

@robobun

robobun commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

The review's one finding is the preview-tag pin at scripts/build/deps/webkit.ts:6. That is the known precondition, not a defect to fix now: the pin moves to the merged oven-sh/WebKit commit as soon as oven-sh/WebKit#601 lands, and the thread stays open until that push. No other changes requested. Buildkite #113310 is running.

This branch has not been deployed

No deployments
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.

2 participants