summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2013-02-20 21:45:56 (GMT)
committerDavid Malcolm <dmalcolm@redhat.com>2013-02-20 21:45:56 (GMT)
commit6a2708e308b57d4ed37dd4dba9ed6944b2229927 (patch)
treed109cd2e0bf2818ae881e220f90f582849a6dc60
parent8b819675253e8c45dd1b877d705c1eae2ed9e76b (diff)
downloadpython-6a2708e308b57d4ed37dd4dba9ed6944b2229927.zip
python-6a2708e308b57d4ed37dd4dba9ed6944b2229927.tar.gz
python-6a2708e308b57d4ed37dd4dba9ed6944b2229927.tar.xz
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)
-rw-r--r--00170-gc-assertions.patch276
-rw-r--r--python.spec19
2 files changed, 294 insertions, 1 deletions
diff --git a/00170-gc-assertions.patch b/00170-gc-assertions.patch
new file mode 100644
index 0000000..088f84d
--- /dev/null
+++ b/00170-gc-assertions.patch
@@ -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);
+ }
diff --git a/python.spec b/python.spec
index f7b4dac..29f1547 100644
--- a/python.spec
+++ b/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)