JavaScript: new XML copies a one-item list's node, and insertChildAfter and insertChildBefore take fuel per child scanned

new XML(list) now returns a deep copy of the list's single item, as
ECMA-357 section 13.4.2 specifies for an XMLList argument; before, it
returned the item itself, so changes to the new value changed the list.

insertChildAfter and insertChildBefore take one unit of fuel for each
child they compare with the reference, as childIndex does.

The test for the missing xpath method now checks the XML and XMLList
prototypes; reading .xpath on a value selects child elements and
returned an empty list in every case.
This commit is contained in:
dotCooCoo
2026-11-11 09:21:00 -08:00
parent 428a4d875d
commit 53bd6ecd9a
2 changed files with 33 additions and 4 deletions
+10 -3
View File
@@ -330,7 +330,7 @@ fn xml_ctor(interp: &mut Interp, _: Value, args: &[Value], construct: bool) -> J
let v = arg(args, 0);
interp.rooted(std::slice::from_ref(&v), |i| {
let x = to_xml(i, &v)?;
if construct && matches!(i.classify(&v), Xv::Node(_)) {
if construct && matches!(i.classify(&v), Xv::Node(_) | Xv::List(_)) {
return Ok(Value::Object(i.node_copy(x)?));
}
Ok(Value::Object(x))
@@ -883,8 +883,15 @@ fn insert_relative(interp: &mut Interp, this: Value, args: &[Value], after: bool
if after { 0 } else { len }
} else {
let Xv::Node(r) = interp.classify(&reference) else { return Ok(Value::Undefined) };
interp.use_fuel()?;
let Some(i) = interp.xml_node(x)?.children.iter().position(|&c| c == r) else { return Ok(Value::Undefined) };
let mut found = None;
for i in 0..len {
interp.use_fuel()?;
if interp.xml_node(x)?.children.get(i) == Some(&r) {
found = Some(i);
break;
}
}
let Some(i) = found else { return Ok(Value::Undefined) };
if after { i + 1 } else { i }
};
interp.rooted(std::slice::from_ref(&v), |i| i.xml_insert(x, at, &c))?;
+23 -1
View File
@@ -290,6 +290,8 @@ fn the_constructors_keep_identity_or_copy_as_specified() {
truth("XML().nodeKind() === 'text' && XML(null) == '' && XML(undefined) == ''");
truth("var x = <a/>; XML(x) === x && new XML(x) !== x && new XML(x) == x");
truth("var l = <><a/></>; XML(l) === l[0]");
truth("var l = <><a/></>; var y = new XML(l); y !== l[0] && y == l[0] && y.parent() === null");
truth("var l = <><a/></>; var y = new XML(l); y.@p = '1'; y.appendChild(<b/>); l[0].@p.length() === 0 && l[0].*.length() === 0");
assert_eq!(thrown_name("XML(<><a/><b/></>)"), "TypeError");
assert_eq!(thrown_name("XML('<a><b></a>')"), "SyntaxError");
assert_eq!(thrown_name("XML('<a/><b/>')"), "SyntaxError");
@@ -368,7 +370,7 @@ fn the_xml_methods_behave_as_specified() {
assert_eq!(string("(<a>1</a>).toString()"), "1");
assert_eq!(string("(<a><b/></a>).toString()"), "<a>\n <b/>\n</a>");
truth("var x = <a/>; x.valueOf() === x");
truth("(<a/>).xpath === undefined || (<a/>).xpath.length() === 0");
truth("!Object.prototype.hasOwnProperty.call(XML.prototype, 'xpath') && !Object.prototype.hasOwnProperty.call(XMLList.prototype, 'xpath')");
truth("var x = <a><b/></a>; x.b.attributes !== undefined");
}
@@ -716,6 +718,26 @@ fn catastrophic_queries_end_with_the_fuel_limit() {
assert_eq!(kind_of(limits, &serialize), RunErrorKind::Limit(LimitKind::Fuel));
}
/// The fuel that `action` takes when it runs after `setup`.
fn fuel_of(setup: &str, action: &str) -> u64 {
let mut r = realm();
r.run(setup).unwrap();
r.run(action).unwrap();
Limits::default().fuel - r.fuel_left()
}
#[test]
fn insert_child_before_and_after_take_fuel_for_each_child_they_scan() {
let narrow = "var x = <a><b/></a>; var last = x.b[0];";
let wide = "var x = <a/>; for (var i = 0; i < 2000; i++) x.appendChild(<b/>); var last = x.b[1999];";
for action in
["x.insertChildAfter(<z/>, <c/>)", "x.insertChildBefore(<z/>, <c/>)", "x.insertChildAfter(last, <c/>)", "x.insertChildBefore(last, <c/>)"]
{
let (n, w) = (fuel_of(narrow, action), fuel_of(wide, action));
assert!(w >= n + 1999, "{action}: {n} with one child, {w} with 2000");
}
}
#[test]
fn limits_cannot_be_caught_by_the_script() {
assert_eq!(kind_of(TIGHT, "try { for (;;) (<a/>).toXMLString(); } catch (e) { 1 }"), RunErrorKind::Limit(LimitKind::Fuel));