Files
scriptc/tests/corpus/array-join-separator-reads-length-first.js
T
Chris Tate 55a803c733 Read the array's length before converting a join separator
Array.prototype.join reads the length before it converts the separator.
The lowering converted the separator first and let the join read the
length afterwards, so a separator whose toString changed the array joined
the wrong number of elements. When ToString of the separator value can run
code of the program (a checked-dynamic value, an object or class instance
with toString), the join now reads the length first: an array that grew
joins its first elements, and the missing elements of one that shrank join
as empty strings. Primitive separators keep the direct join.
2026-10-28 13:07:14 -05:00

29 lines
1.1 KiB
JavaScript

// Array.prototype.join reads the length before it converts the separator:
// a separator whose toString changes the array joins exactly that many
// elements, and elements it removed join as empty strings. Object literal,
// class instance and untyped separators.
function joinChanging(change) {
const values = ["a", "b", "c"];
const sep = {
toString() {
change(values);
return "-";
},
};
return values.join(sep);
}
console.log(joinChanging((values) => { values.push("d"); }));
console.log(joinChanging((values) => { values.length = 1; }));
console.log(joinChanging((values) => { values.length = 0; }));
console.log(joinChanging((values) => { values[1] = "B"; }));
console.log(joinChanging(() => {}));
class Sep {
constructor(values) { this.values = values; }
toString() { this.values.length = 2; return "+"; }
}
const shrinking = ["x", "y", "z"];
console.log(shrinking.join(new Sep(shrinking)));
function untyped(values, sep) { return values.join(sep); }
const grow = ["p", "q"];
console.log(untyped(grow, { toString() { grow.push("r"); return "/"; } }));