From abc121c0268e350cc7482e32d0523e70dd211efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 27 Oct 2024 14:43:14 +0100 Subject: [PATCH 1/5] Add versioning to XML elements. --- Modules/_elementtree.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index e134e096e044b7..cbad0a063bb981 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -230,6 +230,10 @@ typedef struct { PyObject* _children[STATIC_CHILDREN]; + /* incremented whenever 'attrib' is externally mutated */ + uint64_t attrib_version; + /* incremented whenever children are externally mutated */ + uint64_t version; } ElementObjectExtra; typedef struct { @@ -279,6 +283,9 @@ create_extra(ElementObject* self, PyObject* attrib) self->extra->allocated = STATIC_CHILDREN; self->extra->children = self->extra->_children; + self->extra->attrib_version = 0; + self->extra->version = 0; + return 0; } @@ -506,6 +513,7 @@ element_resize(ElementObject* self, Py_ssize_t extra) } self->extra->children = children; self->extra->allocated = size; + self->extra->version++; } return 0; @@ -539,6 +547,7 @@ element_add_subelement(elementtreestate *st, ElementObject *self, self->extra->children[self->extra->length] = Py_NewRef(element); self->extra->length++; + self->extra->version++; return 0; } @@ -780,6 +789,7 @@ _elementtree_Element___copy___impl(ElementObject *self, PyTypeObject *cls) assert(!element->extra->length); element->extra->length = self->extra->length; + element->extra->version = self->extra->version; } return (PyObject*) element; @@ -847,6 +857,7 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) if (element_resize(element, self->extra->length) < 0) goto error; + // TODO(picnixz): check for an evil child's __deepcopy__ on 'self' for (i = 0; i < self->extra->length; i++) { PyObject* child = deepcopy(st, self->extra->children[i], memo); if (!child || !Element_Check(st, child)) { @@ -862,6 +873,7 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) assert(!element->extra->length); element->extra->length = self->extra->length; + element->extra->version = 0; } /* add object to memo dictionary (so deepcopy won't visit it again) */ @@ -1017,10 +1029,12 @@ element_setstate_from_attributes(elementtreestate *st, Py_XSETREF(self->tag, Py_NewRef(tag)); + // TODO(picnix): determine how versioning would work with an evil text text = text ? JOIN_SET(text, PyList_CheckExact(text)) : Py_None; Py_INCREF(JOIN_OBJ(text)); _set_joined_ptr(&self->text, text); + // TODO(picnix): determine how versioning would work with an evil tail tail = tail ? JOIN_SET(tail, PyList_CheckExact(tail)) : Py_None; Py_INCREF(JOIN_OBJ(tail)); _set_joined_ptr(&self->tail, tail); @@ -1071,6 +1085,7 @@ element_setstate_from_attributes(elementtreestate *st, assert(!self->extra->length); self->extra->length = nchildren; + self->extra->version++; } else { if (element_resize(self, 0)) { @@ -1080,6 +1095,7 @@ element_setstate_from_attributes(elementtreestate *st, /* Stash attrib. */ Py_XSETREF(self->extra->attrib, Py_XNewRef(attrib)); + self->extra->attrib_version++; dealloc_extra(oldextra); Py_RETURN_NONE; @@ -1548,6 +1564,7 @@ _elementtree_Element_insert_impl(ElementObject *self, Py_ssize_t index, self->extra->children[index] = Py_NewRef(subelement); self->extra->length++; + self->extra->version++; Py_RETURN_NONE; } @@ -1645,6 +1662,7 @@ _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement) return NULL; } + // TODO(picnixz): check against evil __eq__ for (i = 0; i < self->extra->length; i++) { if (self->extra->children[i] == subelement) break; @@ -1669,6 +1687,7 @@ _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement) self->extra->length--; for (; i < self->extra->length; i++) self->extra->children[i] = self->extra->children[i+1]; + self->extra->version++; Py_DECREF(found); Py_RETURN_NONE; @@ -1724,6 +1743,7 @@ _elementtree_Element_set_impl(ElementObject *self, PyObject *key, if (PyDict_SetItem(attrib, key, value) < 0) return NULL; + self->extra->attrib_version++; Py_RETURN_NONE; } @@ -1757,6 +1777,7 @@ element_setitem(PyObject* self_, Py_ssize_t index, PyObject* item) self->extra->children[i] = self->extra->children[i+1]; } + self->extra->version++; Py_DECREF(old); return 0; @@ -1907,6 +1928,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value) } self->extra->length -= slicelen; + self->extra->version++; /* Discard the recycle list with all the deleted sub-elements */ Py_DECREF(recycle); @@ -1982,6 +2004,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value) } self->extra->length += newlen - slicelen; + self->extra->version++; Py_DECREF(seq); @@ -2078,6 +2101,7 @@ element_attrib_setter(ElementObject *self, PyObject *value, void *closure) return -1; } Py_XSETREF(self->extra->attrib, Py_NewRef(value)); + self->extra->attrib_version++; return 0; } From 4efa51739e6f5ff42b97314ad9bf660983bc3cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:44:55 +0100 Subject: [PATCH 2/5] fix tests --- Lib/test/test_xml_etree_c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index 3a0fc572f457ff..8f371951f1c08d 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -234,7 +234,7 @@ class SizeofTest(unittest.TestCase): def setUp(self): self.elementsize = support.calcobjsize('5P') # extra - self.extra = struct.calcsize('PnnP4P') + self.extra = struct.calcsize('PnnP4Pnn') check_sizeof = support.check_sizeof From 4ca3cf04a5c56599c39f7b8caabdc3188d914fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:24:26 +0100 Subject: [PATCH 3/5] fix portability issues --- Lib/test/test_xml_etree_c.py | 2 +- Modules/_elementtree.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index 8f371951f1c08d..aa95753fed2a49 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -234,7 +234,7 @@ class SizeofTest(unittest.TestCase): def setUp(self): self.elementsize = support.calcobjsize('5P') # extra - self.extra = struct.calcsize('PnnP4Pnn') + self.extra = struct.calcsize('PnnP4PNN') check_sizeof = support.check_sizeof diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index cbad0a063bb981..75870ee27fdd62 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -231,9 +231,9 @@ typedef struct { PyObject* _children[STATIC_CHILDREN]; /* incremented whenever 'attrib' is externally mutated */ - uint64_t attrib_version; + size_t attrib_version; /* incremented whenever children are externally mutated */ - uint64_t version; + size_t version; } ElementObjectExtra; typedef struct { From a1950d1d4b1ac24ddf849623e8da03bad8191fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:49:21 +0100 Subject: [PATCH 4/5] fixup --- Modules/_elementtree.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 75870ee27fdd62..11fee5c911422e 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -231,9 +231,9 @@ typedef struct { PyObject* _children[STATIC_CHILDREN]; /* incremented whenever 'attrib' is externally mutated */ - size_t attrib_version; + size_t attrs_version; /* incremented whenever children are externally mutated */ - size_t version; + size_t nodes_version; } ElementObjectExtra; typedef struct { @@ -283,8 +283,8 @@ create_extra(ElementObject* self, PyObject* attrib) self->extra->allocated = STATIC_CHILDREN; self->extra->children = self->extra->_children; - self->extra->attrib_version = 0; - self->extra->version = 0; + self->extra->attrs_version = 0; + self->extra->nodes_version = 0; return 0; } @@ -513,7 +513,7 @@ element_resize(ElementObject* self, Py_ssize_t extra) } self->extra->children = children; self->extra->allocated = size; - self->extra->version++; + self->extra->nodes_version++; } return 0; @@ -547,7 +547,7 @@ element_add_subelement(elementtreestate *st, ElementObject *self, self->extra->children[self->extra->length] = Py_NewRef(element); self->extra->length++; - self->extra->version++; + self->extra->nodes_version++; return 0; } @@ -789,7 +789,7 @@ _elementtree_Element___copy___impl(ElementObject *self, PyTypeObject *cls) assert(!element->extra->length); element->extra->length = self->extra->length; - element->extra->version = self->extra->version; + element->extra->nodes_version = self->extra->nodes_version; } return (PyObject*) element; @@ -873,7 +873,7 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) assert(!element->extra->length); element->extra->length = self->extra->length; - element->extra->version = 0; + element->extra->nodes_version = 0; } /* add object to memo dictionary (so deepcopy won't visit it again) */ @@ -1029,12 +1029,10 @@ element_setstate_from_attributes(elementtreestate *st, Py_XSETREF(self->tag, Py_NewRef(tag)); - // TODO(picnix): determine how versioning would work with an evil text text = text ? JOIN_SET(text, PyList_CheckExact(text)) : Py_None; Py_INCREF(JOIN_OBJ(text)); _set_joined_ptr(&self->text, text); - // TODO(picnix): determine how versioning would work with an evil tail tail = tail ? JOIN_SET(tail, PyList_CheckExact(tail)) : Py_None; Py_INCREF(JOIN_OBJ(tail)); _set_joined_ptr(&self->tail, tail); @@ -1085,7 +1083,6 @@ element_setstate_from_attributes(elementtreestate *st, assert(!self->extra->length); self->extra->length = nchildren; - self->extra->version++; } else { if (element_resize(self, 0)) { @@ -1095,7 +1092,6 @@ element_setstate_from_attributes(elementtreestate *st, /* Stash attrib. */ Py_XSETREF(self->extra->attrib, Py_XNewRef(attrib)); - self->extra->attrib_version++; dealloc_extra(oldextra); Py_RETURN_NONE; @@ -1564,7 +1560,7 @@ _elementtree_Element_insert_impl(ElementObject *self, Py_ssize_t index, self->extra->children[index] = Py_NewRef(subelement); self->extra->length++; - self->extra->version++; + self->extra->nodes_version++; Py_RETURN_NONE; } @@ -1687,7 +1683,7 @@ _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement) self->extra->length--; for (; i < self->extra->length; i++) self->extra->children[i] = self->extra->children[i+1]; - self->extra->version++; + self->extra->nodes_version++; Py_DECREF(found); Py_RETURN_NONE; @@ -1743,7 +1739,7 @@ _elementtree_Element_set_impl(ElementObject *self, PyObject *key, if (PyDict_SetItem(attrib, key, value) < 0) return NULL; - self->extra->attrib_version++; + self->extra->attrs_version++; Py_RETURN_NONE; } @@ -1777,7 +1773,7 @@ element_setitem(PyObject* self_, Py_ssize_t index, PyObject* item) self->extra->children[i] = self->extra->children[i+1]; } - self->extra->version++; + self->extra->nodes_version++; Py_DECREF(old); return 0; @@ -1928,7 +1924,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value) } self->extra->length -= slicelen; - self->extra->version++; + self->extra->nodes_version++; /* Discard the recycle list with all the deleted sub-elements */ Py_DECREF(recycle); @@ -2004,7 +2000,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value) } self->extra->length += newlen - slicelen; - self->extra->version++; + self->extra->nodes_version++; Py_DECREF(seq); @@ -2101,7 +2097,7 @@ element_attrib_setter(ElementObject *self, PyObject *value, void *closure) return -1; } Py_XSETREF(self->extra->attrib, Py_NewRef(value)); - self->extra->attrib_version++; + self->extra->attrs_version++; return 0; } From 9b6f55970711d78423ca44c522f6d8e8b93ff0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:05:31 +0100 Subject: [PATCH 5/5] unify versioning --- Lib/test/test_xml_etree_c.py | 2 +- Modules/_elementtree.c | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index aa95753fed2a49..aae8c3b0019951 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -234,7 +234,7 @@ class SizeofTest(unittest.TestCase): def setUp(self): self.elementsize = support.calcobjsize('5P') # extra - self.extra = struct.calcsize('PnnP4PNN') + self.extra = struct.calcsize('PnnP4PN') check_sizeof = support.check_sizeof diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 11fee5c911422e..3e206aa4318eee 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -230,10 +230,8 @@ typedef struct { PyObject* _children[STATIC_CHILDREN]; - /* incremented whenever 'attrib' is externally mutated */ - size_t attrs_version; - /* incremented whenever children are externally mutated */ - size_t nodes_version; + /* incremented whenever the object is externally mutated */ + size_t version; } ElementObjectExtra; typedef struct { @@ -283,8 +281,7 @@ create_extra(ElementObject* self, PyObject* attrib) self->extra->allocated = STATIC_CHILDREN; self->extra->children = self->extra->_children; - self->extra->attrs_version = 0; - self->extra->nodes_version = 0; + self->extra->version = 0; return 0; } @@ -513,7 +510,7 @@ element_resize(ElementObject* self, Py_ssize_t extra) } self->extra->children = children; self->extra->allocated = size; - self->extra->nodes_version++; + self->extra->version++; } return 0; @@ -547,7 +544,7 @@ element_add_subelement(elementtreestate *st, ElementObject *self, self->extra->children[self->extra->length] = Py_NewRef(element); self->extra->length++; - self->extra->nodes_version++; + self->extra->version++; return 0; } @@ -789,7 +786,7 @@ _elementtree_Element___copy___impl(ElementObject *self, PyTypeObject *cls) assert(!element->extra->length); element->extra->length = self->extra->length; - element->extra->nodes_version = self->extra->nodes_version; + element->extra->version = self->extra->version; } return (PyObject*) element; @@ -873,7 +870,7 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) assert(!element->extra->length); element->extra->length = self->extra->length; - element->extra->nodes_version = 0; + element->extra->version = 0; } /* add object to memo dictionary (so deepcopy won't visit it again) */ @@ -1560,7 +1557,7 @@ _elementtree_Element_insert_impl(ElementObject *self, Py_ssize_t index, self->extra->children[index] = Py_NewRef(subelement); self->extra->length++; - self->extra->nodes_version++; + self->extra->version++; Py_RETURN_NONE; } @@ -1683,7 +1680,7 @@ _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement) self->extra->length--; for (; i < self->extra->length; i++) self->extra->children[i] = self->extra->children[i+1]; - self->extra->nodes_version++; + self->extra->version++; Py_DECREF(found); Py_RETURN_NONE; @@ -1739,7 +1736,7 @@ _elementtree_Element_set_impl(ElementObject *self, PyObject *key, if (PyDict_SetItem(attrib, key, value) < 0) return NULL; - self->extra->attrs_version++; + self->extra->version++; Py_RETURN_NONE; } @@ -1773,7 +1770,7 @@ element_setitem(PyObject* self_, Py_ssize_t index, PyObject* item) self->extra->children[i] = self->extra->children[i+1]; } - self->extra->nodes_version++; + self->extra->version++; Py_DECREF(old); return 0; @@ -1924,7 +1921,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value) } self->extra->length -= slicelen; - self->extra->nodes_version++; + self->extra->version++; /* Discard the recycle list with all the deleted sub-elements */ Py_DECREF(recycle); @@ -2000,7 +1997,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value) } self->extra->length += newlen - slicelen; - self->extra->nodes_version++; + self->extra->version++; Py_DECREF(seq); @@ -2097,7 +2094,7 @@ element_attrib_setter(ElementObject *self, PyObject *value, void *closure) return -1; } Py_XSETREF(self->extra->attrib, Py_NewRef(value)); - self->extra->attrs_version++; + self->extra->version++; return 0; }