[3.12] gh-139400: Make sure that parent parsers outlive their subparsers in pyexpat (GH-139403) (GH-139609)
Some checks failed
Lint / lint (push) Has been cancelled
Tests / Change detection (push) Has been cancelled
Tests / All required checks pass (push) Has been cancelled
Tests / (push) Has been cancelled
Tests / Windows MSI (push) Has been cancelled
Tests / Docs (push) Has been cancelled
Tests / Check if the ABI has changed (push) Has been cancelled
Tests / Check if Autoconf files are up to date (push) Has been cancelled
Tests / Check if generated files are up to date (push) Has been cancelled
Tests / Ubuntu SSL tests with OpenSSL (push) Has been cancelled
Tests / Hypothesis tests on Ubuntu (push) Has been cancelled
Tests / Address sanitizer (push) Has been cancelled

Within libexpat, a parser created via `XML_ExternalEntityParserCreate`
is relying on its parent parser throughout its entire lifetime.
Prior to this fix, is was possible for the parent parser to be
garbage-collected too early.

(cherry picked from commit 6edb2ddb5f)
This commit is contained in:
Sebastian Pipping 2025-10-07 13:56:31 +02:00 committed by GitHub
parent f9f8cb92c9
commit dea7e3d5f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 0 deletions

View file

@ -758,6 +758,42 @@ class ForeignDTDTests(unittest.TestCase):
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()