self.assertEqual(handler_call_args, [("bar", "baz")])
+class ParentParserLifetimeTest(unittest.TestCase):
+ """
+ Subparsers make use of their parent XML_Parser inside of Expat.
+ As a result, parent parsers need to outlive subparsers.
+
+ See https://github.com/python/cpython/issues/139400.
+ """
+
+ def test_parent_parser_outlives_its_subparsers__single(self):
+ parser = expat.ParserCreate()
+ subparser = parser.ExternalEntityParserCreate(None)
+
+ # Now try to cause garbage collection of the parent parser
+ # while it's still being referenced by a related subparser.
+ del parser
+
+ def test_parent_parser_outlives_its_subparsers__multiple(self):
+ parser = expat.ParserCreate()
+ subparser_one = parser.ExternalEntityParserCreate(None)
+ subparser_two = parser.ExternalEntityParserCreate(None)
+
+ # Now try to cause garbage collection of the parent parser
+ # while it's still being referenced by a related subparser.
+ del parser
+
+ def test_parent_parser_outlives_its_subparsers__chain(self):
+ parser = expat.ParserCreate()
+ subparser = parser.ExternalEntityParserCreate(None)
+ subsubparser = subparser.ExternalEntityParserCreate(None)
+
+ # Now try to cause garbage collection of the parent parsers
+ # while they are still being referenced by a related subparser.
+ del parser
+ del subparser
+
+
class ReparseDeferralTest(unittest.TestCase):
def test_getter_setter_round_trip(self):
parser = expat.ParserCreate()
PyObject_HEAD
XML_Parser itself;
+ /*
+ * Strong reference to a parent `xmlparseobject` if this parser
+ * is a child parser. Set to NULL if this parser is a root parser.
+ * This is needed to keep the parent parser alive as long as it has
+ * at least one child parser.
+ *
+ * See https://github.com/python/cpython/issues/139400 for details.
+ */
+ PyObject *parent;
int ordered_attributes; /* Return attributes as a list. */
int specified_attributes; /* Report only specified attributes. */
int in_callback; /* Is a callback active? */
return NULL;
}
+ // The new subparser will make use of the parent XML_Parser inside of Expat.
+ // So we need to take subparsers into account with the reference counting
+ // of their parent parser.
+ Py_INCREF(self);
+
new_parser->buffer_size = self->buffer_size;
new_parser->buffer_used = 0;
new_parser->buffer = NULL;
new_parser->ns_prefixes = self->ns_prefixes;
new_parser->itself = XML_ExternalEntityParserCreate(self->itself, context,
encoding);
+ new_parser->parent = (PyObject *)self;
new_parser->handlers = 0;
new_parser->intern = Py_XNewRef(self->intern);
new_parser->buffer = PyMem_Malloc(new_parser->buffer_size);
if (new_parser->buffer == NULL) {
Py_DECREF(new_parser);
+ Py_DECREF(self);
return PyErr_NoMemory();
}
}
if (!new_parser->itself) {
Py_DECREF(new_parser);
+ Py_DECREF(self);
return PyErr_NoMemory();
}
new_parser->handlers = PyMem_New(PyObject *, i);
if (!new_parser->handlers) {
Py_DECREF(new_parser);
+ Py_DECREF(self);
return PyErr_NoMemory();
}
clear_handlers(new_parser, 1);
/* namespace_separator is either NULL or contains one char + \0 */
self->itself = XML_ParserCreate_MM(encoding, &ExpatMemoryHandler,
namespace_separator);
+ self->parent = NULL;
if (self->itself == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"XML_ParserCreate failed");
for (int i = 0; handler_info[i].name != NULL; i++) {
Py_VISIT(op->handlers[i]);
}
+ Py_VISIT(op->parent);
Py_VISIT(Py_TYPE(op));
return 0;
}
{
clear_handlers(op, 0);
Py_CLEAR(op->intern);
+ // NOTE: We cannot call Py_CLEAR(op->parent) prior to calling
+ // XML_ParserFree(op->itself), or a subparser could lose its parent
+ // XML_Parser while still making use of it internally.
+ // https://github.com/python/cpython/issues/139400
return 0;
}
if (self->itself != NULL)
XML_ParserFree(self->itself);
self->itself = NULL;
+ Py_CLEAR(self->parent);
if (self->handlers != NULL) {
PyMem_Free(self->handlers);