JavaScript: shrinking an array takes fuel for each property

Assigning a smaller length to an array visits every property in one operation. It now takes fuel for each one before anything is removed, so a run with a small fuel limit or a set interrupt flag stops there and leaves the array unchanged.
This commit is contained in:
dotCooCoo
2026-11-07 15:24:53 -08:00
parent 44c17c4e9a
commit 5ebf32e973
2 changed files with 23 additions and 4 deletions
+11 -4
View File
@@ -1061,12 +1061,19 @@ impl Interp {
if f64::from(new) != n {
return Err(self.throw(ErrorType::Range, "invalid array length"));
}
let o = self.object_mut(id)?;
let Class::Array { length, length_writable } = &mut o.class else { return internal("not an array") };
if !*length_writable {
let o = self.object(id)?;
let Class::Array { length: old, length_writable } = o.class else { return internal("not an array") };
if !length_writable {
return self.reject(throw, &key("length"), "it is read-only");
}
let old = *length;
if new < old {
// Shrinking visits every property: each takes fuel before anything is removed.
for _ in 0..o.props.len() {
self.use_fuel()?;
}
}
let o = self.object_mut(id)?;
let Class::Array { length, .. } = &mut o.class else { return internal("not an array") };
*length = new;
if new < old {
let doomed: Vec<Key> = o.props.keys().into_iter().filter(|k| array_index(k).is_some_and(|i| i >= new)).collect();
+12
View File
@@ -374,6 +374,18 @@ fn for_in_names_take_fuel_and_heap_under_later_limits() {
assert!(realm.interp().heap.bytes <= heap_bytes, "{} > {heap_bytes}", realm.interp().heap.bytes);
}
#[test]
fn shrinking_an_array_takes_fuel_for_each_property() {
let mut realm = small(Limits::default());
assert_eq!(realm.run("var a = []; for (var i = 0; i < 100000; i++) a[i] = i; a.length"), Ok(JsValue::Number(100_000.0)));
realm.set_limits(Limits { fuel: 1000, ..Limits::default() });
assert_eq!(limit(&mut realm, "a.length = 0;"), LimitKind::Fuel);
realm.set_limits(Limits::default());
// The run stopped before anything was removed.
assert_eq!(realm.run("a.length + a[99999]"), Ok(JsValue::Number(199_999.0)));
assert_eq!(realm.run("a.length = 1; a.length + a[0] + (99999 in a ? 1 : 0)"), Ok(JsValue::Number(1.0)));
}
#[test]
fn declarations_check_the_heap_before_they_are_bound() {
// Each script declares more than fits in the heap. The run ends with the heap limit before