diff options
author | Victor Stinner <vstinner@python.org> | 2021-05-21 17:19:54 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-21 17:19:54 (GMT) |
commit | 615069eb08494d089bf24e43547fbc482ed699b8 (patch) | |
tree | 06d4a3c69bb0f8078f4021aee8c19d2924a2f774 | |
parent | 642fdfdc04e8e40c2cd3e65d67b09fac472bb345 (diff) | |
download | cpython-615069eb08494d089bf24e43547fbc482ed699b8.zip cpython-615069eb08494d089bf24e43547fbc482ed699b8.tar.gz cpython-615069eb08494d089bf24e43547fbc482ed699b8.tar.bz2 |
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.
-rw-r--r-- | Include/internal/pycore_pymem.h | 2 | ||||
-rw-r--r-- | Lib/test/test_gc.py | 34 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2021-05-21-01-42-45.bpo-44184.9qOptC.rst | 3 | ||||
-rw-r--r-- | Objects/typeobject.c | 11 |
4 files changed, 46 insertions, 4 deletions
diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index d59ab49..30db3f2 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -42,7 +42,7 @@ PyAPI_FUNC(int) _PyMem_SetDefaultAllocator( fills newly allocated memory with CLEANBYTE (0xCD) and newly freed memory with DEADBYTE (0xDD). Detect also "untouchable bytes" marked with FORBIDDENBYTE (0xFD). */ -static inline int _PyMem_IsPtrFreed(void *ptr) +static inline int _PyMem_IsPtrFreed(const void *ptr) { uintptr_t value = (uintptr_t)ptr; #if SIZEOF_VOID_P == 8 diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index ee4fe49..1528cf6 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1361,6 +1361,34 @@ class GCTogglingTests(unittest.TestCase): # empty __dict__. self.assertEqual(x, None) + +class PythonFinalizationTests(unittest.TestCase): + def test_ast_fini(self): + # bpo-44184: Regression test for subtype_dealloc() when deallocating + # an AST instance also destroy its AST type: subtype_dealloc() must + # not access the type memory after deallocating the instance, since + # the type memory can be freed as well. The test is also related to + # _PyAST_Fini() which clears references to AST types. + code = textwrap.dedent(""" + import ast + import codecs + + # Small AST tree to keep their AST types alive + tree = ast.parse("def f(x, y): return 2*x-y") + x = [tree] + x.append(x) + + # Put the cycle somewhere to survive until the last GC collection. + # Codec search functions are only cleared at the end of + # interpreter_clear(). + def search_func(encoding): + return None + search_func.a = x + codecs.register(search_func) + """) + assert_python_ok("-c", code) + + def test_main(): enabled = gc.isenabled() gc.disable() @@ -1370,7 +1398,11 @@ def test_main(): try: gc.collect() # Delete 2nd generation garbage - run_unittest(GCTests, GCTogglingTests, GCCallbackTests) + run_unittest( + GCTests, + GCCallbackTests, + GCTogglingTests, + PythonFinalizationTests) finally: gc.set_debug(debug) # test gc.enable() even if GC is disabled by default diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-05-21-01-42-45.bpo-44184.9qOptC.rst b/Misc/NEWS.d/next/Core and Builtins/2021-05-21-01-42-45.bpo-44184.9qOptC.rst new file mode 100644 index 0000000..3aba9a5 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-05-21-01-42-45.bpo-44184.9qOptC.rst @@ -0,0 +1,3 @@ +Fix a crash at Python exit when a deallocator function removes the last strong +reference to a heap type. +Patch by Victor Stinner. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 84be0a1..af99ab7 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1446,6 +1446,12 @@ subtype_dealloc(PyObject *self) if (_PyType_IS_GC(base)) { _PyObject_GC_TRACK(self); } + + // Don't read type memory after calling basedealloc() since basedealloc() + // can deallocate the type and free its memory. + int type_needs_decref = (type->tp_flags & Py_TPFLAGS_HEAPTYPE + && !(base->tp_flags & Py_TPFLAGS_HEAPTYPE)); + assert(basedealloc); basedealloc(self); @@ -1453,8 +1459,9 @@ subtype_dealloc(PyObject *self) our type from a HEAPTYPE to a non-HEAPTYPE, so be careful about reference counting. Only decref if the base type is not already a heap allocated type. Otherwise, basedealloc should have decref'd it already */ - if (type->tp_flags & Py_TPFLAGS_HEAPTYPE && !(base->tp_flags & Py_TPFLAGS_HEAPTYPE)) - Py_DECREF(type); + if (type_needs_decref) { + Py_DECREF(type); + } endlabel: Py_TRASHCAN_END |