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
15 changes: 15 additions & 0 deletions JSTests/microbenchmarks/frozen-array-read.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Reads from many small frozen arrays (oven-sh/bun#44305).
const N = 20000, LEN = 16, PASSES = 50;
const arrays = Array.from({ length: N }, (_, i) => Object.freeze(Array.from({ length: LEN }, (_, j) => (i + j) & 0xffff)));
function sum(arrs) {
let s = 0;
for (let p = 0; p < PASSES; p++)
for (const a of arrs)
for (let j = 0; j < a.length; j++)
s += a[j];
return s;
}
noInline(sum);
const result = sum(arrays);
if (result <= 0)
throw new Error("bad sum " + result);
10 changes: 10 additions & 0 deletions JSTests/microbenchmarks/object-seal-freeze-large-array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
for (let i = 0; i < 2_000; i++) {
let a = new Array(i);
a.fill(i);
Object.seal(a);
}
for (let i = 0; i < 2_000; i++) {
let a = new Array(i);
a.fill(i);
Object.freeze(a);
}
85 changes: 85 additions & 0 deletions JSTests/stress/freeze-array-prototype.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
function assert(cond, msg) {
if (!cond)
throw new Error("FAIL: " + msg);
}

function throwsTypeError(f) {
try {
f();
} catch (e) {
return e instanceof TypeError;
}
return false;
}

function strictSetLength(o, v) { "use strict"; o.length = v; }
function strictSetIndex(o, i, v) { "use strict"; o[i] = v; }

function readHoles(n) {
const holey = [1, , 3, , 5];
let undefs = 0;
for (let i = 0; i < n; i++) {
if (holey[i % 5] === undefined)
undefs++;
}
return undefs;
}
assert(readHoles(1e4) === 4e3, "holes before freeze");

const AP = Array.prototype;
Object.freeze(AP);

assert(Object.isFrozen(AP), "isFrozen");
assert(!Object.isExtensible(AP), "not extensible");
const lengthDesc = Object.getOwnPropertyDescriptor(AP, "length");
assert(lengthDesc.value === 0 && !lengthDesc.writable && !lengthDesc.configurable && !lengthDesc.enumerable, "length descriptor");
assert(Object.getOwnPropertyDescriptor(AP, "push").writable === false, "method read-only");

assert(throwsTypeError(() => strictSetLength(AP, 1)), "strict length write throws");
AP.length = 1;
assert(AP.length === 0, "sloppy length write ignored");
assert(throwsTypeError(() => strictSetLength(AP, 0)), "strict same-value length write throws");
assert(Reflect.set(AP, "length", 0) === false, "Reflect.set length");
assert(Reflect.defineProperty(AP, "length", { value: 0 }) === true, "same-value define length");
assert(Reflect.defineProperty(AP, "length", { value: 1 }) === false, "different-value define length");

assert(throwsTypeError(() => strictSetIndex(AP, 0, 1)), "strict index write throws");
AP[0] = 1;
assert(AP[0] === undefined && !Object.hasOwn(AP, 0), "sloppy index write ignored");
assert(throwsTypeError(() => Object.defineProperty(AP, 0, { value: 1 })), "define index throws");
assert(Reflect.defineProperty(AP, 3, { value: 1 }) === false, "Reflect.defineProperty index");

assert(throwsTypeError(() => AP.push.call(AP, 1)), "push throws");
assert(throwsTypeError(() => AP.push.call(AP)), "push with no args throws");
assert(throwsTypeError(() => AP.pop.call(AP)), "pop throws");
assert(throwsTypeError(() => AP.shift.call(AP)), "shift throws");
assert(throwsTypeError(() => AP.unshift.call(AP, 1)), "unshift throws");
assert(throwsTypeError(() => AP.unshift.call(AP)), "unshift no args throws");
assert(throwsTypeError(() => AP.splice.call(AP, 0, 0, 1)), "splice insert throws");
assert(AP.length === 0 && Object.getOwnPropertyNames(AP).every(k => isNaN(+k) || k === ""), "no indexed props leaked");

for (let i = 0; i < 1e4; i++) {
assert(throwsTypeError(() => AP.push.call(AP, i)), "push throws (warm)");
assert(throwsTypeError(() => AP.pop.call(AP)), "pop throws (warm)");
assert(throwsTypeError(() => strictSetLength(AP, i)), "length write throws (warm)");
}

assert(readHoles(1e4) === 4e3, "holes after freeze");
assert([1, , 3].includes(undefined) && [, 2].indexOf(undefined) === -1, "includes/indexOf holes");
assert([...[1, , 3]].length === 3 && [...[1, , 3]][1] === undefined, "spread holes");
assert([1, , 3].slice(0)[1] === undefined && !(1 in [1, , 3].slice(0)), "slice holes");

const frozenEmpty = Object.freeze([]);
assert(throwsTypeError(() => frozenEmpty.push(1)) && throwsTypeError(() => frozenEmpty.pop()), "frozen empty literal");
const frozenNewArray = Object.freeze(new Array());
assert(throwsTypeError(() => frozenNewArray.push(1)) && throwsTypeError(() => frozenNewArray.pop()), "frozen new Array()");
assert(throwsTypeError(() => strictSetLength(frozenNewArray, 0)), "frozen new Array() length");
assert(Object.isFrozen(frozenNewArray) && frozenNewArray.length === 0, "frozen new Array() state");

const sealed = Object.seal(new Array());
assert(throwsTypeError(() => sealed.push(1)), "sealed empty push throws");
sealed.length = 5;
assert(sealed.length === 5 && !(0 in sealed), "sealed empty length writable");

if (typeof $vm !== "undefined" && !$vm.isHavingABadTime(AP))
assert($vm.indexingMode(AP) === "ArrayClass", "frozen Array.prototype keeps blank indexing after rejected writes: " + $vm.indexingMode(AP));
18 changes: 18 additions & 0 deletions JSTests/stress/frozen-array-allocation-profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//@ runDefault("--useConcurrentJIT=false", "--useDFGJIT=false", "--collectContinuously=true", "--useGenerationalGC=false", "--thresholdForJITAfterWarmUp=10")

// Object.freeze switches an array to the SlowPutArrayStorage shape before it transitions the
// structure. A collection that ends inside that window updates the allocation profile of the site
// that created the array; the site must not start allocating SlowPutArrayStorage arrays.

function build(n) { let a = []; for (let i = 0; i < n; ++i) a.push(i); return a; }
noInline(build);

for (let i = 0; i < 20000; ++i) {
let frozen = build(8);
Object.freeze(frozen);
let next = build(8);
if ($vm.indexingMode(next) === "ArrayWithSlowPutArrayStorage" && !$vm.isHavingABadTime(next))
throw new Error("iteration " + i + ": the site allocates SlowPutArrayStorage arrays");
next[0] = 1;
next.push(9);
}
Loading
Loading