Skip to content

Commit 615069e

Browse files
authored
bpo-44184: Fix subtype_dealloc() for freed type (GH-26274)
Fix a crash at Python exit when a deallocator function removes the last strong reference to a heap type. Don't read type memory after calling basedealloc() since basedealloc() can deallocate the type and free its memory. _PyMem_IsPtrFreed() argument is now constant.
1 parent 642fdfd commit 615069e

File tree

4 files changed

+46
-4
lines changed

4 files changed

+46
-4
lines changed

Include/internal/pycore_pymem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ PyAPI_FUNC(int) _PyMem_SetDefaultAllocator(
4242
fills newly allocated memory with CLEANBYTE (0xCD) and newly freed memory
4343
with DEADBYTE (0xDD). Detect also "untouchable bytes" marked
4444
with FORBIDDENBYTE (0xFD). */
45-
static inline int _PyMem_IsPtrFreed(void *ptr)
45+
static inline int _PyMem_IsPtrFreed(const void *ptr)
4646
{
4747
uintptr_t value = (uintptr_t)ptr;
4848
#if SIZEOF_VOID_P == 8

Lib/test/test_gc.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,34 @@ def __del__(self):
13611361
# empty __dict__.
13621362
self.assertEqual(x, None)
13631363

1364+
1365+
class PythonFinalizationTests(unittest.TestCase):
1366+
def test_ast_fini(self):
1367+
# bpo-44184: Regression test for subtype_dealloc() when deallocating
1368+
# an AST instance also destroy its AST type: subtype_dealloc() must
1369+
# not access the type memory after deallocating the instance, since
1370+
# the type memory can be freed as well. The test is also related to
1371+
# _PyAST_Fini() which clears references to AST types.
1372+
code = textwrap.dedent("""
1373+
import ast
1374+
import codecs
1375+
1376+
# Small AST tree to keep their AST types alive
1377+
tree = ast.parse("def f(x, y): return 2*x-y")
1378+
x = [tree]
1379+
x.append(x)
1380+
1381+
# Put the cycle somewhere to survive until the last GC collection.
1382+
# Codec search functions are only cleared at the end of
1383+
# interpreter_clear().
1384+
def search_func(encoding):
1385+
return None
1386+
search_func.a = x
1387+
codecs.register(search_func)
1388+
""")
1389+
assert_python_ok("-c", code)
1390+
1391+
13641392
def test_main():
13651393
enabled = gc.isenabled()
13661394
gc.disable()
@@ -1370,7 +1398,11 @@ def test_main():
13701398

13711399
try:
13721400
gc.collect() # Delete 2nd generation garbage
1373-
run_unittest(GCTests, GCTogglingTests, GCCallbackTests)
1401+
run_unittest(
1402+
GCTests,
1403+
GCCallbackTests,
1404+
GCTogglingTests,
1405+
PythonFinalizationTests)
13741406
finally:
13751407
gc.set_debug(debug)
13761408
# test gc.enable() even if GC is disabled by default
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash at Python exit when a deallocator function removes the last strong
2+
reference to a heap type.
3+
Patch by Victor Stinner.

Objects/typeobject.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,15 +1446,22 @@ subtype_dealloc(PyObject *self)
14461446
if (_PyType_IS_GC(base)) {
14471447
_PyObject_GC_TRACK(self);
14481448
}
1449+
1450+
// Don't read type memory after calling basedealloc() since basedealloc()
1451+
// can deallocate the type and free its memory.
1452+
int type_needs_decref = (type->tp_flags & Py_TPFLAGS_HEAPTYPE
1453+
&& !(base->tp_flags & Py_TPFLAGS_HEAPTYPE));
1454+
14491455
assert(basedealloc);
14501456
basedealloc(self);
14511457

14521458
/* Can't reference self beyond this point. It's possible tp_del switched
14531459
our type from a HEAPTYPE to a non-HEAPTYPE, so be careful about
14541460
reference counting. Only decref if the base type is not already a heap
14551461
allocated type. Otherwise, basedealloc should have decref'd it already */
1456-
if (type->tp_flags & Py_TPFLAGS_HEAPTYPE && !(base->tp_flags & Py_TPFLAGS_HEAPTYPE))
1457-
Py_DECREF(type);
1462+
if (type_needs_decref) {
1463+
Py_DECREF(type);
1464+
}
14581465

14591466
endlabel:
14601467
Py_TRASHCAN_END

0 commit comments

Comments
 (0)