From 9370f4f877b1dfaabbfef8a3c01f692ac65ee99a Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sat, 21 Mar 2026 07:50:06 -0400 Subject: [PATCH 1/6] gh-146250: Fix memory leak of re-initialization of `SyntaxError` Added `Py_XDECREF`/`Py_XSETREF` to avoid a memory leak when calling `SyntaxError.__init__`. --- Lib/test/test_exceptions.py | 6 ++++++ Objects/exceptions.c | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 7354f8281d9682..cd2ee93728ffd0 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1954,6 +1954,12 @@ def test_exec_set_nomemory_hang(self): self.assertGreater(len(output), 0) # At minimum, should not hang self.assertIn(b"MemoryError", output) + # gh-146250: memory leak with re-initialization of SyntaxError + def test_syntax_error_memory_leak(self): + # Test crashes with ASan + e = SyntaxError("msg", ("file.py", 1, 2, "txt", 2, 3)) + e.__init__("new_msg", ("new_file.py", 2, 3, "new_txt", 3, 4)) + class NameErrorTests(unittest.TestCase): def test_name_error_has_name(self): diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 32b0fcec6c4542..d97ef141cdb512 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2802,8 +2802,14 @@ SyntaxError_init(PyObject *op, PyObject *args, PyObject *kwds) return -1; } - self->end_lineno = NULL; - self->end_offset = NULL; + Py_XDECREF(self->filename); + Py_XDECREF(self->lineno); + Py_XDECREF(self->offset); + Py_XDECREF(self->text); + Py_XSETREF(self->end_lineno, NULL); + Py_XSETREF(self->end_offset, NULL); + Py_XDECREF(self->metadata); + if (!PyArg_ParseTuple(info, "OOOO|OOO", &self->filename, &self->lineno, &self->offset, &self->text, From 2371ae9e752bf5c1e700926a2729aa48953310b2 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:55:17 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst new file mode 100644 index 00000000000000..2df8c420b495f5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst @@ -0,0 +1 @@ +Fixed a memory leak in :class:`SyntaxError` when re-initializing it. From f66f5aef8105150a7ee8ba69a3f78e718a84343e Mon Sep 17 00:00:00 2001 From: Brij Kapadia <97006829+bkap123@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:16:42 -0400 Subject: [PATCH 3/6] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: AN Long Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_exceptions.py | 1 - .../2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index cd2ee93728ffd0..daa6823228c0ee 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1956,7 +1956,6 @@ def test_exec_set_nomemory_hang(self): # gh-146250: memory leak with re-initialization of SyntaxError def test_syntax_error_memory_leak(self): - # Test crashes with ASan e = SyntaxError("msg", ("file.py", 1, 2, "txt", 2, 3)) e.__init__("new_msg", ("new_file.py", 2, 3, "new_txt", 3, 4)) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst index 2df8c420b495f5..fff07b31ec21c4 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-11-55-16.gh-issue-146250.ahl3O2.rst @@ -1 +1 @@ -Fixed a memory leak in :class:`SyntaxError` when re-initializing it. +Fixed a memory leak in :exc:`SyntaxError` when re-initializing it. From 601d63f0d603015dddefc1c0c60a9876ce7034de Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:41:41 -0400 Subject: [PATCH 4/6] Updated variable memory management and moved test --- Lib/test/test_exceptions.py | 10 +++++----- Objects/exceptions.c | 32 ++++++++++++++------------------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index daa6823228c0ee..018a1f7b2ddac1 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1954,11 +1954,6 @@ def test_exec_set_nomemory_hang(self): self.assertGreater(len(output), 0) # At minimum, should not hang self.assertIn(b"MemoryError", output) - # gh-146250: memory leak with re-initialization of SyntaxError - def test_syntax_error_memory_leak(self): - e = SyntaxError("msg", ("file.py", 1, 2, "txt", 2, 3)) - e.__init__("new_msg", ("new_file.py", 2, 3, "new_txt", 3, 4)) - class NameErrorTests(unittest.TestCase): def test_name_error_has_name(self): @@ -2566,6 +2561,11 @@ def test_incorrect_constructor(self): args = ("bad.py", 1, 2, "abcdefg", 1) self.assertRaises(TypeError, SyntaxError, "bad bad", args) + def test_syntax_error_memory_leak(self): + # gh-146250: memory leak with re-initialization of SyntaxError + e = SyntaxError("msg", ("file.py", 1, 2, "txt", 2, 3)) + e.__init__("new_msg", ("new_file.py", 2, 3, "new_txt", 3, 4)) + class TestInvalidExceptionMatcher(unittest.TestCase): def test_except_star_invalid_exception_type(self): diff --git a/Objects/exceptions.c b/Objects/exceptions.c index d97ef141cdb512..7cec335d62de7a 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2802,29 +2802,25 @@ SyntaxError_init(PyObject *op, PyObject *args, PyObject *kwds) return -1; } - Py_XDECREF(self->filename); - Py_XDECREF(self->lineno); - Py_XDECREF(self->offset); - Py_XDECREF(self->text); - Py_XSETREF(self->end_lineno, NULL); - Py_XSETREF(self->end_offset, NULL); - Py_XDECREF(self->metadata); - + PyObject *filename, *lineno, *offset, *text; + PyObject *end_lineno = NULL; + PyObject *end_offset = NULL; + PyObject *metadata = NULL; if (!PyArg_ParseTuple(info, "OOOO|OOO", - &self->filename, &self->lineno, - &self->offset, &self->text, - &self->end_lineno, &self->end_offset, &self->metadata)) { + &filename, &lineno, + &offset, &text, + &end_lineno, &end_offset, &metadata)) { Py_DECREF(info); return -1; } - Py_INCREF(self->filename); - Py_INCREF(self->lineno); - Py_INCREF(self->offset); - Py_INCREF(self->text); - Py_XINCREF(self->end_lineno); - Py_XINCREF(self->end_offset); - Py_XINCREF(self->metadata); + Py_XSETREF(self->filename, Py_NewRef(filename)); + Py_XSETREF(self->lineno, Py_NewRef(lineno)); + Py_XSETREF(self->offset, Py_NewRef(offset)); + Py_XSETREF(self->text, Py_XNewRef(text)); + Py_XSETREF(self->end_lineno, Py_XNewRef(end_lineno)); + Py_XSETREF(self->end_offset, Py_XNewRef(end_offset)); + Py_XSETREF(self->metadata, Py_XNewRef(metadata)); Py_DECREF(info); if (self->end_lineno != NULL && self->end_offset == NULL) { From dff89fe12a21d4d2424aa3fd77b60444917320c1 Mon Sep 17 00:00:00 2001 From: Brij Kapadia <97006829+bkap123@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:43:12 -0400 Subject: [PATCH 5/6] small change --- Objects/exceptions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 7cec335d62de7a..c4a591e2cf7f8c 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2817,7 +2817,7 @@ SyntaxError_init(PyObject *op, PyObject *args, PyObject *kwds) Py_XSETREF(self->filename, Py_NewRef(filename)); Py_XSETREF(self->lineno, Py_NewRef(lineno)); Py_XSETREF(self->offset, Py_NewRef(offset)); - Py_XSETREF(self->text, Py_XNewRef(text)); + Py_XSETREF(self->text, Py_NewRef(text)); Py_XSETREF(self->end_lineno, Py_XNewRef(end_lineno)); Py_XSETREF(self->end_offset, Py_XNewRef(end_offset)); Py_XSETREF(self->metadata, Py_XNewRef(metadata)); From 22cff11dffc34d88d0303fcc447ae7e930760225 Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:48:24 -0400 Subject: [PATCH 6/6] Check (optional) attributes update --- Lib/test/test_exceptions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 018a1f7b2ddac1..3f5fcb29b442de 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2565,6 +2565,25 @@ def test_syntax_error_memory_leak(self): # gh-146250: memory leak with re-initialization of SyntaxError e = SyntaxError("msg", ("file.py", 1, 2, "txt", 2, 3)) e.__init__("new_msg", ("new_file.py", 2, 3, "new_txt", 3, 4)) + self.assertEqual(e.msg, "new_msg") + self.assertEqual(e.args, ("new_msg", ("new_file.py", 2, 3, "new_txt", 3, 4))) + self.assertEqual(e.filename, "new_file.py") + self.assertEqual(e.lineno, 2) + self.assertEqual(e.offset, 3) + self.assertEqual(e.text, "new_txt") + self.assertEqual(e.end_lineno, 3) + self.assertEqual(e.end_offset, 4) + + e = SyntaxError("msg", ("file.py", 1, 2, "txt", 2, 3)) + e.__init__("new_msg", ("new_file.py", 2, 3, "new_txt")) + self.assertEqual(e.msg, "new_msg") + self.assertEqual(e.args, ("new_msg", ("new_file.py", 2, 3, "new_txt"))) + self.assertEqual(e.filename, "new_file.py") + self.assertEqual(e.lineno, 2) + self.assertEqual(e.offset, 3) + self.assertEqual(e.text, "new_txt") + self.assertIsNone(e.end_lineno) + self.assertIsNone(e.end_offset) class TestInvalidExceptionMatcher(unittest.TestCase):