From 375e1e479d99feb54e3451880d3c94f7d9961bd3 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Sat, 26 Sep 2026 23:46:27 +0800 Subject: [PATCH 1/4] Prevent double release of assembler byte writers --- .../2026-09-26-23-44-44.gh-issue-158241.NZ9OSU.rst | 2 ++ Python/assemble.c | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-23-44-44.gh-issue-158241.NZ9OSU.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-23-44-44.gh-issue-158241.NZ9OSU.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-23-44-44.gh-issue-158241.NZ9OSU.rst new file mode 100644 index 000000000000000..fd64c9b695ff2f9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-23-44-44.gh-issue-158241.NZ9OSU.rst @@ -0,0 +1,2 @@ +Fix a crash and possible code object corruption following a +:exc:`MemoryError` while compiling Python code. diff --git a/Python/assemble.c b/Python/assemble.c index db9efff5e08ca96..2c8abb116b2e336 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -81,8 +81,11 @@ assemble_init(struct assembler *a, int firstlineno) return SUCCESS; error: PyBytesWriter_Discard(a->a_bytecode_writer); + a->a_bytecode_writer = NULL; PyBytesWriter_Discard(a->a_linetable_writer); + a->a_linetable_writer = NULL; PyBytesWriter_Discard(a->a_except_table_writer); + a->a_except_table_writer = NULL; return ERROR; } From 8bf2dc2dfa3e195bd324a894f0de96159f2551f4 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Sun, 27 Sep 2026 18:42:09 +0800 Subject: [PATCH 2/4] Let assemble_free() handle initialization failures --- Python/assemble.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Python/assemble.c b/Python/assemble.c index 2c8abb116b2e336..78bff89658e56d5 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -68,25 +68,17 @@ assemble_init(struct assembler *a, int firstlineno) a->a_lineno = firstlineno; a->a_bytecode_writer = PyBytesWriter_Create(DEFAULT_CODE_SIZE); if (a->a_bytecode_writer == NULL) { - goto error; + return ERROR; } a->a_linetable_writer = PyBytesWriter_Create(DEFAULT_CNOTAB_SIZE); if (a->a_linetable_writer == NULL) { - goto error; + return ERROR; } a->a_except_table_writer = PyBytesWriter_Create(DEFAULT_LNOTAB_SIZE); if (a->a_except_table_writer == NULL) { - goto error; + return ERROR; } return SUCCESS; -error: - PyBytesWriter_Discard(a->a_bytecode_writer); - a->a_bytecode_writer = NULL; - PyBytesWriter_Discard(a->a_linetable_writer); - a->a_linetable_writer = NULL; - PyBytesWriter_Discard(a->a_except_table_writer); - a->a_except_table_writer = NULL; - return ERROR; } static void From ec8ca3b07a33284e4814e0e51d48c78745fbd760 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Mon, 28 Sep 2026 09:44:05 +0800 Subject: [PATCH 3/4] add test --- Lib/test/test_compile.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 553ac70d83a802c..86de7a53c5fc92c 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -39,6 +39,32 @@ def test_no_ending_newline(self): def test_empty(self): compile("", "", "exec") + @support.requires_subprocess() + @support.nomemtest + def test_assemble_init_allocation_failure(self): + code = textwrap.dedent("""\ + import _testcapi + + expected = compile("x", "", "exec") + failures = 0 + for n in range(1, 500): + _testcapi.set_nomemory(n, n + 1) + try: + compile("x", "", "exec") + except MemoryError: + failures += 1 + finally: + _testcapi.remove_mem_hooks() + + actual = compile("x", "", "exec") + print(failures, actual.co_code == expected.co_code, + actual.co_linetable == expected.co_linetable) + """) + _, output, _ = script_helper.assert_python_ok('-c', code) + failures, code_matches, linetable_matches = output.split() + self.assertGreater(int(failures), 0) + self.assertEqual((code_matches, linetable_matches), (b'True', b'True')) + def test_other_newlines(self): compile("\r\n", "", "exec") compile("\r", "", "exec") From 0f808d85542b8d240afedf81ff8319c46c383963 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Mon, 28 Sep 2026 11:05:01 +0800 Subject: [PATCH 4/4] update --- Lib/test/test_compile.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 86de7a53c5fc92c..4a8c10e368c8678 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -56,14 +56,12 @@ def test_assemble_init_allocation_failure(self): finally: _testcapi.remove_mem_hooks() + assert failures > 0 actual = compile("x", "", "exec") - print(failures, actual.co_code == expected.co_code, - actual.co_linetable == expected.co_linetable) + assert actual.co_code == expected.co_code + assert actual.co_linetable == expected.co_linetable """) - _, output, _ = script_helper.assert_python_ok('-c', code) - failures, code_matches, linetable_matches = output.split() - self.assertGreater(int(failures), 0) - self.assertEqual((code_matches, linetable_matches), (b'True', b'True')) + script_helper.assert_python_ok('-c', code) def test_other_newlines(self): compile("\r\n", "", "exec")