]> git.kaiwu.me - njs.git/commitdiff
QuickJS: align XML tag assignment with njs
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 10 Sep 2026 23:02:35 +0000 (16:02 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 11 Sep 2026 01:29:10 +0000 (18:29 -0700)
Previously, assigning null or undefined to node.$tag$name removed
matching children in QuickJS, while the njs engine rejected every
assignment.

The fix is to distinguish property assignment from deletion in the
QuickJS exotic property handler. Assignments now throw TypeError in both
engines, while delete node.$tag$name remains supported.

external/qjs_xml_module.c
test/xml/xml.t.mjs

index 67c85859c4c3419279f76d12e40b0fbe48b6a618..82b4786ac4b303596c086c9bf72f60feae523045 100644 (file)
@@ -83,6 +83,8 @@ static int qjs_xml_node_delete_property(JSContext *cx, JSValueConst obj,
 static int qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj,
     JSAtom atom, JSValueConst value, JSValueConst getter, JSValueConst setter,
     int flags);
+static int qjs_xml_node_property_modify(JSContext *cx, JSValueConst obj,
+    JSAtom atom, JSValueConst value, int delete);
 static JSValue qjs_xml_node_add_child(JSContext *cx, JSValueConst this_val,
     int argc, JSValueConst *argv);
 static JSValue qjs_xml_node_remove_children(JSContext *cx,
@@ -761,7 +763,7 @@ qjs_xml_node_attr_modify(JSContext *cx, JSValue current, const u_char *name,
 
 static int
 qjs_xml_node_tag_modify(JSContext *cx, JSValue obj, njs_str_t *name,
-    JSValue setval)
+    int delete)
 {
     xmlNode         *node;
     qjs_xml_node_t  *current;
@@ -776,7 +778,7 @@ qjs_xml_node_tag_modify(JSContext *cx, JSValue obj, njs_str_t *name,
         return -1;
     }
 
-    if (!JS_IsNullOrUndefined(setval)) {
+    if (!delete) {
         JS_ThrowTypeError(cx, "XMLNode.$tag$xxx is not assignable, "
                           "use addChild() or node.$tags = [node1, node2, ..] "
                           "syntax");
@@ -1319,23 +1321,28 @@ static int
 qjs_xml_node_set_property(JSContext *cx, JSValueConst obj, JSAtom atom,
     JSValueConst value, JSValueConst receiver, int flags)
 {
-    return qjs_xml_node_define_own_property(cx, obj, atom, value,
-                                            JS_UNDEFINED, JS_UNDEFINED, flags);
+    return qjs_xml_node_property_modify(cx, obj, atom, value, 0);
 }
 
 
 static int
 qjs_xml_node_delete_property(JSContext *cx, JSValueConst obj, JSAtom prop)
 {
-    return qjs_xml_node_define_own_property(cx, obj, prop, JS_UNDEFINED,
-                                        JS_UNDEFINED, JS_UNDEFINED,
-                                        JS_PROP_THROW);
+    return qjs_xml_node_property_modify(cx, obj, prop, JS_UNDEFINED, 1);
 }
 
 
 static int
 qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, JSAtom atom,
     JSValueConst value, JSValueConst getter, JSValueConst setter, int flags)
+{
+    return qjs_xml_node_property_modify(cx, obj, atom, value, 0);
+}
+
+
+static int
+qjs_xml_node_property_modify(JSContext *cx, JSValueConst obj, JSAtom atom,
+    JSValueConst value, int delete)
 {
     int        rc;
     njs_str_t  name, nm;
@@ -1366,7 +1373,7 @@ qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, JSAtom atom,
             nm.start = name.start + njs_length("$tag$");
             nm.length = name.length - njs_length("$tag$");
 
-            rc = qjs_xml_node_tag_modify(cx, obj, &nm, value);
+            rc = qjs_xml_node_tag_modify(cx, obj, &nm, delete);
 
             JS_FreeCString(cx, (char *) name.start);
 
@@ -1408,7 +1415,7 @@ qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, JSAtom atom,
         }
     }
 
-    rc = qjs_xml_node_tag_modify(cx, obj, &name, value);
+    rc = qjs_xml_node_tag_modify(cx, obj, &name, delete);
     JS_FreeCString(cx, (char *) name.start);
 
     return rc;
@@ -1487,7 +1494,7 @@ qjs_xml_node_remove_children(JSContext *cx, JSValueConst this_val,
         name.length = 0;
     }
 
-    rc = qjs_xml_node_tag_modify(cx, this_val, &name, JS_UNDEFINED);
+    rc = qjs_xml_node_tag_modify(cx, this_val, &name, 1);
 
     if (name.start != NULL) {
         JS_FreeCString(cx, (char *) name.start);
index 438c736cb4facd5e3c243fe85115bad02f9e8c93..e4c8bf75700b583caf2a09c3daf598383c392162 100644 (file)
@@ -545,6 +545,37 @@ let modify_tsuite = {
             throw Error('unexpected exception');
           },
           expected: 'OK' },
+        { get: (doc) => {
+            try {
+                doc.note.$tag$to = null;
+            } catch (e) {
+                return [e instanceof TypeError, doc.note.to.$text].toString();
+            }
+
+            return 'missing exception';
+          },
+          expected: 'true,Tove' },
+        { get: (doc) => {
+            try {
+                doc.note.$tag$to = undefined;
+            } catch (e) {
+                return [e instanceof TypeError, doc.note.to.$text].toString();
+            }
+
+            return 'missing exception';
+          },
+          expected: 'true,Tove' },
+        { get: (doc) => {
+            try {
+                delete doc.note.$tags$to;
+            } catch (e) {
+                return [e instanceof TypeError,
+                        doc.note.$tags$to.length].toString();
+            }
+
+            return 'missing exception';
+          },
+          expected: 'true,1' },
         { doc: `<root><a>A</a><b>B</b><c>C</c><a>D</a></root>`,
           get: (doc) => {
             let b = doc.root.b;