churchyard / rpms / python3

Forked from rpms/python3 6 years ago
Clone

Blame 00170-gc-assertions.patch

0424a4f
diff --git a/Include/object.h b/Include/object.h
ef18b45
index c772dea..5729797 100644
0424a4f
--- a/Include/object.h
0424a4f
+++ b/Include/object.h
ef18b45
@@ -1098,6 +1098,49 @@ PyAPI_FUNC(void)
Robert Kuska 86a9a20
 _PyObject_DebugTypeStats(FILE *out);
Robert Kuska 86a9a20
 #endif /* ifndef Py_LIMITED_API */
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
+/* 
Robert Kuska 86a9a20
+   Define a pair of assertion macros.
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+   These work like the regular C assert(), in that they will abort the
Robert Kuska 86a9a20
+   process with a message on stderr if the given condition fails to hold,
Robert Kuska 86a9a20
+   but compile away to nothing if NDEBUG is defined.
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+   However, before aborting, Python will also try to call _PyObject_Dump() on
Robert Kuska 86a9a20
+   the given object.  This may be of use when investigating bugs in which a
Robert Kuska 86a9a20
+   particular object is corrupt (e.g. buggy a tp_visit method in an extension
Robert Kuska 86a9a20
+   module breaking the garbage collector), to help locate the broken objects.
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+   The WITH_MSG variant allows you to supply an additional message that Python
Robert Kuska 86a9a20
+   will attempt to print to stderr, after the object dump.
Robert Kuska 86a9a20
+*/
Robert Kuska 86a9a20
+#ifdef NDEBUG
Robert Kuska 86a9a20
+/* No debugging: compile away the assertions: */
Robert Kuska 86a9a20
+#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) ((void)0)
Robert Kuska 86a9a20
+#else
Robert Kuska 86a9a20
+/* With debugging: generate checks: */
Robert Kuska 86a9a20
+#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) \
Robert Kuska 86a9a20
+  ((expr)                                           \
Robert Kuska 86a9a20
+   ? (void)(0)                                      \
Robert Kuska 86a9a20
+   : _PyObject_AssertFailed((obj),                  \
Robert Kuska 86a9a20
+                            (msg),                  \
Robert Kuska 86a9a20
+                            (__STRING(expr)),       \
Robert Kuska 86a9a20
+                            (__FILE__),             \
Robert Kuska 86a9a20
+                            (__LINE__),             \
Robert Kuska 86a9a20
+                            (__PRETTY_FUNCTION__)))
Robert Kuska 86a9a20
+#endif
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+#define PyObject_ASSERT(obj, expr) \
Robert Kuska 86a9a20
+  PyObject_ASSERT_WITH_MSG(obj, expr, NULL)
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+/* 
Robert Kuska 86a9a20
+   Declare and define the entrypoint even when NDEBUG is defined, to avoid
Robert Kuska 86a9a20
+   causing compiler/linker errors when building extensions without NDEBUG
Robert Kuska 86a9a20
+   against a Python built with NDEBUG defined
Robert Kuska 86a9a20
+*/
Robert Kuska 86a9a20
+PyAPI_FUNC(void) _PyObject_AssertFailed(PyObject *,  const char *,
Robert Kuska 86a9a20
+                                        const char *, const char *, int,
Robert Kuska 86a9a20
+                                        const char *);
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
 #ifdef __cplusplus
Robert Kuska 86a9a20
 }
Robert Kuska 86a9a20
 #endif
0424a4f
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
ef18b45
index 904fc7d..288e242 100644
0424a4f
--- a/Lib/test/test_gc.py
0424a4f
+++ b/Lib/test/test_gc.py
0424a4f
@@ -1,10 +1,11 @@
Robert Kuska 86a9a20
 import unittest
Robert Kuska 86a9a20
 from test.support import (verbose, refcount_test, run_unittest,
0424a4f
                           strip_python_stderr, cpython_only, start_threads,
ef18b45
-                          temp_dir, requires_type_collecting)
ef18b45
+                          temp_dir, import_module, requires_type_collecting)
Robert Kuska 47ab152
 from test.support.script_helper import assert_python_ok, make_script
