From 1721f62804ca59eebc4d53dd9f0cbd46b66ce86e Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:57:35 +0200 Subject: [PATCH] Udpate `test_pyexpat.py` from 3.14.4 --- Lib/test/test_pyexpat.py | 62 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index e28c15de6..015053d1c 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -706,6 +706,25 @@ class ElementDeclHandlerTest(unittest.TestCase): parser.ElementDeclHandler = lambda _1, _2: None self.assertRaises(TypeError, parser.Parse, data, True) + @support.skip_if_unlimited_stack_size + @support.skip_emscripten_stack_overflow() + @support.skip_wasi_stack_overflow() + def test_deeply_nested_content_model(self): + # This should raise a RecursionError and not crash. + # See https://github.com/python/cpython/issues/145986. + N = 500_000 + data = ( + b'\n]>\n\n' + ) + + parser = expat.ParserCreate() + parser.ElementDeclHandler = lambda _1, _2: None + with support.infinite_recursion(): + with self.assertRaises(RecursionError): + parser.Parse(data) + class MalformedInputTest(unittest.TestCase): @unittest.expectedFailure # TODO: RUSTPYTHON def test1(self): @@ -838,6 +857,45 @@ class ParentParserLifetimeTest(unittest.TestCase): del subparser +class ExternalEntityParserCreateErrorTest(unittest.TestCase): + """ExternalEntityParserCreate error paths should not crash or leak + refcounts on the parent parser. + + See https://github.com/python/cpython/issues/144984. + """ + + @classmethod + def setUpClass(cls): + cls.testcapi = import_helper.import_module('_testcapi') + + @unittest.skipIf(support.Py_TRACE_REFS, + 'Py_TRACE_REFS conflicts with testcapi.set_nomemory') + def test_error_path_no_crash(self): + # When an allocation inside ExternalEntityParserCreate fails, + # the partially-initialized subparser is deallocated. This + # must not dereference NULL handlers or double-decrement the + # parent parser's refcount. + parser = expat.ParserCreate() + parser.buffer_text = True + rc_before = sys.getrefcount(parser) + + # We avoid self.assertRaises(MemoryError) here because the + # context manager itself needs memory allocations that fail + # while the nomemory hook is active. + self.testcapi.set_nomemory(1, 10) + raised = False + try: + parser.ExternalEntityParserCreate(None) + except MemoryError: + raised = True + finally: + self.testcapi.remove_mem_hooks() + self.assertTrue(raised, "MemoryError not raised") + + rc_after = sys.getrefcount(parser) + self.assertEqual(rc_after, rc_before) + + class ReparseDeferralTest(unittest.TestCase): @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled' def test_getter_setter_round_trip(self): @@ -1028,7 +1086,9 @@ class AttackProtectionTestBase(abc.ABC): self.assert_root_parser_failure(setter, 123.45) -@unittest.skipIf(expat.version_info < (2, 7, 2), "requires Expat >= 2.7.2") +@unittest.skipIf(not hasattr(expat.XMLParserType, + "SetAllocTrackerMaximumAmplification"), + "requires Python compiled with Expat >= 2.7.2") class MemoryProtectionTest(AttackProtectionTestBase, unittest.TestCase): # NOTE: with the default Expat configuration, the billion laughs protection