Skip to content

TypedArray search: an integer needle that a Float32Array or Float16Array cannot hold matches nothing (oven-sh/WebKit#604) - #42083

Closed
robobun wants to merge 2 commits into
mainfrom
robobun/b68345e0/typed-array-indexof-int32-needle
Closed

robobun wants to merge 2 commits into
mainfrom
robobun/b68345e0/typed-array-indexof-int32-needle

Conversation

@robobun

@robobun robobun commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Float32Array / Float16Array indexOf, lastIndexOf and includes match the element an integer needle rounds to when the element type cannot hold it: new Float32Array([16777216]).indexOf(16777217) is 0 and new Float16Array([Infinity]).includes(65520) is true. V8 and the spec (IsStrictlyEqual / SameValueZero on Numbers) say -1 / false.
  • Only an int32-encoded needle hits (a literal, parseInt, integer arithmetic). The same value read from a Float64Array already missed.
  • Cause: FloatTypedArrayAdaptor::toNativeFromInt32WithoutCoercion and Float16Adaptor::toNativeFromInt32WithoutCoercion in JavaScriptCore's runtime/TypedArrayAdaptors.h were a bare static_cast. The double path checks that the conversion round-trips. The int32 path did not.

Fix

Background

  • A typed array stores raw machine values. The search converts the needle to the element type once and compares raw values. That is only valid when the conversion is exact, otherwise no element can be equal.
  • A float has a 24-bit significand and a half 11 bits, so integers above 2^24 (2048 for a half) thin out, and a half rounds 65520 and above to infinity.
  • JSC holds a Number as an int32 or as a double. Which one a value gets is an engine detail, so both paths must agree.
Notes
  • Source: fuzz ledger #45462. The matrix that reproduced: Float16 × {2049, 65505, 65520, 65535, 65536, 2^24+1, 2^31-1} and Float32 × {2^24+1, 2^31-1} × all three methods, identical with the JITs off, so the C++ builtin. Impact as reported: f32ids.includes(id) is true for a neighbouring id once ids pass 2^24 (2048 for Float16), and f16.indexOf(65520) returns the position of an Infinity.
  • JSC-side verification on a jsc shell built from the branch: the new JSTests/stress/typed-array-index-of-int32-needle-not-representable.js (fails on the current pin's shell at the first case), the existing typedarray-indexOf.js, typedarray-includes.js, typedarray-lastIndexOf.js, non-suitable-typed-array-index-of.js stress files, and the 130 test262 files under built-ins/TypedArray/prototype/{indexOf,includes,lastIndexOf} (130 pass before and after, test262 has no case for this).
  • Upstream WebKit main has the same two functions.
  • The only callers of toNativeFromValueWithoutCoercion are the three search builtins in JSGenericTypedArrayViewPrototypeFunctions.h, and each already maps "not representable" to not found (with the existing special case for an undefined needle in includes). There is no DFG/FTL intrinsic for typed array search, only for Array.prototype.
  • While using node v26.3.0 as an oracle: V8 returns -1 for new Float16Array([-1]).indexOf(-1) and every other negative finite half. That is a V8 bug. JSC finds them before and after this change, and the new test keeps a -2048 case.
  • The ledger names two siblings, the Uint8ClampedArray double path (#45441) and the BigInt path (#45461). They are separate functions and not touched here.

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

test/js/bun/jsc/typed-array-indexof.test.ts:
(pass) integer needle that the element type cannot represent > Float32Array [14.59ms]
(pass) integer needle that the element type cannot represent > Float16Array [20.23ms]
(pass) integer needle that the element type cannot represent > Float64Array holds every int32 exactly [4.91ms]
(pass) integer needle that the element type cannot represent > integer arithmetic and parseInt results behave like the literal [2.97ms]
(pass) integer needle that the element type cannot represent > the same needle held as a double gives the same answer [6.66ms]

 5 pass
 0 fail
 41 expect() calls
Ran 5 tests across 1 file. [2.07s]
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                |  2 +-
 test/js/bun/jsc/typed-array-indexof.test.ts | 92 +++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+), 1 deletion(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                         reads  edits  tests
scripts/build/deps/webkit.ts                     2      2      4
test/js/bun/jsc/typed-array-indexof.test.ts      0      2      4

…ray cannot hold matches nothing (oven-sh/WebKit#604)

Pin WebKit to the preview build of oven-sh/WebKit#604 and add a test.
Float32Array / Float16Array indexOf, lastIndexOf and includes matched the
element an int32 needle rounds to (16777217 found 16777216, 65520 found
Infinity in a Float16Array, 2049 found 2048). The double-encoded needle
path already rejected a value the element type cannot represent.
@robobun

robobun commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:35 PM PT - Sep 8th, 2026

❌ @robobun, your commit 60888a0 has 2 failures in Build #113311 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 42083

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

bun-42083 --bun

@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: 18cddce2-136f-4dc0-9e75-e0d0335b88f6

📥 Commits

Reviewing files that changed from the base of the PR and between b5ba14b and 60888a0.

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

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


Walkthrough

The PR switches the selected WebKit build to an autobuild preview and adds regression tests for floating-point typed-array search methods.

Changes

Typed-array regression coverage

Layer / File(s) Summary
WebKit autobuild selection
scripts/build/deps/webkit.ts
WEBKIT_VERSION now uses the autobuild-preview-pr-604-a9c83095 build identifier.
Floating-point typed-array search tests
test/js/bun/jsc/typed-array-indexof.test.ts
Tests cover indexOf, lastIndexOf, and includes for Float16Array, Float32Array, and Float64Array, including exact and inexact integer representations.

Suggested reviewers: jarred-sumner

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 7a5a4

This updates the WebKit dependency and adds regression coverage for floating-point typed-array search behavior. No merge-blocking risk is currently identified.

🚥 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 search behavior being fixed for non-representable integer needles in Float32Array and Float16Array. It is specific and related to the main change.
Description check ✅ Passed The description provides detailed problem, fix, background, verification, and dependency-pin information. It does not use the template headings exactly, but it includes the required content under equi…

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

@robobun

robobun commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced on bun 1.4.3 (Linux x64) with:

const f32 = new Float32Array([16777216, 2147483648]);
console.log(f32.indexOf(16777217), f32.includes(2147483647), f32.indexOf(16777216.5 + 0.5)); // bun: 0 true 0    node v26.3.0: -1 false -1
const f16 = new Float16Array([Infinity, 2048, 65504]);
console.log(f16.indexOf(65520), f16.indexOf(2049), f16.includes(65505), f16.lastIndexOf(2 ** 31 - 1)); // bun: 0 1 true 0    node: -1 -1 false -1

USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/typed-array-indexof.test.ts fails 3 of 5 tests. bun bd test with the oven-sh/WebKit#604 preview pin passes all 5, and the snippet above prints the node values.

The engine fix is oven-sh/WebKit#604. This PR carries the test and the preview pin, and stays blocked until that PR merges. Then I move the pin to the autobuild-<sha> release of the merge commit.

The first CI run (build 113311) failed on every build lane with HTTP 404 for the WebKit tarballs: I pushed while the preview release was still a draft. The release is published now and the second run fetches it.

The automated review raised one point: the preview pin must not merge as is. That is already the plan above. The thread stays open as the merge blocker until oven-sh/WebKit#604 merges and the pin moves. No code change from the review.

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

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-604-a9c83095, an ephemeral preview release that is deleted once oven-sh/WebKit#604 merges or closes — after that every fresh build/CI without a cached tarball fails at the WebKit fetch (download.ts:323-332 already special-cases this exact failure). Fix: before merge, replace with the 40-hex merge-commit SHA and confirm the autobuild-<sha> release has every {os,arch,musl,debug|lto,asan} artifact prebuiltSuffix can request. The PR description already says "Do not merge before oven-sh/WebKit#604 lands"; this comment is the merge blocker that enforces it.

Extended reasoning...

REVIEW.md → Dependencies & vendoring: "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:323-332 documents that autobuild-preview-pr-* releases "only exist while the WebKit PR is open". If this PR merges as-is and the WebKit PR later merges (deleting the preview release), any dev or CI agent without webkit-preview-pr-604-a9c83095* already in cfg.cacheDir hits a 404 at prebuiltUrl() and the build aborts. On the base branch (2e2aa22…, a merged-commit SHA) the release is permanent and this cannot happen. The author's description acknowledges the hazard but the pin still needs to be swapped before merge — the description is not a code guard.

Verification: normal — acknowledged in diff: the PR description states "The pin is a preview tag. Do not merge before oven-sh/WebKit#604 lands. I move the pin to the autobuild-<sha> release of the merge commit then", which accurately describes the hazard but does not resolve it; the pin as committed is still ephemeral. scripts/build/deps/webkit.ts:6 changes WEBKIT_VERSION from the 40-hex SHA… | normal —…

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 that is the plan stated in the PR body. This pin is only here so CI can run the test against the engine fix before oven-sh/WebKit#604 merges. When it merges I push the 40-hex merge-commit sha here, after checking that its autobuild-<sha> release carries all 42 artifacts. I leave this thread open until then as the merge blocker.

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

Code review found no new issues

No new issues were found in this update; 1 finding from earlier reviews is still open above.

robobun added a commit that referenced this pull request Sep 9, 2026
…ows from #42083 and #42089

Adds the rows those two PRs pinned that this file did not have yet:
negative int32 needles and arithmetic results on Float32Array, the
65505..65519 and 65535 needles on Float16Array, more wrapped BigInt
needles (negative many-digit, -(2^64) +/- 1, -(2^63) - 1 on
BigUint64Array), and the same searches through a subarray, a
resizable buffer, a shared buffer, and with a fromIndex.
@robobun

robobun commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #42093, which pins the oven-sh/WebKit#609 preview. That change includes this fix (the Float32Array / Float16Array int32 needle path now goes through the checked double conversion) together with the BigInt64 / BigUint64 and Uint8Clamped search value conversions, and #42093 carries these test rows in test/js/bun/jsc/typed-array-search-needle.test.ts (7dbd53f).

@robobun robobun closed this Sep 9, 2026
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