https://github.com/python/cpython/commit/5f1b710a2883bd5a4af25d055b60c512d568feea
commit: 5f1b710a2883bd5a4af25d055b60c512d568feea
branch: 3.14
author: Stan Ulbrych <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-04-22T22:19:25+01:00
summary:
[3.14] gh-148801: Fix unbound C recursion in `Element.__deepcopy__()`
(GH-148802) (#148842)
(cherry picked from commit 33e82be1746a964b595b2bba64f38a5787681eb3)
files:
A Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
M Lib/test/test_xml_etree.py
M Modules/_elementtree.c
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 0b343cc4bb8371..b207dbe68be94c 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -3098,6 +3098,19 @@ def __deepcopy__(self, memo):
self.assertEqual([c.tag for c in children[3:]],
[a.tag, b.tag, a.tag, b.tag])
+ @support.skip_if_unlimited_stack_size
+ @support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
+ def test_deeply_nested_deepcopy(self):
+ # This should raise a RecursionError and not crash.
+ # See https://github.com/python/cpython/issues/148801.
+ root = cur = ET.Element('s')
+ for _ in range(150_000):
+ cur = ET.SubElement(cur, 'u')
+ with support.infinite_recursion():
+ with self.assertRaises(RecursionError):
+ copy.deepcopy(root)
+
class MutationDeleteElementPath(str):
def __new__(cls, elem, *args):
diff --git
a/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
new file mode 100644
index 00000000000000..6fcd30e8f057b9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst
@@ -0,0 +1,2 @@
+:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__
+<object.__deepcopy__>` on deeply nested trees.
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 22d3205e6ad314..496864175725e6 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -16,6 +16,7 @@
#endif
#include "Python.h"
+#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
#include "pycore_pyhash.h" // _Py_HashSecret
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
@@ -802,26 +803,31 @@ _elementtree_Element___deepcopy___impl(ElementObject
*self, PyObject *memo)
/*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/
{
Py_ssize_t i;
- ElementObject* element;
+ ElementObject* element = NULL;
PyObject* tag;
PyObject* attrib;
PyObject* text;
PyObject* tail;
PyObject* id;
+ if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) {
+ return NULL;
+ }
+
PyTypeObject *tp = Py_TYPE(self);
elementtreestate *st = get_elementtree_state_by_type(tp);
// The deepcopy() helper takes care of incrementing the refcount
// of the object to copy so to avoid use-after-frees.
tag = deepcopy(st, self->tag, memo);
- if (!tag)
- return NULL;
+ if (!tag) {
+ goto error;
+ }
if (self->extra && self->extra->attrib) {
attrib = deepcopy(st, self->extra->attrib, memo);
if (!attrib) {
Py_DECREF(tag);
- return NULL;
+ goto error;
}
} else {
attrib = NULL;
@@ -832,8 +838,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self,
PyObject *memo)
Py_DECREF(tag);
Py_XDECREF(attrib);
- if (!element)
- return NULL;
+ if (!element) {
+ goto error;
+ }
text = deepcopy(st, JOIN_OBJ(self->text), memo);
if (!text)
@@ -895,10 +902,12 @@ _elementtree_Element___deepcopy___impl(ElementObject
*self, PyObject *memo)
if (i < 0)
goto error;
+ _Py_LeaveRecursiveCall();
return (PyObject*) element;
error:
- Py_DECREF(element);
+ _Py_LeaveRecursiveCall();
+ Py_XDECREF(element);
return NULL;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]