blob: d9a029a018806f157a39aa16c9ad14d03e9e1935 [file] [edit]
// Regression test: iterating a JSStringIterator directly (i.e. the object returned by
// str[Symbol.iterator](), not a primitive string) must not be type-confused with a primitive JSString.
//
// getIterationMode() classifies a primitive string as IterationMode::FastString, and the synchronous
// op_iterator_open fast path builds a JSStringIterator from asString(iterable) on that assumption. If
// getIterationMode also returned FastString for an already-existing JSStringIterator cell, the sync path
// would asString() the iterator itself (an unchecked JSString downcast) and crash. A JSStringIterator is
// therefore left as Generic here (its fast-drive path is intentionally deferred to a separate change);
// this test only pins the invariant that iterating it directly yields the string's code points and never
// crashes, across sync for-of / spread / Array.from and the async-from-sync wrapper, warmed across tiers.
function assert(cond, message) {
if (!cond)
throw new Error("Assertion failed: " + message);
}
// Plain sync for-of over a string iterator object (this is the shape that crashed).
function forOfStringIterator(str) {
const out = [];
for (const c of str[Symbol.iterator]())
out.push(c);
return out.join(",");
}
// Spread of a string iterator (op_iterator_open + op_iterator_next fast path).
function spreadStringIterator(str) {
return [...str[Symbol.iterator]()].join(",");
}
// Array.from over a string iterator.
function arrayFromStringIterator(str) {
return Array.from(str[Symbol.iterator]()).join(",");
}
// A partially consumed string iterator: the fast open must REUSE the iterator (not rebuild it from
// scratch), so iteration continues from the current position rather than restarting.
function partiallyConsumed(str) {
const it = str[Symbol.iterator]();
it.next(); // drop the first code point
const out = [];
for (const c of it)
out.push(c);
return out.join(",");
}
let done = false;
let error = null;
// The async-from-sync wrapper drives the same JSStringIterator through the FastStringIterator mode.
async function asyncDriver() {
for (let i = 0; i < testLoopCount; i++) {
let out = [];
for await (const c of "abc"[Symbol.iterator]())
out.push(c);
assert(out.join(",") === "a,b,c", "for-await over string iterator: " + out.join(","));
// A primitive string still classifies as FastString and builds a fresh iterator.
out = [];
for await (const c of "xyz")
out.push(c);
assert(out.join(",") === "x,y,z", "for-await over primitive string: " + out.join(","));
}
done = true;
}
// Warm the sync paths across tiers.
for (let i = 0; i < testLoopCount; i++) {
assert(forOfStringIterator("abc") === "a,b,c", "for-of string iterator");
assert(spreadStringIterator("xyz") === "x,y,z", "spread string iterator");
assert(arrayFromStringIterator("hello") === "h,e,l,l,o", "Array.from string iterator");
assert(partiallyConsumed("abcd") === "b,c,d", "partially consumed string iterator");
assert(forOfStringIterator("") === "", "empty string iterator");
// Astral characters must iterate by code point (2 UTF-16 units), proving genuine string-iterator semantics.
assert(forOfStringIterator("a\u{1F600}b") === "a,\u{1F600},b", "astral code points");
}
asyncDriver().then(() => {}, (e) => { error = e; });
drainMicrotasks();
if (error)
throw error;
assert(done, "async driver did not complete");