diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2021-07-15 16:36:51 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-15 16:36:51 -0700 |
commit | 298ee657ab8170adf75a186c0414b7ca3baf1991 (patch) | |
tree | 17307435e9ee9871f12df489ea194d102f9dc13f | |
parent | 0b4704973dbef712d05bdd62349bb4244f545430 (diff) | |
download | cpython-git-298ee657ab8170adf75a186c0414b7ca3baf1991.tar.gz |
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.
(cherry picked from commit 615069eb08494d089bf24e43547fbc482ed699b8)
Co-authored-by: Victor Stinner <vstinner@python.org>
-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 3d925e2250..9bcb5f5efd 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 38c9cb7123..59cff20e6a 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1360,6 +1360,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() @@ -1369,7 +1397,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 0000000000..3aba9a5847 --- /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 82faa7987f..f201515d7d 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1341,6 +1341,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); @@ -1348,8 +1354,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 |