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

Skip to content

Commit 0192ba3

Browse files
committed
Issue #14065: Added cyclic GC support to ET.Element
1 parent 1e25755 commit 0192ba3

2 files changed

Lines changed: 74 additions & 16 deletions

File tree

Lib/test/test_xml_etree.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
# Don't re-import "xml.etree.ElementTree" module in the docstring,
1515
# except if the test is specific to the Python implementation.
1616

17-
import sys
17+
import gc
1818
import html
1919
import io
20+
import sys
2021
import unittest
2122

2223
from test import support
@@ -1846,6 +1847,30 @@ def test_augmentation_type_errors(self):
18461847
self.assertRaises(TypeError, e.extend, [ET.Element('bar'), 'foo'])
18471848
self.assertRaises(TypeError, e.insert, 0, 'foo')
18481849

1850+
def test_cyclic_gc(self):
1851+
class ShowGC:
1852+
def __init__(self, flaglist):
1853+
self.flaglist = flaglist
1854+
def __del__(self):
1855+
self.flaglist.append(1)
1856+
1857+
# Test the shortest cycle: lst->element->lst
1858+
fl = []
1859+
lst = [ShowGC(fl)]
1860+
lst.append(ET.Element('joe', attr=lst))
1861+
del lst
1862+
gc.collect()
1863+
self.assertEqual(fl, [1])
1864+
1865+
# A longer cycle: lst->e->e2->lst
1866+
fl = []
1867+
e = ET.Element('joe')
1868+
lst = [ShowGC(fl), e]
1869+
e2 = ET.SubElement(e, 'foo', attr=lst)
1870+
del lst, e, e2
1871+
gc.collect()
1872+
self.assertEqual(fl, [1])
1873+
18491874

18501875
class ElementTreeTest(unittest.TestCase):
18511876
def test_istype(self):

Modules/_elementtree.c

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ create_new_element(PyObject* tag, PyObject* attrib)
282282
{
283283
ElementObject* self;
284284

285-
self = PyObject_New(ElementObject, &Element_Type);
285+
self = PyObject_GC_New(ElementObject, &Element_Type);
286286
if (self == NULL)
287287
return NULL;
288288

@@ -309,7 +309,7 @@ create_new_element(PyObject* tag, PyObject* attrib)
309309
self->tail = Py_None;
310310

311311
ALLOC(sizeof(ElementObject), "create element");
312-
312+
PyObject_GC_Track(self);
313313
return (PyObject*) self;
314314
}
315315

@@ -556,19 +556,51 @@ subelement(PyObject* self, PyObject* args, PyObject* kw)
556556
return elem;
557557
}
558558

559-
static void
560-
element_dealloc(ElementObject* self)
559+
static int
560+
element_gc_traverse(ElementObject *self, visitproc visit, void *arg)
561+
{
562+
Py_VISIT(self->tag);
563+
Py_VISIT(JOIN_OBJ(self->text));
564+
Py_VISIT(JOIN_OBJ(self->tail));
565+
566+
if (self->extra) {
567+
int i;
568+
Py_VISIT(self->extra->attrib);
569+
570+
for (i = 0; i < self->extra->length; ++i)
571+
Py_VISIT(self->extra->children[i]);
572+
}
573+
return 0;
574+
}
575+
576+
static int
577+
element_gc_clear(ElementObject *self)
561578
{
562-
if (self->extra)
579+
PyObject *text = JOIN_OBJ(self->text);
580+
PyObject *tail = JOIN_OBJ(self->tail);
581+
Py_CLEAR(self->tag);
582+
Py_CLEAR(text);
583+
Py_CLEAR(tail);
584+
585+
/* After dropping all references from extra, it's no longer valid anyway,
586+
** so fully deallocate it (see also element_clearmethod)
587+
*/
588+
if (self->extra) {
563589
dealloc_extra(self);
590+
self->extra = NULL;
591+
}
592+
return 0;
593+
}
564594

565-
/* discard attributes */
566-
Py_DECREF(self->tag);
567-
Py_DECREF(JOIN_OBJ(self->text));
568-
Py_DECREF(JOIN_OBJ(self->tail));
595+
static void
596+
element_dealloc(ElementObject* self)
597+
{
598+
PyObject_GC_UnTrack(self);
599+
/* element_gc_clear clears all references and deallocates extra
600+
*/
601+
element_gc_clear(self);
569602

570603
RELEASE(sizeof(ElementObject), "destroy element");
571-
572604
Py_TYPE(self)->tp_free((PyObject *)self);
573605
}
574606

@@ -589,7 +621,7 @@ element_append(ElementObject* self, PyObject* args)
589621
}
590622

591623
static PyObject*
592-
element_clear(ElementObject* self, PyObject* args)
624+
element_clearmethod(ElementObject* self, PyObject* args)
593625
{
594626
if (!PyArg_ParseTuple(args, ":clear"))
595627
return NULL;
@@ -1505,7 +1537,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value)
15051537

15061538
static PyMethodDef element_methods[] = {
15071539

1508-
{"clear", (PyCFunction) element_clear, METH_VARARGS},
1540+
{"clear", (PyCFunction) element_clearmethod, METH_VARARGS},
15091541

15101542
{"get", (PyCFunction) element_get, METH_VARARGS},
15111543
{"set", (PyCFunction) element_set, METH_VARARGS},
@@ -1655,10 +1687,11 @@ static PyTypeObject Element_Type = {
16551687
(getattrofunc)element_getattro, /* tp_getattro */
16561688
0, /* tp_setattro */
16571689
0, /* tp_as_buffer */
1658-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
1690+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
1691+
/* tp_flags */
16591692
0, /* tp_doc */
1660-
0, /* tp_traverse */
1661-
0, /* tp_clear */
1693+
(traverseproc)element_gc_traverse, /* tp_traverse */
1694+
(inquiry)element_gc_clear, /* tp_clear */
16621695
0, /* tp_richcompare */
16631696
0, /* tp_weaklistoffset */
16641697
0, /* tp_iter */

0 commit comments

Comments
 (0)