Robert Kuska 47ab152
 
Robert Kuska 86a9a20
 import sys
0424a4f
+import sysconfig
0424a4f
 import time
0424a4f
 import gc
0424a4f
 import weakref
d668c1e
@@ -46,6 +47,8 @@ class GC_Detector(object):
0424a4f
         # gc collects it.
0424a4f
         self.wr = weakref.ref(C1055820(666), it_happened)
0424a4f
 
0424a4f
+BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])
0424a4f
+
0424a4f
 @with_tp_del
0424a4f
 class Uncollectable(object):
0424a4f
     """Create a reference cycle with multiple __del__ methods.
d668c1e
@@ -863,6 +866,50 @@ class GCCallbackTests(unittest.TestCase):
Robert Kuska 86a9a20
         self.assertEqual(len(gc.garbage), 0)
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
 
0424a4f
+    @unittest.skipIf(BUILD_WITH_NDEBUG,
0424a4f
+                     'built with -NDEBUG')
Robert Kuska 86a9a20
+    def test_refcount_errors(self):
Robert Kuska 86a9a20
+        self.preclean()
Robert Kuska 86a9a20
+        # Verify the "handling" of objects with broken refcounts
Robert Kuska 86a9a20
+        import_module("ctypes") #skip if not supported
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+        import subprocess
Robert Kuska 86a9a20
+        code = '''if 1:
Robert Kuska 86a9a20
+        a = []
Robert Kuska 86a9a20
+        b = [a]
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+        # Simulate the refcount of "a" being too low (compared to the
Robert Kuska 86a9a20
+        # references held on it by live data), but keeping it above zero
Robert Kuska 86a9a20
+        # (to avoid deallocating it):
Robert Kuska 86a9a20
+        import ctypes
Robert Kuska 86a9a20
+        ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+        # The garbage collector should now have a fatal error when it reaches
Robert Kuska 86a9a20
+        # the broken object:
Robert Kuska 86a9a20
+        import gc
Robert Kuska 86a9a20
+        gc.collect()
Robert Kuska 86a9a20
+        '''
Robert Kuska 86a9a20
+        p = subprocess.Popen([sys.executable, "-c", code],
Robert Kuska 86a9a20
+                             stdout=subprocess.PIPE,
Robert Kuska 86a9a20
+                             stderr=subprocess.PIPE)
Robert Kuska 86a9a20
+        stdout, stderr = p.communicate()
Robert Kuska 86a9a20
+        p.stdout.close()
Robert Kuska 86a9a20
+        p.stderr.close()
Robert Kuska 86a9a20
+        # Verify that stderr has a useful error message:
Robert Kuska 86a9a20
+        self.assertRegex(stderr,
Robert Kuska 86a9a20
+            b'Modules/gcmodule.c:[0-9]+: visit_decref: Assertion "\(\(gc\)->gc.gc_refs >> \(1\)\) != 0" failed.')
Robert Kuska 86a9a20
+        self.assertRegex(stderr,
Robert Kuska 86a9a20
+            b'refcount was too small')
Robert Kuska 86a9a20
+        self.assertRegex(stderr,
Robert Kuska 86a9a20
+            b'object  : \[\]')
Robert Kuska 86a9a20
+        self.assertRegex(stderr,
Robert Kuska 86a9a20
+            b'type    : list')
Robert Kuska 86a9a20
+        self.assertRegex(stderr,
Robert Kuska 86a9a20
+            b'refcount: 1')
Robert Kuska 86a9a20
+        self.assertRegex(stderr,
Robert Kuska 86a9a20
+            b'address : 0x[0-9a-f]+')
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
+
Robert Kuska 86a9a20
 class GCTogglingTests(unittest.TestCase):
Robert Kuska 86a9a20
     def setUp(self):
Robert Kuska 86a9a20
         gc.enable()
0424a4f
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c
ef18b45
index 8ba1093..e795308 100644
0424a4f
--- a/Modules/gcmodule.c
0424a4f
+++ b/Modules/gcmodule.c
ef18b45
@@ -239,7 +239,8 @@ update_refs(PyGC_Head *containers)
Robert Kuska 86a9a20
 {
Robert Kuska 86a9a20
     PyGC_Head *gc = containers->gc.gc_next;
Robert Kuska 86a9a20
     for (; gc != containers; gc = gc->gc.gc_next) {
Robert Kuska 86a9a20
-        assert(_PyGCHead_REFS(gc) == GC_REACHABLE);
Robert Kuska 86a9a20
+        PyObject_ASSERT(FROM_GC(gc),
Robert Kuska 86a9a20
+                        _PyGCHead_REFS(gc) == GC_REACHABLE);
Robert Kuska 86a9a20
         _PyGCHead_SET_REFS(gc, Py_REFCNT(FROM_GC(gc)));
Robert Kuska 86a9a20
         /* Python's cyclic gc should never see an incoming refcount
Robert Kuska 86a9a20
          * of 0:  if something decref'ed to 0, it should have been
ef18b45
@@ -259,7 +260,8 @@ update_refs(PyGC_Head *containers)
Robert Kuska 86a9a20
          * so serious that maybe this should be a release-build
Robert Kuska 86a9a20
          * check instead of an assert?
Robert Kuska 86a9a20
          */
Robert Kuska 86a9a20
-        assert(_PyGCHead_REFS(gc) != 0);
Robert Kuska 86a9a20
+        PyObject_ASSERT(FROM_GC(gc),
Robert Kuska 86a9a20
+                        _PyGCHead_REFS(gc) != 0);
Robert Kuska 86a9a20
     }
Robert Kuska 86a9a20
 }
Robert Kuska 86a9a20
 
ef18b45
@@ -274,7 +276,9 @@ visit_decref(PyObject *op, void *data)
Robert Kuska 86a9a20
          * generation being collected, which can be recognized
Robert Kuska 86a9a20
          * because only they have positive gc_refs.
Robert Kuska 86a9a20
          */
Robert Kuska 86a9a20
-        assert(_PyGCHead_REFS(gc) != 0); /* else refcount was too small */
Robert Kuska 86a9a20
+        PyObject_ASSERT_WITH_MSG(FROM_GC(gc),
Robert Kuska 86a9a20
+                        _PyGCHead_REFS(gc) != 0,
Robert Kuska 86a9a20
+                        "refcount was too small"); /* else refcount was too small */
Robert Kuska 86a9a20
         if (_PyGCHead_REFS(gc) > 0)
Robert Kuska 86a9a20
             _PyGCHead_DECREF(gc);
Robert Kuska 86a9a20
     }
ef18b45
@@ -334,9 +338,10 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
Robert Kuska 86a9a20
          * If gc_refs == GC_UNTRACKED, it must be ignored.
Robert Kuska 86a9a20
          */
Robert Kuska 86a9a20
          else {
Robert Kuska 86a9a20
-            assert(gc_refs > 0
Robert Kuska 86a9a20
-                   || gc_refs == GC_REACHABLE
Robert Kuska 86a9a20
-                   || gc_refs == GC_UNTRACKED);
Robert Kuska 86a9a20
+             PyObject_ASSERT(FROM_GC(gc),
Robert Kuska 86a9a20
+                             gc_refs > 0
Robert Kuska 86a9a20
+                             || gc_refs == GC_REACHABLE
Robert Kuska 86a9a20
+                             || gc_refs == GC_UNTRACKED);
Robert Kuska 86a9a20
          }
Robert Kuska 86a9a20
     }
Robert Kuska 86a9a20
     return 0;
ef18b45
@@ -378,7 +383,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
Robert Kuska 86a9a20
              */
Robert Kuska 86a9a20
             PyObject *op = FROM_GC(gc);
Robert Kuska 86a9a20
             traverseproc traverse = Py_TYPE(op)->tp_traverse;
Robert Kuska 86a9a20
-            assert(_PyGCHead_REFS(gc) > 0);
Robert Kuska 86a9a20
+            PyObject_ASSERT(op, _PyGCHead_REFS(gc) > 0);
Robert Kuska 86a9a20
             _PyGCHead_SET_REFS(gc, GC_REACHABLE);
Robert Kuska 86a9a20
             (void) traverse(op,
Robert Kuska 86a9a20
                             (visitproc)visit_reachable,
ef18b45
@@ -441,7 +446,7 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
Robert Kuska 86a9a20
     for (gc = unreachable->gc.gc_next; gc != unreachable; gc = next) {
Robert Kuska 86a9a20
         PyObject *op = FROM_GC(gc);
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
-        assert(IS_TENTATIVELY_UNREACHABLE(op));
Robert Kuska 86a9a20
+        PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op));
Robert Kuska 86a9a20
         next = gc->gc.gc_next;
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
         if (has_legacy_finalizer(op)) {
ef18b45
@@ -517,7 +522,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
Robert Kuska 86a9a20
         PyWeakReference **wrlist;
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
         op = FROM_GC(gc);
Robert Kuska 86a9a20
-        assert(IS_TENTATIVELY_UNREACHABLE(op));
Robert Kuska 86a9a20
+        PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op));
Robert Kuska 86a9a20
         next = gc->gc.gc_next;
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
         if (! PyType_SUPPORTS_WEAKREFS(Py_TYPE(op)))
ef18b45
@@ -538,9 +543,9 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
Robert Kuska 86a9a20
              * the callback pointer intact.  Obscure:  it also
Robert Kuska 86a9a20
              * changes *wrlist.
Robert Kuska 86a9a20
              */
Robert Kuska 86a9a20
-            assert(wr->wr_object == op);
Robert Kuska 86a9a20
+            PyObject_ASSERT(wr->wr_object, wr->wr_object == op);
Robert Kuska 86a9a20
             _PyWeakref_ClearRef(wr);
Robert Kuska 86a9a20
-            assert(wr->wr_object == Py_None);
Robert Kuska 86a9a20
+            PyObject_ASSERT(wr->wr_object, wr->wr_object == Py_None);
Robert Kuska 86a9a20
             if (wr->wr_callback == NULL)
Robert Kuska 86a9a20
                 continue;                       /* no callback */
Robert Kuska 86a9a20
 
ef18b45
@@ -574,7 +579,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
Robert Kuska 86a9a20
      */
Robert Kuska 86a9a20
             if (IS_TENTATIVELY_UNREACHABLE(wr))
Robert Kuska 86a9a20
                 continue;
Robert Kuska 86a9a20
-            assert(IS_REACHABLE(wr));
Robert Kuska 86a9a20
+            PyObject_ASSERT(op, IS_REACHABLE(wr));
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
             /* Create a new reference so that wr can't go away
Robert Kuska 86a9a20
              * before we can process it again.
ef18b45
@@ -583,7 +588,8 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
             /* Move wr to wrcb_to_call, for the next pass. */
Robert Kuska 86a9a20
             wrasgc = AS_GC(wr);
Robert Kuska 86a9a20
-            assert(wrasgc != next); /* wrasgc is reachable, but
Robert Kuska 86a9a20
+            PyObject_ASSERT(op, wrasgc != next);
Robert Kuska 86a9a20
+                                    /* wrasgc is reachable, but
Robert Kuska 86a9a20
                                        next isn't, so they can't
Robert Kuska 86a9a20
                                        be the same */
Robert Kuska 86a9a20
             gc_list_move(wrasgc, &wrcb_to_call);
ef18b45
@@ -599,11 +605,11 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
         gc = wrcb_to_call.gc.gc_next;
Robert Kuska 86a9a20
         op = FROM_GC(gc);
Robert Kuska 86a9a20
-        assert(IS_REACHABLE(op));
Robert Kuska 86a9a20
-        assert(PyWeakref_Check(op));
Robert Kuska 86a9a20
+        PyObject_ASSERT(op, IS_REACHABLE(op));
Robert Kuska 86a9a20
+        PyObject_ASSERT(op, PyWeakref_Check(op));
Robert Kuska 86a9a20
         wr = (PyWeakReference *)op;
Robert Kuska 86a9a20
         callback = wr->wr_callback;
Robert Kuska 86a9a20
-        assert(callback != NULL);
Robert Kuska 86a9a20
+        PyObject_ASSERT(op, callback != NULL);
Robert Kuska 86a9a20
 
Robert Kuska 86a9a20
         /* copy-paste of weakrefobject.c's handle_callback() */
Robert Kuska 86a9a20
         temp = PyObject_CallFunctionObjArgs(callback, wr, NULL);
ef18b45
@@ -720,12 +726,14 @@ check_garbage(PyGC_Head *collectable)
Robert Kuska 86a9a20
     for (gc = collectable->gc.gc_next; gc != collectable;
Robert Kuska 86a9a20
          gc = gc->gc.gc_next) {
Robert Kuska 86a9a20
         _PyGCHead_SET_REFS(gc, Py_REFCNT(FROM_GC(gc)));
Robert Kuska 86a9a20
-        assert(_PyGCHead_REFS(gc) != 0);
Robert Kuska 86a9a20
+        PyObject_ASSERT(FROM_GC(gc),
Robert Kuska 86a9a20
+                        _PyGCHead_REFS(gc) != 0);
Robert Kuska 86a9a20
     }
Robert Kuska 86a9a20
     subtract_refs(collectable);
Robert Kuska 86a9a20
     for (gc = collectable->gc.gc_next; gc != collectable;
Robert Kuska 86a9a20
          gc = gc->gc.gc_next) {
Robert Kuska 86a9a20
-        assert(_PyGCHead_REFS(gc) >= 0);
Robert Kuska 86a9a20
+        PyObject_ASSERT(FROM_GC(gc),
Robert Kuska 86a9a20
+                        _PyGCHead_REFS(gc) >= 0);
Robert Kuska 86a9a20
         if (_PyGCHead_REFS(gc) != 0)
Robert Kuska 86a9a20
             return -1;
Robert Kuska 86a9a20
     }
0424a4f
diff --git a/Objects/object.c b/Objects/object.c
ef18b45
index 220aa90..f6c7161 100644
0424a4f
--- a/Objects/object.c
0424a4f
+++ b/Objects/object.c
ef18b45
@@ -2177,6 +2177,35 @@ _PyTrash_thread_destroy_chain(void)
d668c1e
     --tstate->trash_delete_nesting;
0424a4f
 }
Robert Kuska 86a9a20
 
0424a4f
+PyAPI_FUNC(void)
0424a4f
+_PyObject_AssertFailed(PyObject *obj, const char *msg, const char *expr,
0424a4f
+              const char *file, int line, const char *function)
0424a4f
+{
0424a4f
+    fprintf(stderr,
0424a4f
+            "%s:%d: %s: Assertion \"%s\" failed.\n",
0424a4f
+            file, line, function, expr);
0424a4f
+    if (msg) {
0424a4f
+        fprintf(stderr, "%s\n", msg);
0424a4f
+    }
Robert Kuska 86a9a20
+
0424a4f
+    fflush(stderr);
0424a4f
+
0424a4f
+    if (obj) {
0424a4f
+        /* This might succeed or fail, but we're about to abort, so at least
0424a4f
+           try to provide any extra info we can: */
0424a4f
+        _PyObject_Dump(obj);
0424a4f
+    }
0424a4f
+    else {
0424a4f
+        fprintf(stderr, "NULL object\n");
0424a4f
+    }
0424a4f
+
0424a4f
+    fflush(stdout);
0424a4f
+    fflush(stderr);
0424a4f
+
0424a4f
+    /* Terminate the process: */
0424a4f
+    abort();
0424a4f
+}
0424a4f
+
0424a4f
 #ifndef Py_TRACE_REFS
0424a4f
 /* For Py_LIMITED_API, we need an out-of-line version of _Py_Dealloc.
0424a4f
    Define this here, so we can undefine the macro. */