JavaScript: XML namespace declarations and QName strings count against the heap limit

addNamespace and setNamespace now add a declaration's bytes to the heap total when the element
gains a new prefix, and QName.prototype.toString adds the bytes of the string it returns. Before,
both only checked for room, so a loop of distinct prefixes could grow an element past the heap
limit until a collection recounted it.
This commit is contained in:
dotCooCoo
2026-11-11 08:09:22 -08:00
parent bc6c20d0c9
commit de95640d93
2 changed files with 34 additions and 2 deletions
+8 -2
View File
@@ -299,7 +299,9 @@ fn qname_to_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> Js
_ => {}
}
units.extend_from_slice(&name.local);
interp.reserve_heap(string_bytes(units.len()))?;
let bytes = string_bytes(units.len());
interp.reserve_heap(bytes)?;
interp.heap.bytes = interp.heap.bytes.saturating_add(bytes);
Ok(Value::String(Str::from_units(units)))
}
@@ -816,7 +818,11 @@ fn add_in_scope(interp: &mut Interp, x: ObjId, ns: XmlNs) -> JsResult<()> {
if prefix.is_empty() && node.name.as_ref().and_then(|n| n.uri.as_ref()).is_some_and(|u| u.is_empty()) {
return Ok(());
}
interp.reserve_heap(crate::heap::XML_NS_BYTES)?;
if !node.ns.iter().any(|m| m.prefix.as_ref() == Some(&prefix)) {
let bytes = crate::heap::XML_NS_BYTES;
interp.reserve_heap(bytes)?;
interp.heap.bytes = interp.heap.bytes.saturating_add(bytes);
}
let attrs = interp.xml_node(x)?.attrs.clone();
let node = interp.xml_node_mut(x)?;
node.ns.retain(|m| m.prefix.as_ref() != Some(&prefix));
+26
View File
@@ -1018,6 +1018,32 @@ fn comparing_xml_values_is_charged_to_the_heap() {
assert!(heap_stops_only(setup, "(SPARE, x == y)", "x == y && y.@q == '2'") > 0);
}
/// Runs `setup` with no limit, then `action` in the same realm, and returns how `action` ended.
fn after_setup(setup: &str, action: &str) -> Result<JsValue, RunErrorKind> {
let mut r = realm();
let interp = r.interp();
let global = interp.global;
assert!(crate::builtins::method(interp, global, "squeeze", squeeze, 1).is_ok());
r.run(setup).unwrap();
r.run(action).map_err(|e| e.kind)
}
#[test]
fn namespace_declarations_and_qname_strings_are_charged_to_the_heap() {
let heap = Err(RunErrorKind::Limit(LimitKind::Heap));
let distinct = "var x = <r/>; var a = []; for (var i = 0; i < 400; i++) a.push(new Namespace('p' + i, 'u' + i));";
for method in ["addNamespace", "setNamespace"] {
let action = format!("squeeze(4096); for (var i = 0; i < 400; i++) x.{method}(a[i]); 1");
assert_eq!(after_setup(distinct, &action), heap, "{method}");
}
let same = "var x = <r/>; var n = new Namespace('p', 'u');";
let action = "squeeze(4096); for (var i = 0; i < 400; i++) x.addNamespace(n); 1";
assert_eq!(after_setup(same, action), Ok(JsValue::Number(1.0)));
let long = "var s = 'x'; for (var i = 0; i < 12; i++) s += s; var q = new QName('u', s);";
let action = "squeeze(12000); var t1 = q.toString(); var t2 = q.toString(); 1";
assert_eq!(after_setup(long, action), heap);
}
#[test]
fn an_interrupt_stops_serialization() {
let mut r = realm();