2.7.3-25: in debug builds, try to print repr() when a C-level assert fails in the garbage collector (patch 170; rhbz#850013)
* Wed Feb 20 2013 David Malcolm <dmalcolm@redhat.com> - 2.7.3-25 - in debug builds, try to print repr() when a C-level assert fails in the garbage collector (typically indicating a reference-counting error somewhere else e.g in an extension module) (patch 170; rhbz#850013)
This commit is contained in:
parent
8b81967525
commit
6a2708e308
|
@ -0,0 +1,276 @@
|
|||
diff -up Python-2.7.3/Lib/test/test_gc.py.gc-assertions Python-2.7.3/Lib/test/test_gc.py
|
||||
--- Python-2.7.3/Lib/test/test_gc.py.gc-assertions 2013-02-20 16:28:20.890536607 -0500
|
||||
+++ Python-2.7.3/Lib/test/test_gc.py 2013-02-20 16:39:52.720489297 -0500
|
||||
@@ -1,6 +1,7 @@
|
||||
import unittest
|
||||
-from test.test_support import verbose, run_unittest
|
||||
+from test.test_support import verbose, run_unittest, import_module
|
||||
import sys
|
||||
+import sysconfig
|
||||
import gc
|
||||
import weakref
|
||||
|
||||
@@ -32,6 +33,8 @@ class GC_Detector(object):
|
||||
self.wr = weakref.ref(C1055820(666), it_happened)
|
||||
|
||||
|
||||
+BUILT_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])
|
||||
+
|
||||
### Tests
|
||||
###############################################################################
|
||||
|
||||
@@ -476,6 +479,49 @@ class GCTests(unittest.TestCase):
|
||||
# would be damaged, with an empty __dict__.
|
||||
self.assertEqual(x, None)
|
||||
|
||||
+ @unittest.skipIf(BUILT_WITH_NDEBUG,
|
||||
+ 'built with -NDEBUG')
|
||||
+ def test_refcount_errors(self):
|
||||
+ # Verify the "handling" of objects with broken refcounts
|
||||
+
|
||||
+ import_module("ctypes") #skip if not supported
|
||||
+
|
||||
+ import subprocess
|
||||
+ code = '''if 1:
|
||||
+ a = []
|
||||
+ b = [a]
|
||||
+
|
||||
+ # Simulate the refcount of "a" being too low (compared to the
|
||||
+ # references held on it by live data), but keeping it above zero
|
||||
+ # (to avoid deallocating it):
|
||||
+ import ctypes
|
||||
+ ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
|
||||
+
|
||||
+ # The garbage collector should now have a fatal error when it reaches
|
||||
+ # the broken object:
|
||||
+ import gc
|
||||
+ gc.collect()
|
||||
+ '''
|
||||
+ p = subprocess.Popen([sys.executable, "-c", code],
|
||||
+ stdout=subprocess.PIPE,
|
||||
+ stderr=subprocess.PIPE)
|
||||
+ stdout, stderr = p.communicate()
|
||||
+ p.stdout.close()
|
||||
+ p.stderr.close()
|
||||
+ # Verify that stderr has a useful error message:
|
||||
+ self.assertRegexpMatches(stderr,
|
||||
+ b'Modules/gcmodule.c:[0-9]+: visit_decref: Assertion "gc->gc.gc_refs != 0" failed.')
|
||||
+ self.assertRegexpMatches(stderr,
|
||||
+ b'refcount was too small')
|
||||
+ self.assertRegexpMatches(stderr,
|
||||
+ b'object : \[\]')
|
||||
+ self.assertRegexpMatches(stderr,
|
||||
+ b'type : list')
|
||||
+ self.assertRegexpMatches(stderr,
|
||||
+ b'refcount: 1')
|
||||
+ self.assertRegexpMatches(stderr,
|
||||
+ b'address : 0x[0-9a-f]+')
|
||||
+
|
||||
class GCTogglingTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
gc.enable()
|
||||
diff -up Python-2.7.3/Modules/gcmodule.c.gc-assertions Python-2.7.3/Modules/gcmodule.c
|
||||
--- Python-2.7.3/Modules/gcmodule.c.gc-assertions 2012-04-09 19:07:34.000000000 -0400
|
||||
+++ Python-2.7.3/Modules/gcmodule.c 2013-02-20 16:28:21.029536600 -0500
|
||||
@@ -21,6 +21,73 @@
|
||||
#include "Python.h"
|
||||
#include "frameobject.h" /* for PyFrame_ClearFreeList */
|
||||
|
||||
+/*
|
||||
+ Define a pair of assertion macros.
|
||||
+
|
||||
+ These work like the regular C assert(), in that they will abort the
|
||||
+ process with a message on stderr if the given condition fails to hold,
|
||||
+ but compile away to nothing if NDEBUG is defined.
|
||||
+
|
||||
+ However, before aborting, Python will also try to call _PyObject_Dump() on
|
||||
+ the given object. This may be of use when investigating bugs in which a
|
||||
+ particular object is corrupt (e.g. buggy a tp_visit method in an extension
|
||||
+ module breaking the garbage collector), to help locate the broken objects.
|
||||
+
|
||||
+ The WITH_MSG variant allows you to supply an additional message that Python
|
||||
+ will attempt to print to stderr, after the object dump.
|
||||
+*/
|
||||
+#ifdef NDEBUG
|
||||
+/* No debugging: compile away the assertions: */
|
||||
+#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) ((void)0)
|
||||
+#else
|
||||
+/* With debugging: generate checks: */
|
||||
+#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) \
|
||||
+ ((expr) \
|
||||
+ ? (void)(0) \
|
||||
+ : _PyObject_AssertFailed((obj), \
|
||||
+ (msg), \
|
||||
+ (__STRING(expr)), \
|
||||
+ (__FILE__), \
|
||||
+ (__LINE__), \
|
||||
+ (__PRETTY_FUNCTION__)))
|
||||
+#endif
|
||||
+
|
||||
+#define PyObject_ASSERT(obj, expr) \
|
||||
+ PyObject_ASSERT_WITH_MSG(obj, expr, NULL)
|
||||
+
|
||||
+static void _PyObject_AssertFailed(PyObject *, const char *,
|
||||
+ const char *, const char *, int,
|
||||
+ const char *);
|
||||
+
|
||||
+static void
|
||||
+_PyObject_AssertFailed(PyObject *obj, const char *msg, const char *expr,
|
||||
+ const char *file, int line, const char *function)
|
||||
+{
|
||||
+ fprintf(stderr,
|
||||
+ "%s:%d: %s: Assertion \"%s\" failed.\n",
|
||||
+ file, line, function, expr);
|
||||
+ if (msg) {
|
||||
+ fprintf(stderr, "%s\n", msg);
|
||||
+ }
|
||||
+
|
||||
+ fflush(stderr);
|
||||
+
|
||||
+ if (obj) {
|
||||
+ /* This might succeed or fail, but we're about to abort, so at least
|
||||
+ try to provide any extra info we can: */
|
||||
+ _PyObject_Dump(obj);
|
||||
+ }
|
||||
+ else {
|
||||
+ fprintf(stderr, "NULL object\n");
|
||||
+ }
|
||||
+
|
||||
+ fflush(stdout);
|
||||
+ fflush(stderr);
|
||||
+
|
||||
+ /* Terminate the process: */
|
||||
+ abort();
|
||||
+}
|
||||
+
|
||||
/* Get an object's GC head */
|
||||
#define AS_GC(o) ((PyGC_Head *)(o)-1)
|
||||
|
||||
@@ -288,7 +355,8 @@ update_refs(PyGC_Head *containers)
|
||||
{
|
||||
PyGC_Head *gc = containers->gc.gc_next;
|
||||
for (; gc != containers; gc = gc->gc.gc_next) {
|
||||
- assert(gc->gc.gc_refs == GC_REACHABLE);
|
||||
+ PyObject_ASSERT(FROM_GC(gc),
|
||||
+ gc->gc.gc_refs == GC_REACHABLE);
|
||||
gc->gc.gc_refs = Py_REFCNT(FROM_GC(gc));
|
||||
/* Python's cyclic gc should never see an incoming refcount
|
||||
* of 0: if something decref'ed to 0, it should have been
|
||||
@@ -308,7 +376,8 @@ update_refs(PyGC_Head *containers)
|
||||
* so serious that maybe this should be a release-build
|
||||
* check instead of an assert?
|
||||
*/
|
||||
- assert(gc->gc.gc_refs != 0);
|
||||
+ PyObject_ASSERT(FROM_GC(gc),
|
||||
+ gc->gc.gc_refs != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +392,9 @@ visit_decref(PyObject *op, void *data)
|
||||
* generation being collected, which can be recognized
|
||||
* because only they have positive gc_refs.
|
||||
*/
|
||||
- assert(gc->gc.gc_refs != 0); /* else refcount was too small */
|
||||
+ PyObject_ASSERT_WITH_MSG(FROM_GC(gc),
|
||||
+ gc->gc.gc_refs != 0,
|
||||
+ "refcount was too small");
|
||||
if (gc->gc.gc_refs > 0)
|
||||
gc->gc.gc_refs--;
|
||||
}
|
||||
@@ -383,9 +454,10 @@ visit_reachable(PyObject *op, PyGC_Head
|
||||
* If gc_refs == GC_UNTRACKED, it must be ignored.
|
||||
*/
|
||||
else {
|
||||
- assert(gc_refs > 0
|
||||
- || gc_refs == GC_REACHABLE
|
||||
- || gc_refs == GC_UNTRACKED);
|
||||
+ PyObject_ASSERT(FROM_GC(gc),
|
||||
+ gc_refs > 0
|
||||
+ || gc_refs == GC_REACHABLE
|
||||
+ || gc_refs == GC_UNTRACKED);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -427,7 +499,7 @@ move_unreachable(PyGC_Head *young, PyGC_
|
||||
*/
|
||||
PyObject *op = FROM_GC(gc);
|
||||
traverseproc traverse = Py_TYPE(op)->tp_traverse;
|
||||
- assert(gc->gc.gc_refs > 0);
|
||||
+ PyObject_ASSERT(op, gc->gc.gc_refs > 0);
|
||||
gc->gc.gc_refs = GC_REACHABLE;
|
||||
(void) traverse(op,
|
||||
(visitproc)visit_reachable,
|
||||
@@ -494,7 +566,8 @@ move_finalizers(PyGC_Head *unreachable,
|
||||
for (gc = unreachable->gc.gc_next; gc != unreachable; gc = next) {
|
||||
PyObject *op = FROM_GC(gc);
|
||||
|
||||
- assert(IS_TENTATIVELY_UNREACHABLE(op));
|
||||
+ PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op));
|
||||
+
|
||||
next = gc->gc.gc_next;
|
||||
|
||||
if (has_finalizer(op)) {
|
||||
@@ -570,7 +643,7 @@ handle_weakrefs(PyGC_Head *unreachable,
|
||||
PyWeakReference **wrlist;
|
||||
|
||||
op = FROM_GC(gc);
|
||||
- assert(IS_TENTATIVELY_UNREACHABLE(op));
|
||||
+ PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op));
|
||||
next = gc->gc.gc_next;
|
||||
|
||||
if (! PyType_SUPPORTS_WEAKREFS(Py_TYPE(op)))
|
||||
@@ -591,9 +664,9 @@ handle_weakrefs(PyGC_Head *unreachable,
|
||||
* the callback pointer intact. Obscure: it also
|
||||
* changes *wrlist.
|
||||
*/
|
||||
- assert(wr->wr_object == op);
|
||||
+ PyObject_ASSERT(wr->wr_object, wr->wr_object == op);
|
||||
_PyWeakref_ClearRef(wr);
|
||||
- assert(wr->wr_object == Py_None);
|
||||
+ PyObject_ASSERT(wr->wr_object, wr->wr_object == Py_None);
|
||||
if (wr->wr_callback == NULL)
|
||||
continue; /* no callback */
|
||||
|
||||
@@ -627,7 +700,7 @@ handle_weakrefs(PyGC_Head *unreachable,
|
||||
*/
|
||||
if (IS_TENTATIVELY_UNREACHABLE(wr))
|
||||
continue;
|
||||
- assert(IS_REACHABLE(wr));
|
||||
+ PyObject_ASSERT(op, IS_REACHABLE(wr));
|
||||
|
||||
/* Create a new reference so that wr can't go away
|
||||
* before we can process it again.
|
||||
@@ -636,7 +709,8 @@ handle_weakrefs(PyGC_Head *unreachable,
|
||||
|
||||
/* Move wr to wrcb_to_call, for the next pass. */
|
||||
wrasgc = AS_GC(wr);
|
||||
- assert(wrasgc != next); /* wrasgc is reachable, but
|
||||
+ PyObject_ASSERT(op, wrasgc != next);
|
||||
+ /* wrasgc is reachable, but
|
||||
next isn't, so they can't
|
||||
be the same */
|
||||
gc_list_move(wrasgc, &wrcb_to_call);
|
||||
@@ -652,11 +726,11 @@ handle_weakrefs(PyGC_Head *unreachable,
|
||||
|
||||
gc = wrcb_to_call.gc.gc_next;
|
||||
op = FROM_GC(gc);
|
||||
- assert(IS_REACHABLE(op));
|
||||
- assert(PyWeakref_Check(op));
|
||||
+ PyObject_ASSERT(op, IS_REACHABLE(op));
|
||||
+ PyObject_ASSERT(op, PyWeakref_Check(op));
|
||||
wr = (PyWeakReference *)op;
|
||||
callback = wr->wr_callback;
|
||||
- assert(callback != NULL);
|
||||
+ PyObject_ASSERT(op, callback != NULL);
|
||||
|
||||
/* copy-paste of weakrefobject.c's handle_callback() */
|
||||
temp = PyObject_CallFunctionObjArgs(callback, wr, NULL);
|
||||
@@ -759,7 +833,7 @@ delete_garbage(PyGC_Head *collectable, P
|
||||
PyGC_Head *gc = collectable->gc.gc_next;
|
||||
PyObject *op = FROM_GC(gc);
|
||||
|
||||
- assert(IS_TENTATIVELY_UNREACHABLE(op));
|
||||
+ PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op));
|
||||
if (debug & DEBUG_SAVEALL) {
|
||||
PyList_Append(garbage, op);
|
||||
}
|
19
python.spec
19
python.spec
|
@ -106,7 +106,7 @@ Summary: An interpreted, interactive, object-oriented programming language
|
|||
Name: %{python}
|
||||
# Remember to also rebase python-docs when changing this:
|
||||
Version: 2.7.3
|
||||
Release: 24%{?dist}
|
||||
Release: 25%{?dist}
|
||||
License: Python
|
||||
Group: Development/Languages
|
||||
Requires: %{python}-libs%{?_isa} = %{version}-%{release}
|
||||
|
@ -771,6 +771,17 @@ Patch168: 00168-distutils-cflags.patch
|
|||
# (rhbz#879695)
|
||||
Patch169: 00169-avoid-implicit-usage-of-md5-in-multiprocessing.patch
|
||||
|
||||
# 00170 #
|
||||
# In debug builds, try to print repr() when a C-level assert fails in the
|
||||
# garbage collector (typically indicating a reference-counting error
|
||||
# somewhere else e.g in an extension module)
|
||||
# Backported to 2.7 from a patch I sent upstream for py3k
|
||||
# http://bugs.python.org/issue9263 (rhbz#614680)
|
||||
# hiding the proposed new macros/functions within gcmodule.c to avoid exposing
|
||||
# them within the extension API.
|
||||
# (rhbz#850013)
|
||||
Patch170: 00170-gc-assertions.patch
|
||||
|
||||
# (New patches go here ^^^)
|
||||
#
|
||||
# When adding new patches to "python" and "python3" in Fedora 17 onwards,
|
||||
|
@ -1107,6 +1118,7 @@ mv Modules/cryptmodule.c Modules/_cryptmodule.c
|
|||
%patch167 -p1
|
||||
%patch168 -p1
|
||||
%patch169 -p1
|
||||
%patch170 -p1
|
||||
|
||||
|
||||
# This shouldn't be necesarry, but is right now (2.2a3)
|
||||
|
@ -1939,6 +1951,11 @@ rm -fr %{buildroot}
|
|||
# ======================================================
|
||||
|
||||
%changelog
|
||||
* Wed Feb 20 2013 David Malcolm <dmalcolm@redhat.com> - 2.7.3-25
|
||||
- in debug builds, try to print repr() when a C-level assert fails in the
|
||||
garbage collector (typically indicating a reference-counting error somewhere
|
||||
else e.g in an extension module) (patch 170; rhbz#850013)
|
||||
|
||||
* Wed Feb 20 2013 David Malcolm <dmalcolm@redhat.com> - 2.7.3-24
|
||||
- move lib2to3/tests from python-libs to python-test (rhbz#850056)
|
||||
|
||||
|
|
Loading…
Reference in New Issue