You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
10 KiB
276 lines
10 KiB
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 time |
|
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); |
|
}
|
|
|