Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ebf37a2

Browse files
committed
Fixes and enhancements to _elementtree:
* Fixed refleak problems when GC collection is run (see messages in issue #14065) * Added weakref support to Element objects
1 parent 5c73e8e commit ebf37a2

2 files changed

Lines changed: 79 additions & 21 deletions

File tree

Lib/test/test_xml_etree.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,41 @@ class Dummy:
18591859
gc_collect()
18601860
self.assertIsNone(wref())
18611861

1862+
# A longer cycle: d->e->e2->d
1863+
e = ET.Element('joe')
1864+
d = Dummy()
1865+
d.dummyref = e
1866+
wref = weakref.ref(d)
1867+
e2 = ET.SubElement(e, 'foo', attr=d)
1868+
del d, e, e2
1869+
gc_collect()
1870+
self.assertIsNone(wref())
1871+
1872+
# A cycle between Element objects as children of one another
1873+
# e1->e2->e3->e1
1874+
e1 = ET.Element('e1')
1875+
e2 = ET.Element('e2')
1876+
e3 = ET.Element('e3')
1877+
e1.append(e2)
1878+
e2.append(e2)
1879+
e3.append(e1)
1880+
wref = weakref.ref(e1)
1881+
del e1, e2, e3
1882+
gc_collect()
1883+
self.assertIsNone(wref())
1884+
1885+
def test_weakref(self):
1886+
flag = False
1887+
def wref_cb(w):
1888+
nonlocal flag
1889+
flag = True
1890+
e = ET.Element('e')
1891+
wref = weakref.ref(e, wref_cb)
1892+
self.assertEqual(wref().tag, 'e')
1893+
del e
1894+
self.assertEqual(flag, True)
1895+
self.assertEqual(wref(), None)
1896+
18621897

18631898
class ElementTreeTest(unittest.TestCase):
18641899
def test_istype(self):

Modules/_elementtree.c

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
/* See http://www.python.org/psf/license for licensing details. */
4949

5050
#include "Python.h"
51+
#include "structmember.h"
5152

5253
#define VERSION "1.0.6"
5354

@@ -229,6 +230,8 @@ typedef struct {
229230

230231
ElementObjectExtra* extra;
231232

233+
PyObject *weakreflist; /* For tp_weaklistoffset */
234+
232235
} ElementObject;
233236

234237
static PyTypeObject Element_Type;
@@ -261,17 +264,24 @@ create_extra(ElementObject* self, PyObject* attrib)
261264
LOCAL(void)
262265
dealloc_extra(ElementObject* self)
263266
{
264-
int i;
267+
if (!self->extra)
268+
return;
269+
270+
/* Avoid DECREFs calling into this code again (cycles, etc.)
271+
*/
272+
ElementObjectExtra *myextra = self->extra;
273+
self->extra = NULL;
265274

266-
Py_DECREF(self->extra->attrib);
275+
Py_DECREF(myextra->attrib);
267276

268-
for (i = 0; i < self->extra->length; i++)
269-
Py_DECREF(self->extra->children[i]);
277+
int i;
278+
for (i = 0; i < myextra->length; i++)
279+
Py_DECREF(myextra->children[i]);
270280

271-
if (self->extra->children != self->extra->_children)
272-
PyObject_Free(self->extra->children);
281+
if (myextra->children != myextra->_children)
282+
PyObject_Free(myextra->children);
273283

274-
PyObject_Free(self->extra);
284+
PyObject_Free(myextra);
275285
}
276286

277287
/* Convenience internal function to create new Element objects with the given
@@ -308,6 +318,8 @@ create_new_element(PyObject* tag, PyObject* attrib)
308318
Py_INCREF(Py_None);
309319
self->tail = Py_None;
310320

321+
self->weakreflist = NULL;
322+
311323
ALLOC(sizeof(ElementObject), "create element");
312324
PyObject_GC_Track(self);
313325
return (PyObject*) self;
@@ -328,6 +340,7 @@ element_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
328340
e->tail = Py_None;
329341

330342
e->extra = NULL;
343+
e->weakreflist = NULL;
331344
}
332345
return (PyObject *)e;
333346
}
@@ -576,26 +589,39 @@ element_gc_traverse(ElementObject *self, visitproc visit, void *arg)
576589
static int
577590
element_gc_clear(ElementObject *self)
578591
{
579-
PyObject *text = JOIN_OBJ(self->text);
580-
PyObject *tail = JOIN_OBJ(self->tail);
581592
Py_CLEAR(self->tag);
582-
Py_CLEAR(text);
583-
Py_CLEAR(tail);
584593

585-
/* After dropping all references from extra, it's no longer valid anyway,
586-
** so fully deallocate it (see also element_clearmethod)
594+
/* The following is like Py_CLEAR for self->text and self->tail, but
595+
* written explicitily because the real pointers hide behind access
596+
* macros.
587597
*/
588-
if (self->extra) {
589-
dealloc_extra(self);
590-
self->extra = NULL;
598+
if (self->text) {
599+
PyObject *tmp = JOIN_OBJ(self->text);
600+
self->text = NULL;
601+
Py_DECREF(tmp);
602+
}
603+
604+
if (self->tail) {
605+
PyObject *tmp = JOIN_OBJ(self->tail);
606+
self->tail = NULL;
607+
Py_DECREF(tmp);
591608
}
609+
610+
/* After dropping all references from extra, it's no longer valid anyway,
611+
* so fully deallocate it.
612+
*/
613+
dealloc_extra(self);
592614
return 0;
593615
}
594616

595617
static void
596618
element_dealloc(ElementObject* self)
597619
{
598620
PyObject_GC_UnTrack(self);
621+
622+
if (self->weakreflist != NULL)
623+
PyObject_ClearWeakRefs((PyObject *) self);
624+
599625
/* element_gc_clear clears all references and deallocates extra
600626
*/
601627
element_gc_clear(self);
@@ -626,10 +652,7 @@ element_clearmethod(ElementObject* self, PyObject* args)
626652
if (!PyArg_ParseTuple(args, ":clear"))
627653
return NULL;
628654

629-
if (self->extra) {
630-
dealloc_extra(self);
631-
self->extra = NULL;
632-
}
655+
dealloc_extra(self);
633656

634657
Py_INCREF(Py_None);
635658
Py_DECREF(JOIN_OBJ(self->text));
@@ -1693,7 +1716,7 @@ static PyTypeObject Element_Type = {
16931716
(traverseproc)element_gc_traverse, /* tp_traverse */
16941717
(inquiry)element_gc_clear, /* tp_clear */
16951718
0, /* tp_richcompare */
1696-
0, /* tp_weaklistoffset */
1719+
offsetof(ElementObject, weakreflist), /* tp_weaklistoffset */
16971720
0, /* tp_iter */
16981721
0, /* tp_iternext */
16991722
element_methods, /* tp_methods */

0 commit comments

Comments
 (0)