blob: 816a7deb096b17a9488d46e53668bc0a4d33dd65 [file] [edit]
// Regression test for the JIT-inlined MapIteratorNext / SetIteratorNext path
// and the C++ helper transitAndNext.
//
// JSOrderedHashTable's DataTable is sized to its load-factor cap (DataCapacity)
// rather than the bucket count, so a Map/Set populated to exactly DataCapacity
// has no spare zero-initialized slot inside the DataTable past the last entry.
// The iterator (both the JIT-inlined fast path and the C++ helper) terminates
// when it loads an empty JSValue from the slot one past the last alive entry;
// without a trailing IterationSentinel slot, that load would run off the end
// of the storage and read whatever happens to be in adjacent memory, which can
// either yield bogus extra entries, loop forever on a stray deleted-marker, or
// crash. ASan reliably catches the OOB read; in normal release builds the bug
// surfaces only when adjacent memory happens to hold a non-zero value, so this
// test exercises the path but is not a deterministic detector on its own.
//
// The sizes packed below are exactly DataCapacity at the small-capacity
// regime, where DataCapacity = capacity / 2. If the dataCapacity formula in
// JSOrderedHashTableHelper ever changes, regenerate these values.
// capacity 8 -> DataCapacity 4
// capacity 32 -> DataCapacity 16
// capacity 128 -> DataCapacity 64
function assert(b, msg) {
if (!b)
throw new Error("FAIL: " + msg);
}
noInline(assert);
const fullSizes = [4, 16, 64];
function buildMap(n) {
const m = new Map();
for (let i = 0; i < n; ++i)
m.set(i, i * 2);
return m;
}
function buildSet(n) {
const s = new Set();
for (let i = 0; i < n; ++i)
s.add(i);
return s;
}
function drainMap(m) {
const it = m[Symbol.iterator]();
let count = 0;
let sumK = 0;
let sumV = 0;
while (true) {
const { value, done } = it.next();
if (done) {
assert(value === undefined, "done iterator value");
break;
}
sumK += value[0];
sumV += value[1];
++count;
}
// One additional next() after done must keep returning done with no error.
const after = it.next();
assert(after.done === true, "post-done iterator");
return [count, sumK, sumV];
}
noInline(drainMap);
function drainSet(s) {
const it = s[Symbol.iterator]();
let count = 0;
let sum = 0;
while (true) {
const { value, done } = it.next();
if (done) {
assert(value === undefined, "done iterator value");
break;
}
sum += value;
++count;
}
const after = it.next();
assert(after.done === true, "post-done iterator");
return [count, sum];
}
noInline(drainSet);
for (const n of fullSizes) {
const map = buildMap(n);
const set = buildSet(n);
const expectedSumK = (n * (n - 1)) / 2;
const expectedSumV = expectedSumK * 2;
const expectedSetSum = expectedSumK;
for (let i = 0; i < testLoopCount; ++i) {
const [c, sk, sv] = drainMap(map);
assert(c === n, "map count " + n);
assert(sk === expectedSumK, "map sumK " + n);
assert(sv === expectedSumV, "map sumV " + n);
const [sc, ss] = drainSet(set);
assert(sc === n, "set count " + n);
assert(ss === expectedSetSum, "set sum " + n);
}
}