diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 3f5fcb29b442de..e27d4aac4e99de 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/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__``. 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 */