Skip to content

Commit 6f906b3

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.
1 parent 9d4712b commit 6f906b3

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
@@ -117,6 +117,22 @@ def __del__(self):
117117
elem.tail = X()
118118
elem.__setstate__({'tag': 42}) # shouldn't cause an assertion failure
119119

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

121137
@unittest.skipUnless(cET, 'requires _elementtree')
122138
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___impl(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___impl(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

@@ -957,6 +972,7 @@ element_setstate_from_attributes(ElementObject *self,
957972
PyObject *children)
958973
{
959974
Py_ssize_t i, nchildren;
975+
ElementObjectExtra *oldextra = NULL;
960976

961977
if (!tag) {
962978
PyErr_SetString(PyExc_TypeError, "tag may not be NULL");
@@ -975,41 +991,58 @@ element_setstate_from_attributes(ElementObject *self,
975991
_set_joined_ptr(&self->tail, tail);
976992

977993
/* Handle ATTRIB and CHILDREN. */
978-
if (!children && !attrib)
994+
if (!children && !attrib) {
979995
Py_RETURN_NONE;
996+
}
980997

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

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

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

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

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

10141047
Py_RETURN_NONE;
10151048
}

0 commit comments

Comments
 (0)