JavaScript: join with an empty separator does not walk the holes of a sparse array

Array.prototype.join wrote the separator once per index between the
elements it found, so new Array(4294967295).join("") ran four billion
empty writes without taking fuel. Separators are now written in one step
per gap, and an empty separator writes nothing.
This commit is contained in:
dotCooCoo
2026-11-07 17:38:17 -08:00
parent 3e09521b1f
commit d21ab8f4e2
3 changed files with 18 additions and 6 deletions
+14
View File
@@ -227,6 +227,20 @@ impl Builder {
Ok(())
}
/// Appends `units` `count` times. Nothing is written for an empty `units`, however large
/// `count` is.
fn push_repeated(&mut self, interp: &mut Interp, units: &[u16], count: u64) -> JsResult<()> {
if units.is_empty() || count == 0 {
return Ok(());
}
let more = usize::try_from(count).unwrap_or(usize::MAX).saturating_mul(units.len());
self.make_room(interp, more)?;
for _ in 0..count {
self.units.extend_from_slice(units);
}
Ok(())
}
fn push_str(&mut self, interp: &mut Interp, s: &str) -> JsResult<()> {
let units: Vec<u16> = s.encode_utf16().collect();
self.push_units(interp, &units)
+2 -6
View File
@@ -291,15 +291,11 @@ fn join_elements(interp: &mut Interp, o: ObjId, len: u32, sep: &[u16], item: fn(
while let Some(k) = it.next(interp)? {
let v = get_at(interp, o, k as f64)?;
let Some(s) = item(interp, v)? else { continue };
for _ in seps..k {
out.push_units(interp, sep)?;
}
out.push_repeated(interp, sep, k.saturating_sub(seps))?;
seps = k;
out.push_units(interp, &s.flat())?;
}
for _ in seps..u64::from(len - 1) {
out.push_units(interp, sep)?;
}
out.push_repeated(interp, sep, u64::from(len - 1).saturating_sub(seps))?;
Ok(out.finish())
}
+2
View File
@@ -231,6 +231,8 @@ fn sparse_arrays_of_any_length_cost_only_their_elements() {
let cases = [
("var a = new Array(4294967295); a.join('')", JsValue::String(String::new())),
("var a = []; a[4294967294] = 'z'; a[3] = 'y'; a.join('').length", JsValue::Number(2.0)),
// An empty separator is not written once per hole, so a thousand such joins finish.
("var b = []; b[4294967294] = 'z'; for (var i = 0; i < 1000; i++) new Array(4294967295).join('') + b.join(''); i", JsValue::Number(1000.0)),
("var a = []; a[4294967294] = 2; a[7] = 1; a.sort(); a[0] * 10 + a[1] + (a.length === 4294967295 ? 0 : 100)", JsValue::Number(12.0)),
("var a = []; a[4294967294] = 1; a.indexOf(1)", JsValue::Number(4_294_967_294.0)),
("var a = []; a[4294967294] = 1; a.lastIndexOf(1, -1) + a.lastIndexOf(2)", JsValue::Number(4_294_967_293.0)),