Read only value slots in lastIndexOf with an element-typed needle

lastIndexOf with a needle of the element type read every non-hole index
as the element type. An undefined or foreign element written through a
dynamic view (or a hole that toSorted turned into undefined) has no
element-typed payload, so the read aborted with "array index N is
undefined". The search now reads an index only when it holds a value,
and skips undefined and foreign elements, as Node's strict equality
does for a string or number needle.
This commit is contained in:
Chris Tate
2026-10-24 16:19:16 -05:00
parent 73389fd513
commit a538ff42f0
2 changed files with 44 additions and 11 deletions
@@ -1788,17 +1788,27 @@ function arraySearchHelper(
},
loc,
};
const visitPresent: IrStmt[] = [
{ kind: "varDecl", localId: "value.0", init: value, loc },
{
kind: "if",
cond: match,
then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }],
else_: null,
loc,
},
advance,
];
const found: IrStmt = {
kind: "if",
cond: directNeedle ? equal : match,
then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }],
else_: null,
loc,
};
// A direct needle reads only value slots: an undefined or foreign
// element written through a dynamic view has no element-typed payload.
const visitPresent: IrStmt[] = directNeedle
? [
{
kind: "if",
cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
then: [{ kind: "varDecl", localId: "value.0", init: value, loc }, found],
else_: null,
loc,
},
advance,
]
: [{ kind: "varDecl", localId: "value.0", init: value, loc }, found, advance];
const skipHole: IrStmt[] =
method === "lastIndexOf"
? [advance]
@@ -177,3 +177,26 @@ holderView.push({
},
});
console.log(JSON.stringify(holder), JSON.stringify(holder));
// toSorted keeps the full length and reads holes as undefined; indexOf,
// lastIndexOf and includes skip the undefined and foreign elements.
const searched: string[] = ["b", "a"];
const searchedView: unknown[] = searched;
searchedView[9] = 4;
searchedView[5] = undefined;
console.log(searched.indexOf("b"), searched.lastIndexOf("b"), searched.lastIndexOf("a", 8));
console.log(searched.includes("a"), searched.lastIndexOf("z"));
const searchedSorted = searched.toSorted();
console.log(searchedSorted.length, searchedSorted.slice(0, 4), searchedSorted.lastIndexOf("b"));
console.log(searchedSorted.indexOf("a"), searchedSorted.includes("b"));
const counts: number[] = [3, 1];
const countsView: unknown[] = counts;
countsView[6] = "x";
countsView[4] = 2;
const countsSorted = counts.toSorted();
console.log(countsSorted.length, countsSorted, countsSorted.lastIndexOf(3), counts.lastIndexOf(2));
const far: number[] = [5];
const farView: unknown[] = far;
farView[70000] = "z";
const farSorted = far.toSorted();
console.log(farSorted.length, farSorted.slice(0, 3), farSorted.slice(69999), farSorted.lastIndexOf(5));