From 208c9f9fe758ef9a4ba291b45f213dae26deedd6 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:26:09 +0000 Subject: [PATCH 1/3] BigInt64Array / BigUint64Array indexOf, lastIndexOf, includes: an out-of-range BigInt needle matches nothing (WebKit bump for oven-sh/WebKit#607) JSC converted the needle with ToBigInt64 / ToBigUint64, which wrap modulo 2^64, so new BigInt64Array([-1n]).includes(2n ** 64n - 1n) was true. Pin WebKit to the preview build of oven-sh/WebKit#607, which makes the conversion return no match for a BigInt the element type cannot represent, and add coverage. --- scripts/build/deps/webkit.ts | 2 +- .../jsc/bigint-typed-array-index-of.test.ts | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 test/js/bun/jsc/bigint-typed-array-index-of.test.ts diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 7e8ddecce2ac..c643c6824de6 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "2e2aa2290fac856d6f451ceacb58f7f5b44dd057"; +export const WEBKIT_VERSION = "autobuild-preview-pr-607-bb622e7b"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/js/bun/jsc/bigint-typed-array-index-of.test.ts b/test/js/bun/jsc/bigint-typed-array-index-of.test.ts new file mode 100644 index 000000000000..7e2145050cc7 --- /dev/null +++ b/test/js/bun/jsc/bigint-typed-array-index-of.test.ts @@ -0,0 +1,137 @@ +import { describe, expect, test } from "bun:test"; + +// BigInt64Array / BigUint64Array .indexOf, .lastIndexOf and .includes compare +// the needle with each element's BigInt value (IsStrictlyEqual / +// SameValueZero). A BigInt the element type cannot represent never matches. +// JSC used to convert the needle with a wrapping ToBigInt64 / ToBigUint64, so +// a needle congruent to an element modulo 2^64 was reported as found: +// `new BigInt64Array([-1n]).includes(2n ** 64n - 1n)` was true. + +const int64Max = 2n ** 63n - 1n; +const int64Min = -(2n ** 63n); +const uint64Max = 2n ** 64n - 1n; + +type Case = [elements: bigint[], needle: bigint, expectedIndex: number]; + +const int64Cases: Case[] = [ + [[3n, -1n, 7n], 2n ** 64n - 1n, -1], // wraps to -1n + [[3n, -1n, 7n], 2n ** 64n + 3n, -1], // wraps to 3n + [[3n, -1n, 7n], -(2n ** 64n) + 7n, -1], // wraps to 7n + [[3n, 0n, 7n], 2n ** 64n, -1], // wraps to 0n + [[3n, 0n, 7n], -(2n ** 64n), -1], // wraps to 0n + [[3n, 0n, 7n], 2n ** 128n, -1], // wraps to 0n + [[3n, int64Min, 7n], 2n ** 63n, -1], // wraps to -2^63 + [[3n, int64Max, 7n], int64Min - 1n, -1], // wraps to 2^63 - 1 + [[3n, 5n, 7n], 2n ** 200n + 5n, -1], // many digits, low 64 bits are 5 + [[3n, -5n, 7n], -(2n ** 200n) - 5n, -1], + // Representable needles still match, including the boundaries. + [[3n, -1n, 7n], -1n, 1], + [[3n, -1n, 7n], 3n, 0], + [[3n, -1n, 7n], 7n, 2], + [[3n, 0n, 7n], 0n, 1], + [[3n, int64Max, 7n], int64Max, 1], + [[3n, int64Min, 7n], int64Min, 1], + [[3n, 2n ** 32n, 7n], 2n ** 32n, 1], + [[3n, -(2n ** 32n), 7n], -(2n ** 32n), 1], + [[3n, -1n, 7n], 4n, -1], +]; + +const uint64Cases: Case[] = [ + [[3n, uint64Max, 7n], -1n, -1], // wraps to 2^64 - 1 + [[3n, int64Max, 7n], int64Min - 1n, -1], // wraps to 2^63 - 1 + [[3n, 2n ** 63n, 7n], int64Min, -1], // wraps to 2^63 + [[3n, 0n, 7n], 2n ** 64n, -1], // wraps to 0n + [[3n, 0n, 7n], -(2n ** 64n), -1], // wraps to 0n + [[3n, 0n, 7n], 2n ** 128n, -1], // wraps to 0n + [[3n, 5n, 7n], 2n ** 64n + 5n, -1], // wraps to 5n + [[3n, 5n, 7n], -(2n ** 64n) + 5n, -1], // wraps to 5n + [[3n, 5n, 7n], 2n ** 200n + 5n, -1], + [[3n, uint64Max - 4n, 7n], -5n, -1], // wraps to 2^64 - 5 + // Representable needles still match, including the boundaries. + [[3n, uint64Max, 7n], uint64Max, 1], + [[3n, 0n, 7n], 0n, 1], + [[3n, 2n ** 63n, 7n], 2n ** 63n, 1], + [[3n, int64Max, 7n], int64Max, 1], + [[3n, 2n ** 32n, 7n], 2n ** 32n, 1], + [[3n, 5n, 7n], 3n, 0], + [[3n, 5n, 7n], 7n, 2], + [[3n, 5n, 7n], 4n, -1], +]; + +type BigIntArrayConstructor = BigInt64ArrayConstructor | BigUint64ArrayConstructor; + +// The same elements seen through a plain view, a subarray, a length-tracking +// view of a resizable buffer, a fixed window of one, and a shared buffer. +function makeViews(constructor: BigIntArrayConstructor, elements: bigint[]) { + const byteLength = elements.length * 8; + + const plain = new constructor(elements); + + const padded = new constructor(elements.length + 2); + padded.set(elements, 1); + const subarray = padded.subarray(1, elements.length + 1); + + const resizable = new ArrayBuffer(byteLength, { maxByteLength: byteLength + 64 }); + const tracking = new constructor(resizable); + tracking.set(elements); + const fixedWindow = new constructor(resizable, 0, elements.length); + + const shared = new constructor(new SharedArrayBuffer(byteLength)); + shared.set(elements); + + return { plain, subarray, tracking, fixedWindow, shared }; +} + +function search(array: BigInt64Array | BigUint64Array, needle: bigint) { + return { + indexOf: array.indexOf(needle), + lastIndexOf: array.lastIndexOf(needle), + includes: array.includes(needle), + indexOfFrom1: array.indexOf(needle, 1), + lastIndexOfFromMinus2: array.lastIndexOf(needle, -2), + includesFromMinus1: array.includes(needle, -1), + // The generic Array.prototype path was always correct. It is the reference. + arrayIndexOf: Array.prototype.indexOf.call(array, needle), + arrayIncludes: Array.prototype.includes.call(array, needle), + }; +} + +function expected(length: number, index: number) { + return { + indexOf: index, + lastIndexOf: index, + includes: index !== -1, + indexOfFrom1: index >= 1 ? index : -1, + lastIndexOfFromMinus2: index <= length - 2 ? index : -1, + includesFromMinus1: index === length - 1, + arrayIndexOf: index, + arrayIncludes: index !== -1, + }; +} + +describe.each([ + ["BigInt64Array", BigInt64Array, int64Cases], + ["BigUint64Array", BigUint64Array, uint64Cases], +] as const)("%s indexOf / lastIndexOf / includes", (_name, constructor, cases) => { + test.each(cases.map(([elements, needle, index]) => [`${needle}n`, `[${elements.join("n, ")}n]`, elements, needle, index] as const))( + "needle %s in %s", + (_needle, _elements, elements, needle, index) => { + for (const [kind, view] of Object.entries(makeViews(constructor, elements))) { + expect({ kind, ...search(view, needle) }).toEqual({ kind, ...expected(elements.length, index) }); + } + }, + ); +}); + +test("a Number needle never matches a BigInt element and a BigInt needle never matches a Number element", () => { + const i64 = new BigInt64Array([0n, 1n, -1n]); + expect(i64.indexOf(1 as any)).toBe(-1); + expect(i64.includes(-1 as any)).toBe(false); + expect(i64.lastIndexOf("1" as any)).toBe(-1); + const u64 = new BigUint64Array([0n, 1n, uint64Max]); + expect(u64.indexOf((2 ** 64) as any)).toBe(-1); + expect(u64.includes(18446744073709551615 as any)).toBe(false); + const f64 = new Float64Array([0, 1, 2 ** 64]); + expect(f64.indexOf((2n ** 64n) as any)).toBe(-1); + expect(f64.includes(1n as any)).toBe(false); +}); From 960137cb7ffdfced231b96d958b1da9f73122d8b Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:13:51 +0000 Subject: [PATCH 2/3] [autofix.ci] apply automated fixes --- .../bun/jsc/bigint-typed-array-index-of.test.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/js/bun/jsc/bigint-typed-array-index-of.test.ts b/test/js/bun/jsc/bigint-typed-array-index-of.test.ts index 7e2145050cc7..275f6807af66 100644 --- a/test/js/bun/jsc/bigint-typed-array-index-of.test.ts +++ b/test/js/bun/jsc/bigint-typed-array-index-of.test.ts @@ -113,14 +113,15 @@ describe.each([ ["BigInt64Array", BigInt64Array, int64Cases], ["BigUint64Array", BigUint64Array, uint64Cases], ] as const)("%s indexOf / lastIndexOf / includes", (_name, constructor, cases) => { - test.each(cases.map(([elements, needle, index]) => [`${needle}n`, `[${elements.join("n, ")}n]`, elements, needle, index] as const))( - "needle %s in %s", - (_needle, _elements, elements, needle, index) => { - for (const [kind, view] of Object.entries(makeViews(constructor, elements))) { - expect({ kind, ...search(view, needle) }).toEqual({ kind, ...expected(elements.length, index) }); - } - }, - ); + test.each( + cases.map( + ([elements, needle, index]) => [`${needle}n`, `[${elements.join("n, ")}n]`, elements, needle, index] as const, + ), + )("needle %s in %s", (_needle, _elements, elements, needle, index) => { + for (const [kind, view] of Object.entries(makeViews(constructor, elements))) { + expect({ kind, ...search(view, needle) }).toEqual({ kind, ...expected(elements.length, index) }); + } + }); }); test("a Number needle never matches a BigInt element and a BigInt needle never matches a Number element", () => { From 976d77c0f635547e821676ecdf30f4babd4208fd Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:18:41 +0000 Subject: [PATCH 3/3] test: cover a repeated element so lastIndexOf must find the last match --- .../jsc/bigint-typed-array-index-of.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/js/bun/jsc/bigint-typed-array-index-of.test.ts b/test/js/bun/jsc/bigint-typed-array-index-of.test.ts index 275f6807af66..a604dbe00373 100644 --- a/test/js/bun/jsc/bigint-typed-array-index-of.test.ts +++ b/test/js/bun/jsc/bigint-typed-array-index-of.test.ts @@ -122,6 +122,32 @@ describe.each([ expect({ kind, ...search(view, needle) }).toEqual({ kind, ...expected(elements.length, index) }); } }); + + // The rows above hold each value once, so indexOf and lastIndexOf agree there. + test("a repeated element: indexOf finds the first match, lastIndexOf the last, a wrapped needle neither", () => { + const wrapped = 7n + 2n ** 64n; + for (const [kind, view] of Object.entries(makeViews(constructor, [7n, 3n, 7n, 7n, 3n]))) { + expect({ + kind, + indexOf: view.indexOf(7n), + lastIndexOf: view.lastIndexOf(7n), + indexOfFrom1: view.indexOf(7n, 1), + lastIndexOfFromMinus3: view.lastIndexOf(7n, -3), + wrappedIndexOf: view.indexOf(wrapped), + wrappedLastIndexOf: view.lastIndexOf(wrapped), + wrappedIncludes: view.includes(wrapped), + }).toEqual({ + kind, + indexOf: 0, + lastIndexOf: 3, + indexOfFrom1: 2, + lastIndexOfFromMinus3: 2, + wrappedIndexOf: -1, + wrappedLastIndexOf: -1, + wrappedIncludes: false, + }); + } + }); }); test("a Number needle never matches a BigInt element and a BigInt needle never matches a Number element", () => {