Skip to content

Commit 9f73517

Browse files
committed
improve detection and tests
1 parent f2b5bb1 commit 9f73517

File tree

2 files changed

+17
-35
lines changed

2 files changed

+17
-35
lines changed

Lib/test/test_xml_etree.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2686,17 +2686,13 @@ def __eq__(self, o):
26862686
del e[:]
26872687
return False
26882688

2689-
# The pure Python implementation raises a ValueError but the C
2690-
# implementation raises a RuntimeError (like OrderedDict does).
2691-
exc_type = ValueError if ET is pyET else RuntimeError
2692-
26932689
e = ET.Element('foo')
26942690
e.extend([X('bar')])
2695-
self.assertRaises(exc_type, e.remove, ET.Element('baz'))
2691+
self.assertRaises(ValueError, e.remove, ET.Element('baz'))
26962692

26972693
e = ET.Element('foo')
26982694
e.extend([ET.Element('bar')])
2699-
self.assertRaises(exc_type, e.remove, X('baz'))
2695+
self.assertRaises(ValueError, e.remove, X('baz'))
27002696

27012697
def test_remove_with_clear_children(self):
27022698
# See: https://github.com/python/cpython/issues/126033
@@ -2706,15 +2702,11 @@ def __eq__(self, o):
27062702
root.clear()
27072703
return False
27082704

2709-
# The pure Python implementation raises a ValueError but the C
2710-
# implementation raises a RuntimeError (like OrderedDict does).
2711-
exc_type = ValueError if ET is pyET else RuntimeError
2712-
27132705
for foo_type, rem_type in [(X, ET.Element), (ET.Element, X)]:
27142706
with self.subTest(foo_type=foo_type, rem_type=rem_type):
27152707
root = ET.Element('.')
27162708
root.extend([foo_type('foo'), rem_type('bar')])
2717-
self.assertRaises(exc_type, root.remove, rem_type('baz'))
2709+
self.assertRaises(ValueError, root.remove, rem_type('baz'))
27182710

27192711
def test_remove_with_mutate_root(self):
27202712
# See: https://github.com/python/cpython/issues/126033
@@ -2731,15 +2723,11 @@ def __eq__(self, o):
27312723
root.remove(first_element)
27322724
return False
27332725

2734-
# The pure Python implementation raises a ValueError but the C
2735-
# implementation raises a RuntimeError (like OrderedDict does).
2736-
exc_type = ValueError if ET is pyET else RuntimeError
2737-
27382726
for bar_type, rem_type in [(X, ET.Element), (ET.Element, X), (X, X)]:
27392727
with self.subTest(bar_type=bar_type, rem_type=rem_type):
27402728
root = ET.Element('.')
27412729
root.extend([first_element, bar_type('bar')])
2742-
self.assertRaises(exc_type, root.remove, rem_type('baz'))
2730+
self.assertRaises(ValueError, root.remove, rem_type('baz'))
27432731

27442732
@support.infinite_recursion(25)
27452733
def test_recursive_repr(self):

Modules/_elementtree.c

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,38 +1634,36 @@ _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement)
16341634
{
16351635
if (self->extra == NULL) {
16361636
/* element has no children, so raise exception */
1637-
goto not_found;
1637+
goto error;
16381638
}
16391639

16401640
Py_ssize_t i;
1641-
const Py_ssize_t length = self->extra->length;
1641+
// When iterating over the list of children, we need to check that the
1642+
// list is not cleared (self->extra != NULL) and that we are still within
1643+
// the correct bounds (i < self->extra->length).
1644+
//
1645+
// We deliberately avoid protecting against children lists that grow
1646+
// faster than the index since list objects do not protect against it.
16421647
for (i = 0; self->extra && i < self->extra->length; i++) {
16431648
if (self->extra->children[i] == subelement) {
16441649
break;
16451650
}
16461651
PyObject *child = Py_NewRef(self->extra->children[i]);
16471652
int rc = PyObject_RichCompareBool(child, subelement, Py_EQ);
16481653
Py_DECREF(child);
1649-
if (self->extra == NULL || self->extra->length != length) {
1650-
goto fail;
1654+
if (rc < 0) {
1655+
return NULL;
16511656
}
1652-
if (rc > 0) {
1657+
else if (rc > 0) {
16531658
break;
16541659
}
1655-
else if (rc < 0) {
1656-
return NULL;
1657-
}
16581660
}
16591661

16601662
// An extra check must be done if the mutation occurs at the very last
16611663
// step and removes or clears the 'extra' list (the condition on the
16621664
// length would not be satisfied any more).
1663-
if (self->extra == NULL || self->extra->length != length) {
1664-
goto fail;
1665-
}
1666-
else if (i >= self->extra->length) {
1667-
/* subelement is not in children, so raise exception */
1668-
goto not_found;
1665+
if (self->extra == NULL || i >= self->extra->length) {
1666+
goto error;
16691667
}
16701668

16711669
PyObject *found = self->extra->children[i];
@@ -1678,13 +1676,9 @@ _elementtree_Element_remove_impl(ElementObject *self, PyObject *subelement)
16781676
Py_DECREF(found);
16791677
Py_RETURN_NONE;
16801678

1681-
not_found:
1679+
error:
16821680
PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");
16831681
return NULL;
1684-
1685-
fail:
1686-
PyErr_SetString(PyExc_RuntimeError, "children mutated during iteration");
1687-
return NULL;
16881682
}
16891683

16901684
static PyObject*

0 commit comments

Comments
 (0)