Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "549170099226f816a4b204ea1d8fa102fb79eefa";
export const WEBKIT_VERSION = "autobuild-preview-pr-348-c106f67d";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
146 changes: 146 additions & 0 deletions test/js/bun/jsc/typedarray-uint32max-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

// A Uint8Array can have length up to MAX_ARRAY_BUFFER_SIZE (2**32 on 64-bit),
// so 4294967295 (UINT32_MAX) is a valid index. Regular JS arrays top out at
// MAX_ARRAY_INDEX (2**32 - 2), and the TypedArray [[Get]]/[[Set]] paths were
// reusing that cap, which left the last element unreachable via bracket access
// while [[HasProperty]] and DataView could still see it.
// https://tc39.es/ecma262/#sec-isvalidintegerindex

const fixture = /* js */ `
"use strict";
const LEN = 2 ** 32;
const IDX = LEN - 1; // 4294967295
const KEY = String(IDX); // "4294967295"

let u;
try {
u = new Uint8Array(LEN);
} catch {
console.log(JSON.stringify({ skipped: true }));
process.exit(0);
}
if (u.length !== LEN) throw new Error("unexpected length " + u.length);

// Seed the last byte via a path that was never broken.
new DataView(u.buffer).setUint8(IDX, 11);

const out = {
initialGetNum: u[IDX],
initialGetStr: u[KEY],
at: u.at(IDX),
inNum: IDX in u,
inStr: KEY in u,
hasOwn: Object.hasOwn(u, KEY),
gopd: Object.getOwnPropertyDescriptor(u, KEY),
};

u[IDX] = 22;
out.afterIndexedSet = u[IDX];
out.afterIndexedSetDV = new DataView(u.buffer).getUint8(IDX);

Reflect.set(u, KEY, 33);
out.afterReflectSet = u[IDX];
out.afterReflectSetDV = new DataView(u.buffer).getUint8(IDX);

try {
Object.defineProperty(u, KEY, { value: 44, writable: true, enumerable: true, configurable: true });
out.afterDefine = new DataView(u.buffer).getUint8(IDX);
} catch (e) {
out.afterDefine = "threw: " + e.constructor.name;
}

out.deleteInBounds = Reflect.deleteProperty(u, KEY);

// Keep matching spec for adjacent cases:
out.oneBeyond = u["4294967296"];
out.minusZero = u["-0"];

// On a short view, the same index is out of bounds.
const small = new Uint8Array(8);
out.smallGet = small[IDX];
out.smallIn = KEY in small;
out.smallDelete = Reflect.deleteProperty(small, KEY);
let smallDefineThrew = false;
try {
Object.defineProperty(small, KEY, { value: 1, writable: true, enumerable: true, configurable: true });
} catch {
smallDefineThrew = true;
}
out.smallDefineThrew = smallDefineThrew;

// Other 1-byte element types at max length.
const i8 = new Int8Array(u.buffer);
i8[IDX] = -7;
out.i8 = i8[IDX];
const uc = new Uint8ClampedArray(u.buffer);
uc[IDX] = 300;
out.uc = uc[IDX];

// Structure-keyed caches (HasOwnPropertyCache, GetBy/InBy IC miss entries)
// must not be keyed on the shared TypedArray structure for this index, since
// the answer is length-dependent. Allocate both views first so GC between the
// two probes doesn't hide a stale entry by clearing the cache.
const icSmall = new Uint8Array(u.buffer, 0, 8);
new DataView(u.buffer).setUint8(IDX, 55);
out.hasOwnSmallFirst = Object.hasOwn(icSmall, KEY);
out.hasOwnBigAfter = Object.hasOwn(u, KEY);
function probeGet(x) { return x["4294967295"]; }
function probeIn(x) { return "4294967295" in x; }
for (let i = 0; i < 200; i++) { probeGet(icSmall); probeIn(icSmall); }
out.icGetBig = probeGet(u);
out.icInBig = probeIn(u);

console.log(JSON.stringify(out));
`;

test("TypedArray indexed access at 4294967295 on a 2**32-length view", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", fixture],
env: {
...bunEnv,
ASAN_OPTIONS: [bunEnv.ASAN_OPTIONS, "allocator_may_return_null=1"].filter(Boolean).join(":"),
},
stdout: "pipe",
stderr: "pipe",
Comment thread
robobun marked this conversation as resolved.
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr).toBe("");
const out = JSON.parse(stdout.trim());
if (out.skipped) {
console.log("skipping: could not allocate a 2**32-byte Uint8Array");
expect(exitCode).toBe(0);
return;
}

expect(out).toEqual({
initialGetNum: 11,
initialGetStr: 11,
at: 11,
inNum: true,
inStr: true,
hasOwn: true,
gopd: { value: 11, writable: true, enumerable: true, configurable: true },
afterIndexedSet: 22,
afterIndexedSetDV: 22,
afterReflectSet: 33,
afterReflectSetDV: 33,
afterDefine: 44,
deleteInBounds: false,
oneBeyond: undefined,
minusZero: undefined,
smallGet: undefined,
smallIn: false,
smallDelete: true,
smallDefineThrew: true,
i8: -7,
uc: 255,
hasOwnSmallFirst: false,
hasOwnBigAfter: true,
icGetBig: 55,
icInBig: true,
});
expect(exitCode).toBe(0);
});
Loading