summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2021-07-15 16:36:51 -0700
committerGitHub <noreply@github.com>2021-07-15 16:36:51 -0700
commit298ee657ab8170adf75a186c0414b7ca3baf1991 (patch)
tree17307435e9ee9871f12df489ea194d102f9dc13f
parent0b4704973dbef712d05bdd62349bb4244f545430 (diff)
downloadcpython-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.h2
-rw-r--r--Lib/test/test_gc.py34
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2021-05-21-01-42-45.bpo-44184.9qOptC.rst3
-rw-r--r--Objects/typeobject.c11
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