From 6bbb47109800eec3849cc5e2b4791626141ac09c Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 5 Jun 2026 22:55:04 +0100 Subject: [PATCH 1/3] gh-150988: Fix refleak in `OSError` when attrs are set before `__init__` --- Lib/test/test_exceptions.py | 27 +++++++++++++++++++++++++++ Objects/exceptions.c | 10 +++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 3f5fcb29b442de..b8de02fa4e4e9f 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1714,6 +1714,33 @@ def inner(): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) + def test_oserror_reinit_leak(self): + msg = "some error message" + filename = "some filename" + filename2 = "some filename 2" + + class LeakingOSError(OSError): + def __init__(self, code, message, filename, filename2): + self.strerror = message + self.filename = filename + self.filename2 = filename2 + super().__init__(code, message, filename, None, filename2) + + refcount_msg = sys.getrefcount(msg) + refcount_filename = sys.getrefcount(filename) + refcount_filename2 = sys.getrefcount(filename2) + + for _ in range(5): + try: + raise LeakingOSError(1, msg, filename, filename2) + except OSError: + pass + + gc_collect() + self.assertEqual(sys.getrefcount(msg), refcount_msg) + self.assertEqual(sys.getrefcount(filename), refcount_filename) + self.assertEqual(sys.getrefcount(filename2), refcount_filename2) + def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows with self.assertRaises(OSError) as cm: diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 34a7844c857732..149595e64cec14 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2140,10 +2140,10 @@ oserror_init(PyOSErrorObject *self, PyObject **p_args, return -1; } else { - self->filename = Py_NewRef(filename); + Py_XSETREF(self->filename, Py_NewRef(filename)); if (filename2 && filename2 != Py_None) { - self->filename2 = Py_NewRef(filename2); + Py_XSETREF(self->filename2, Py_NewRef(filename2)); } if (nargs >= 2 && nargs <= 5) { @@ -2158,10 +2158,10 @@ oserror_init(PyOSErrorObject *self, PyObject **p_args, } } } - self->myerrno = Py_XNewRef(myerrno); - self->strerror = Py_XNewRef(strerror); + Py_XSETREF(self->myerrno, Py_XNewRef(myerrno)); + Py_XSETREF(self->strerror, Py_XNewRef(strerror)); #ifdef MS_WINDOWS - self->winerror = Py_XNewRef(winerror); + Py_XSETREF(self->winerror, Py_XNewRef(winerror)); #endif /* Steals the reference to args */ From 849e9049eace59de22268b9a060baa01b5ee4cfd Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 5 Jun 2026 22:55:12 +0100 Subject: [PATCH 2/3] Add news entry --- .../2026-06-05-22-52-41.gh-issue-150988.fDKfMJ.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-22-52-41.gh-issue-150988.fDKfMJ.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-22-52-41.gh-issue-150988.fDKfMJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-22-52-41.gh-issue-150988.fDKfMJ.rst new file mode 100644 index 00000000000000..9f768a58d4c81b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-22-52-41.gh-issue-150988.fDKfMJ.rst @@ -0,0 +1,2 @@ +Fix a reference leak in :exc:`OSError` when attributes are set before +``super().__init__``. From 3efc97e8547ffd665cdec286b68125515036e917 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 5 Jun 2026 23:02:39 +0100 Subject: [PATCH 3/3] Remove trailing whitespace --- Lib/test/test_exceptions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index b8de02fa4e4e9f..e27d4aac4e99de 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1718,24 +1718,24 @@ def test_oserror_reinit_leak(self): msg = "some error message" filename = "some filename" filename2 = "some filename 2" - + class LeakingOSError(OSError): def __init__(self, code, message, filename, filename2): self.strerror = message self.filename = filename self.filename2 = filename2 super().__init__(code, message, filename, None, filename2) - + refcount_msg = sys.getrefcount(msg) refcount_filename = sys.getrefcount(filename) refcount_filename2 = sys.getrefcount(filename2) - + for _ in range(5): try: raise LeakingOSError(1, msg, filename, filename2) except OSError: pass - + gc_collect() self.assertEqual(sys.getrefcount(msg), refcount_msg) self.assertEqual(sys.getrefcount(filename), refcount_filename)