Skip to content

Commit 1ff7ad9

Browse files
committed
gh-150988: Fix OSError reference leak in oserror_init()
Use Py_XSETREF when assigning errno, strerror, filename, filename2, and winerror so pre-existing references from subclass __init__ are released.
1 parent ce916dc commit 1ff7ad9

3 files changed

Lines changed: 51 additions & 5 deletions

File tree

Lib/test/test_exceptions.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,6 +1714,50 @@ def inner():
17141714
gc_collect() # For PyPy or other GCs.
17151715
self.assertEqual(wr(), None)
17161716

1717+
@cpython_only
1718+
def test_oserror_refleak_attributes_before_init(self):
1719+
# gh-150988: no leak when a subclass sets OSError fields before
1720+
# super().__init__(), or when __init__ is called more than once.
1721+
import gc
1722+
1723+
class LeakingOSError(OSError):
1724+
def __init__(self, code, message, filename, filename2):
1725+
self.strerror = message
1726+
self.filename = filename
1727+
self.filename2 = filename2
1728+
super().__init__(code, message, filename, None, filename2)
1729+
1730+
msg = "some error message"
1731+
filename = "some filename"
1732+
filename2 = "some filename 2"
1733+
refcount_msg = sys.getrefcount(msg)
1734+
refcount_filename = sys.getrefcount(filename)
1735+
refcount_filename2 = sys.getrefcount(filename2)
1736+
1737+
for _ in range(5):
1738+
try:
1739+
raise LeakingOSError(1, msg, filename, filename2)
1740+
except OSError:
1741+
pass
1742+
1743+
gc_collect()
1744+
self.assertEqual(sys.getrefcount(msg), refcount_msg)
1745+
self.assertEqual(sys.getrefcount(filename), refcount_filename)
1746+
self.assertEqual(sys.getrefcount(filename2), refcount_filename2)
1747+
1748+
exc = LeakingOSError(1, msg, filename, filename2)
1749+
exc.__init__(2, msg, filename, filename2)
1750+
self.assertEqual(exc.errno, 2)
1751+
self.assertEqual(exc.strerror, msg)
1752+
self.assertEqual(exc.filename, filename)
1753+
self.assertEqual(exc.filename2, filename2)
1754+
del exc
1755+
1756+
gc_collect()
1757+
self.assertEqual(sys.getrefcount(msg), refcount_msg)
1758+
self.assertEqual(sys.getrefcount(filename), refcount_filename)
1759+
self.assertEqual(sys.getrefcount(filename2), refcount_filename2)
1760+
17171761
def test_errno_ENOTDIR(self):
17181762
# Issue #12802: "not a directory" errors are ENOTDIR even on Windows
17191763
with self.assertRaises(OSError) as cm:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a reference leak in :exc:`OSError` when attributes are set before
2+
``super().__init__()`` or when :meth:`~object.__init__` runs more than once.

Objects/exceptions.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,10 +2140,10 @@ oserror_init(PyOSErrorObject *self, PyObject **p_args,
21402140
return -1;
21412141
}
21422142
else {
2143-
self->filename = Py_NewRef(filename);
2143+
Py_XSETREF(self->filename, Py_NewRef(filename));
21442144

21452145
if (filename2 && filename2 != Py_None) {
2146-
self->filename2 = Py_NewRef(filename2);
2146+
Py_XSETREF(self->filename2, Py_NewRef(filename2));
21472147
}
21482148

21492149
if (nargs >= 2 && nargs <= 5) {
@@ -2158,10 +2158,10 @@ oserror_init(PyOSErrorObject *self, PyObject **p_args,
21582158
}
21592159
}
21602160
}
2161-
self->myerrno = Py_XNewRef(myerrno);
2162-
self->strerror = Py_XNewRef(strerror);
2161+
Py_XSETREF(self->myerrno, Py_XNewRef(myerrno));
2162+
Py_XSETREF(self->strerror, Py_XNewRef(strerror));
21632163
#ifdef MS_WINDOWS
2164-
self->winerror = Py_XNewRef(winerror);
2164+
Py_XSETREF(self->winerror, Py_XNewRef(winerror));
21652165
#endif
21662166

21672167
/* Steals the reference to args */

0 commit comments

Comments
 (0)