mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 01:21:59 +00:00
JavaScript: for-in takes fuel for each property and reserves heap for its names
Listing the names for a for-in loop now takes a unit of fuel for each property it visits and reserves heap for the name list before it is kept, so a run with small limits stops on an object built under larger ones.
This commit is contained in:
+11
-4
@@ -1388,7 +1388,7 @@ impl Interp {
|
||||
/// that an object nearer the start of the chain has (enumerable or not).
|
||||
///
|
||||
/// For a String object, the heap budget for all its index names is checked before any is
|
||||
/// built, and each name takes a unit of fuel.
|
||||
/// built. Each index name and each property visited takes a unit of fuel.
|
||||
fn enumerable_keys(&mut self, id: ObjId) -> JsResult<Vec<Key>> {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut out = Vec::new();
|
||||
@@ -1411,6 +1411,9 @@ impl Interp {
|
||||
if is_array {
|
||||
seen.insert(key("length"));
|
||||
}
|
||||
for _ in 0..self.object(o)?.props.len() {
|
||||
self.use_fuel()?;
|
||||
}
|
||||
let obj = self.object(o)?;
|
||||
for (k, p) in obj.props.iter() {
|
||||
if seen.insert(k.clone()) && p.enumerable() {
|
||||
@@ -1429,14 +1432,18 @@ impl Interp {
|
||||
} else {
|
||||
let o = self.to_object(&v)?;
|
||||
// A wrapper made by ToObject is not on the stack; a collection while the names are
|
||||
// listed must not free it.
|
||||
// listed or their bytes reserved must not free it.
|
||||
let saved = self.temp_roots.len();
|
||||
self.temp_roots.push(Value::Object(o));
|
||||
let keys = self.enumerable_keys(o);
|
||||
let keys = self.enumerable_keys(o).and_then(|keys| {
|
||||
let bytes = keys.iter().map(|k| string_bytes(k.len())).sum::<usize>();
|
||||
self.reserve_heap(bytes)?;
|
||||
self.heap.bytes = self.heap.bytes.saturating_add(bytes);
|
||||
Ok(keys)
|
||||
});
|
||||
self.temp_roots.truncate(saved);
|
||||
(keys?, Some(o))
|
||||
};
|
||||
self.heap.bytes += keys.iter().map(|k| string_bytes(k.len())).sum::<usize>();
|
||||
let iter = self.heap.alloc_object(Object::new(Class::ForIn(Box::new(ForIn { keys, pos: 0, object })), None));
|
||||
self.replace(1, Value::Object(iter))
|
||||
}
|
||||
|
||||
@@ -352,6 +352,28 @@ fn function_source_text_counts_toward_the_heap() {
|
||||
assert_eq!(realm.run("(function f(a) { return a; }).toString()"), Ok(JsValue::String("function f(a) { return a; }".into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_names_take_fuel_and_heap_under_later_limits() {
|
||||
// The first run, under the default limits, builds an object with many properties. Later runs
|
||||
// with smaller limits list its names only within their own fuel and heap.
|
||||
let mut realm = small(Limits::default());
|
||||
assert_eq!(realm.run("var o = {}; for (var i = 0; i < 100000; i++) o['k' + i] = i; o.k99999"), Ok(JsValue::Number(99999.0)));
|
||||
realm.set_limits(Limits { fuel: 1000, ..Limits::default() });
|
||||
assert_eq!(limit(&mut realm, "for (var k in o) break;"), LimitKind::Fuel);
|
||||
realm.set_limits(Limits::default());
|
||||
assert_eq!(realm.run("var n = 0; for (var k in o) n++; n"), Ok(JsValue::Number(100_000.0)));
|
||||
realm.interp().collect_garbage();
|
||||
let live = realm.interp().heap.bytes;
|
||||
// The list of names takes more than 3 MB (at least 32 + 2 * 2 bytes each), so 1 MiB more than
|
||||
// the live heap holds the object but not the list.
|
||||
let heap_bytes = live + (1 << 20);
|
||||
realm.set_limits(Limits { heap_bytes, ..Limits::default() });
|
||||
assert_eq!(realm.run("o.k0"), Ok(JsValue::Number(0.0)));
|
||||
assert_eq!(limit(&mut realm, "for (var k in o) break;"), LimitKind::Heap);
|
||||
// The run ends before the list is made, so the estimate stays within the limit.
|
||||
assert!(realm.interp().heap.bytes <= heap_bytes, "{} > {heap_bytes}", realm.interp().heap.bytes);
|
||||
}
|
||||
|
||||
#[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
|
||||
|
||||
Reference in New Issue
Block a user