diff --git a/crates/js/src/builtins.rs b/crates/js/src/builtins.rs index 7016e690d..40131ef07 100644 --- a/crates/js/src/builtins.rs +++ b/crates/js/src/builtins.rs @@ -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 = s.encode_utf16().collect(); self.push_units(interp, &units) diff --git a/crates/js/src/builtins/array.rs b/crates/js/src/builtins/array.rs index 1361f5599..ad034c64e 100644 --- a/crates/js/src/builtins/array.rs +++ b/crates/js/src/builtins/array.rs @@ -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()) } diff --git a/crates/js/src/builtins_tests.rs b/crates/js/src/builtins_tests.rs index 749454756..f031c70dc 100644 --- a/crates/js/src/builtins_tests.rs +++ b/crates/js/src/builtins_tests.rs @@ -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)),