Skip to content

Commit bcbefe2

Browse files
bpo-35008: Fix possible leaks in Element.__setstate__(). (GH-9924)
C implementation of xml.etree.ElementTree.Element.__setstate__() leaked references to children when called for already initialized element. (cherry picked from commit 6f906b3) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 4bfecb9 commit bcbefe2

File tree

3 files changed

+83
-31
lines changed

3 files changed

+83
-31
lines changed

Lib/test/test_xml_etree_c.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,22 @@ def __del__(self):
116116
elem.tail = X()
117117
elem.__setstate__({'tag': 42}) # shouldn't cause an assertion failure
118118

119+
def test_setstate_leaks(self):
120+
# Test reference leaks
121+
elem = cET.Element.__new__(cET.Element)
122+
for i in range(100):
123+
elem.__setstate__({'tag': 'foo', 'attrib': {'bar': 42},
124+
'_children': [cET.Element('child')],
125+
'text': 'text goes here',
126+
'tail': 'opposite of head'})
127+
128+
self.assertEqual(elem.tag, 'foo')
129+
self.assertEqual(elem.text, 'text goes here')
130+
self.assertEqual(elem.tail, 'opposite of head')
131+
self.assertEqual(list(elem.attrib.items()), [('bar', 42)])
132+
self.assertEqual(len(elem), 1)
133+
self.assertEqual(elem[0].tag, 'child')
134+
119135

120136
@unittest.skipUnless(cET, 'requires _elementtree')
121137
class TestAliasWorking(unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed references leaks when call the ``__setstate__()`` method of
2+
:class:`xml.etree.ElementTree.Element` in the C implementation for already
3+
initialized element.

Modules/_elementtree.c

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,29 @@ create_extra(ElementObject* self, PyObject* attrib)
233233
}
234234

235235
LOCAL(void)
236-
dealloc_extra(ElementObject* self)
236+
dealloc_extra(ElementObjectExtra *extra)
237237
{
238-
ElementObjectExtra *myextra;
239238
Py_ssize_t i;
240239

240+
if (!extra)
241+
return;
242+
243+
Py_DECREF(extra->attrib);
244+
245+
for (i = 0; i < extra->length; i++)
246+
Py_DECREF(extra->children[i]);
247+
248+
if (extra->children != extra->_children)
249+
PyObject_Free(extra->children);
250+
251+
PyObject_Free(extra);
252+
}
253+
254+
LOCAL(void)
255+
clear_extra(ElementObject* self)
256+
{
257+
ElementObjectExtra *myextra;
258+
241259
if (!self->extra)
242260
return;
243261

@@ -246,15 +264,7 @@ dealloc_extra(ElementObject* self)
246264
myextra = self->extra;
247265
self->extra = NULL;
248266

249-
Py_DECREF(myextra->attrib);
250-
251-
for (i = 0; i < myextra->length; i++)
252-
Py_DECREF(myextra->children[i]);
253-
254-
if (myextra->children != myextra->_children)
255-
PyObject_Free(myextra->children);
256-
257-
PyObject_Free(myextra);
267+
dealloc_extra(myextra);
258268
}
259269

260270
/* Convenience internal function to create new Element objects with the given
@@ -420,6 +430,7 @@ element_resize(ElementObject* self, Py_ssize_t extra)
420430
Py_ssize_t size;
421431
PyObject* *children;
422432

433+
assert(extra >= 0);
423434
/* make sure self->children can hold the given number of extra
424435
elements. set an exception and return -1 if allocation failed */
425436

@@ -624,7 +635,7 @@ element_gc_clear(ElementObject *self)
624635
/* After dropping all references from extra, it's no longer valid anyway,
625636
* so fully deallocate it.
626637
*/
627-
dealloc_extra(self);
638+
clear_extra(self);
628639
return 0;
629640
}
630641

@@ -676,7 +687,7 @@ static PyObject *
676687
_elementtree_Element_clear_impl(ElementObject *self)
677688
/*[clinic end generated code: output=8bcd7a51f94cfff6 input=3c719ff94bf45dd6]*/
678689
{
679-
dealloc_extra(self);
690+
clear_extra(self);
680691

681692
Py_INCREF(Py_None);
682693
_set_joined_ptr(&self->text, Py_None);
@@ -710,6 +721,7 @@ _elementtree_Element___copy___impl(ElementObject *self)
710721
Py_INCREF(JOIN_OBJ(self->tail));
711722
_set_joined_ptr(&element->tail, self->tail);
712723

724+
assert(!element->extra || !element->extra->length);
713725
if (self->extra) {
714726
if (element_resize(element, self->extra->length) < 0) {
715727
Py_DECREF(element);
@@ -721,6 +733,7 @@ _elementtree_Element___copy___impl(ElementObject *self)
721733
element->extra->children[i] = self->extra->children[i];
722734
}
723735

736+
assert(!element->extra->length);
724737
element->extra->length = self->extra->length;
725738
}
726739

@@ -783,6 +796,7 @@ _elementtree_Element___deepcopy__(ElementObject *self, PyObject *memo)
783796
goto error;
784797
_set_joined_ptr(&element->tail, JOIN_SET(tail, JOIN_GET(self->tail)));
785798

799+
assert(!element->extra || !element->extra->length);
786800
if (self->extra) {
787801
if (element_resize(element, self->extra->length) < 0)
788802
goto error;
@@ -796,6 +810,7 @@ _elementtree_Element___deepcopy__(ElementObject *self, PyObject *memo)
796810
element->extra->children[i] = child;
797811
}
798812

813+
assert(!element->extra->length);
799814
element->extra->length = self->extra->length;
800815
}
801816

@@ -956,6 +971,7 @@ element_setstate_from_attributes(ElementObject *self,
956971
PyObject *children)
957972
{
958973
Py_ssize_t i, nchildren;
974+
ElementObjectExtra *oldextra = NULL;
959975

960976
if (!tag) {
961977
PyErr_SetString(PyExc_TypeError, "tag may not be NULL");
@@ -974,41 +990,58 @@ element_setstate_from_attributes(ElementObject *self,
974990
_set_joined_ptr(&self->tail, tail);
975991

976992
/* Handle ATTRIB and CHILDREN. */
977-
if (!children && !attrib)
993+
if (!children && !attrib) {
978994
Py_RETURN_NONE;
995+
}
979996

980997
/* Compute 'nchildren'. */
981998
if (children) {
982999
if (!PyList_Check(children)) {
9831000
PyErr_SetString(PyExc_TypeError, "'_children' is not a list");
9841001
return NULL;
9851002
}
986-
nchildren = PyList_Size(children);
987-
}
988-
else {
989-
nchildren = 0;
990-
}
1003+
nchildren = PyList_GET_SIZE(children);
9911004

992-
/* Allocate 'extra'. */
993-
if (element_resize(self, nchildren)) {
994-
return NULL;
995-
}
996-
assert(self->extra && self->extra->allocated >= nchildren);
1005+
/* (Re-)allocate 'extra'.
1006+
Avoid DECREFs calling into this code again (cycles, etc.)
1007+
*/
1008+
oldextra = self->extra;
1009+
self->extra = NULL;
1010+
if (element_resize(self, nchildren)) {
1011+
assert(!self->extra || !self->extra->length);
1012+
clear_extra(self);
1013+
self->extra = oldextra;
1014+
return NULL;
1015+
}
1016+
assert(self->extra);
1017+
assert(self->extra->allocated >= nchildren);
1018+
if (oldextra) {
1019+
assert(self->extra->attrib == Py_None);
1020+
self->extra->attrib = oldextra->attrib;
1021+
oldextra->attrib = Py_None;
1022+
}
9971023

998-
/* Copy children */
999-
for (i = 0; i < nchildren; i++) {
1000-
self->extra->children[i] = PyList_GET_ITEM(children, i);
1001-
Py_INCREF(self->extra->children[i]);
1002-
}
1024+
/* Copy children */
1025+
for (i = 0; i < nchildren; i++) {
1026+
self->extra->children[i] = PyList_GET_ITEM(children, i);
1027+
Py_INCREF(self->extra->children[i]);
1028+
}
10031029

1004-
self->extra->length = nchildren;
1005-
self->extra->allocated = nchildren;
1030+
assert(!self->extra->length);
1031+
self->extra->length = nchildren;
1032+
}
1033+
else {
1034+
if (element_resize(self, 0)) {
1035+
return NULL;
1036+
}
1037+
}
10061038

10071039
/* Stash attrib. */
10081040
if (attrib) {
10091041
Py_INCREF(attrib);
10101042
Py_XSETREF(self->extra->attrib, attrib);
10111043
}
1044+
dealloc_extra(oldextra);
10121045

10131046
Py_RETURN_NONE;
10141047
}

0 commit comments

Comments
 (0)