]>
Commit | Line | Data |
---|---|---|
ee14ee66 MT |
1 | diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py |
2 | index 7e47b2d..12a210d 100644 | |
3 | --- a/Lib/test/test_gc.py | |
4 | +++ b/Lib/test/test_gc.py | |
5 | @@ -1,7 +1,8 @@ | |
48d9a6a0 | 6 | import unittest |
ee14ee66 MT |
7 | from test.support import (verbose, run_unittest, start_threads, |
8 | - requires_type_collecting) | |
9 | + requires_type_collecting, import_module) | |
48d9a6a0 MT |
10 | import sys |
11 | +import sysconfig | |
12 | import time | |
13 | import gc | |
14 | import weakref | |
ee14ee66 | 15 | @@ -39,6 +40,8 @@ class GC_Detector(object): |
48d9a6a0 MT |
16 | self.wr = weakref.ref(C1055820(666), it_happened) |
17 | ||
18 | ||
19 | +BUILT_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS']) | |
20 | + | |
21 | ### Tests | |
22 | ############################################################################### | |
23 | ||
ee14ee66 | 24 | @@ -537,6 +540,49 @@ class GCTests(unittest.TestCase): |
48d9a6a0 MT |
25 | # would be damaged, with an empty __dict__. |
26 | self.assertEqual(x, None) | |
27 | ||
28 | + @unittest.skipIf(BUILT_WITH_NDEBUG, | |
29 | + 'built with -NDEBUG') | |
30 | + def test_refcount_errors(self): | |
31 | + # Verify the "handling" of objects with broken refcounts | |
32 | + | |
33 | + import_module("ctypes") #skip if not supported | |
34 | + | |
35 | + import subprocess | |
36 | + code = '''if 1: | |
37 | + a = [] | |
38 | + b = [a] | |
39 | + | |
40 | + # Simulate the refcount of "a" being too low (compared to the | |
41 | + # references held on it by live data), but keeping it above zero | |
42 | + # (to avoid deallocating it): | |
43 | + import ctypes | |
44 | + ctypes.pythonapi.Py_DecRef(ctypes.py_object(a)) | |
45 | + | |
46 | + # The garbage collector should now have a fatal error when it reaches | |
47 | + # the broken object: | |
48 | + import gc | |
49 | + gc.collect() | |
50 | + ''' | |
51 | + p = subprocess.Popen([sys.executable, "-c", code], | |
52 | + stdout=subprocess.PIPE, | |
53 | + stderr=subprocess.PIPE) | |
54 | + stdout, stderr = p.communicate() | |
55 | + p.stdout.close() | |
56 | + p.stderr.close() | |
57 | + # Verify that stderr has a useful error message: | |
58 | + self.assertRegexpMatches(stderr, | |
59 | + b'Modules/gcmodule.c:[0-9]+: visit_decref: Assertion "gc->gc.gc_refs != 0" failed.') | |
60 | + self.assertRegexpMatches(stderr, | |
61 | + b'refcount was too small') | |
62 | + self.assertRegexpMatches(stderr, | |
63 | + b'object : \[\]') | |
64 | + self.assertRegexpMatches(stderr, | |
65 | + b'type : list') | |
66 | + self.assertRegexpMatches(stderr, | |
67 | + b'refcount: 1') | |
68 | + self.assertRegexpMatches(stderr, | |
69 | + b'address : 0x[0-9a-f]+') | |
70 | + | |
71 | class GCTogglingTests(unittest.TestCase): | |
72 | def setUp(self): | |
73 | gc.enable() | |
ee14ee66 MT |
74 | diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c |
75 | index 916e481..0233ce2 100644 | |
76 | --- a/Modules/gcmodule.c | |
77 | +++ b/Modules/gcmodule.c | |
48d9a6a0 MT |
78 | @@ -21,6 +21,73 @@ |
79 | #include "Python.h" | |
80 | #include "frameobject.h" /* for PyFrame_ClearFreeList */ | |
81 | ||
82 | +/* | |
83 | + Define a pair of assertion macros. | |
84 | + | |
85 | + These work like the regular C assert(), in that they will abort the | |
86 | + process with a message on stderr if the given condition fails to hold, | |
87 | + but compile away to nothing if NDEBUG is defined. | |
88 | + | |
89 | + However, before aborting, Python will also try to call _PyObject_Dump() on | |
90 | + the given object. This may be of use when investigating bugs in which a | |
91 | + particular object is corrupt (e.g. buggy a tp_visit method in an extension | |
92 | + module breaking the garbage collector), to help locate the broken objects. | |
93 | + | |
94 | + The WITH_MSG variant allows you to supply an additional message that Python | |
95 | + will attempt to print to stderr, after the object dump. | |
96 | +*/ | |
97 | +#ifdef NDEBUG | |
98 | +/* No debugging: compile away the assertions: */ | |
99 | +#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) ((void)0) | |
100 | +#else | |
101 | +/* With debugging: generate checks: */ | |
102 | +#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) \ | |
103 | + ((expr) \ | |
104 | + ? (void)(0) \ | |
105 | + : _PyObject_AssertFailed((obj), \ | |
106 | + (msg), \ | |
107 | + (__STRING(expr)), \ | |
108 | + (__FILE__), \ | |
109 | + (__LINE__), \ | |
110 | + (__PRETTY_FUNCTION__))) | |
111 | +#endif | |
112 | + | |
113 | +#define PyObject_ASSERT(obj, expr) \ | |
114 | + PyObject_ASSERT_WITH_MSG(obj, expr, NULL) | |
115 | + | |
116 | +static void _PyObject_AssertFailed(PyObject *, const char *, | |
117 | + const char *, const char *, int, | |
118 | + const char *); | |
119 | + | |
120 | +static void | |
121 | +_PyObject_AssertFailed(PyObject *obj, const char *msg, const char *expr, | |
122 | + const char *file, int line, const char *function) | |
123 | +{ | |
124 | + fprintf(stderr, | |
125 | + "%s:%d: %s: Assertion \"%s\" failed.\n", | |
126 | + file, line, function, expr); | |
127 | + if (msg) { | |
128 | + fprintf(stderr, "%s\n", msg); | |
129 | + } | |
130 | + | |
131 | + fflush(stderr); | |
132 | + | |
133 | + if (obj) { | |
134 | + /* This might succeed or fail, but we're about to abort, so at least | |
135 | + try to provide any extra info we can: */ | |
136 | + _PyObject_Dump(obj); | |
137 | + } | |
138 | + else { | |
139 | + fprintf(stderr, "NULL object\n"); | |
140 | + } | |
141 | + | |
142 | + fflush(stdout); | |
143 | + fflush(stderr); | |
144 | + | |
145 | + /* Terminate the process: */ | |
146 | + abort(); | |
147 | +} | |
148 | + | |
149 | /* Get an object's GC head */ | |
150 | #define AS_GC(o) ((PyGC_Head *)(o)-1) | |
151 | ||
ee14ee66 | 152 | @@ -328,7 +395,8 @@ update_refs(PyGC_Head *containers) |
48d9a6a0 MT |
153 | { |
154 | PyGC_Head *gc = containers->gc.gc_next; | |
155 | for (; gc != containers; gc = gc->gc.gc_next) { | |
156 | - assert(gc->gc.gc_refs == GC_REACHABLE); | |
157 | + PyObject_ASSERT(FROM_GC(gc), | |
158 | + gc->gc.gc_refs == GC_REACHABLE); | |
159 | gc->gc.gc_refs = Py_REFCNT(FROM_GC(gc)); | |
160 | /* Python's cyclic gc should never see an incoming refcount | |
161 | * of 0: if something decref'ed to 0, it should have been | |
ee14ee66 | 162 | @@ -348,7 +416,8 @@ update_refs(PyGC_Head *containers) |
48d9a6a0 MT |
163 | * so serious that maybe this should be a release-build |
164 | * check instead of an assert? | |
165 | */ | |
166 | - assert(gc->gc.gc_refs != 0); | |
167 | + PyObject_ASSERT(FROM_GC(gc), | |
168 | + gc->gc.gc_refs != 0); | |
169 | } | |
170 | } | |
171 | ||
ee14ee66 | 172 | @@ -363,7 +432,9 @@ visit_decref(PyObject *op, void *data) |
48d9a6a0 MT |
173 | * generation being collected, which can be recognized |
174 | * because only they have positive gc_refs. | |
175 | */ | |
176 | - assert(gc->gc.gc_refs != 0); /* else refcount was too small */ | |
177 | + PyObject_ASSERT_WITH_MSG(FROM_GC(gc), | |
178 | + gc->gc.gc_refs != 0, | |
179 | + "refcount was too small"); | |
180 | if (gc->gc.gc_refs > 0) | |
181 | gc->gc.gc_refs--; | |
182 | } | |
ee14ee66 | 183 | @@ -423,9 +494,10 @@ visit_reachable(PyObject *op, PyGC_Head *reachable) |
48d9a6a0 MT |
184 | * If gc_refs == GC_UNTRACKED, it must be ignored. |
185 | */ | |
186 | else { | |
187 | - assert(gc_refs > 0 | |
188 | - || gc_refs == GC_REACHABLE | |
189 | - || gc_refs == GC_UNTRACKED); | |
190 | + PyObject_ASSERT(FROM_GC(gc), | |
191 | + gc_refs > 0 | |
192 | + || gc_refs == GC_REACHABLE | |
193 | + || gc_refs == GC_UNTRACKED); | |
194 | } | |
195 | } | |
196 | return 0; | |
ee14ee66 | 197 | @@ -467,7 +539,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) |
48d9a6a0 MT |
198 | */ |
199 | PyObject *op = FROM_GC(gc); | |
200 | traverseproc traverse = Py_TYPE(op)->tp_traverse; | |
201 | - assert(gc->gc.gc_refs > 0); | |
202 | + PyObject_ASSERT(op, gc->gc.gc_refs > 0); | |
203 | gc->gc.gc_refs = GC_REACHABLE; | |
204 | (void) traverse(op, | |
205 | (visitproc)visit_reachable, | |
ee14ee66 | 206 | @@ -545,7 +617,8 @@ move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) |
48d9a6a0 MT |
207 | for (gc = unreachable->gc.gc_next; gc != unreachable; gc = next) { |
208 | PyObject *op = FROM_GC(gc); | |
209 | ||
210 | - assert(IS_TENTATIVELY_UNREACHABLE(op)); | |
211 | + PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op)); | |
212 | + | |
213 | next = gc->gc.gc_next; | |
214 | ||
215 | if (has_finalizer(op)) { | |
ee14ee66 | 216 | @@ -621,7 +694,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) |
48d9a6a0 MT |
217 | PyWeakReference **wrlist; |
218 | ||
219 | op = FROM_GC(gc); | |
220 | - assert(IS_TENTATIVELY_UNREACHABLE(op)); | |
221 | + PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op)); | |
222 | next = gc->gc.gc_next; | |
223 | ||
224 | if (! PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) | |
ee14ee66 | 225 | @@ -642,9 +715,9 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) |
48d9a6a0 MT |
226 | * the callback pointer intact. Obscure: it also |
227 | * changes *wrlist. | |
228 | */ | |
229 | - assert(wr->wr_object == op); | |
230 | + PyObject_ASSERT(wr->wr_object, wr->wr_object == op); | |
231 | _PyWeakref_ClearRef(wr); | |
232 | - assert(wr->wr_object == Py_None); | |
233 | + PyObject_ASSERT(wr->wr_object, wr->wr_object == Py_None); | |
234 | if (wr->wr_callback == NULL) | |
235 | continue; /* no callback */ | |
236 | ||
ee14ee66 | 237 | @@ -678,7 +751,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) |
48d9a6a0 MT |
238 | */ |
239 | if (IS_TENTATIVELY_UNREACHABLE(wr)) | |
240 | continue; | |
241 | - assert(IS_REACHABLE(wr)); | |
242 | + PyObject_ASSERT(op, IS_REACHABLE(wr)); | |
243 | ||
244 | /* Create a new reference so that wr can't go away | |
245 | * before we can process it again. | |
ee14ee66 | 246 | @@ -687,7 +760,8 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) |
48d9a6a0 MT |
247 | |
248 | /* Move wr to wrcb_to_call, for the next pass. */ | |
249 | wrasgc = AS_GC(wr); | |
250 | - assert(wrasgc != next); /* wrasgc is reachable, but | |
251 | + PyObject_ASSERT(op, wrasgc != next); | |
252 | + /* wrasgc is reachable, but | |
253 | next isn't, so they can't | |
254 | be the same */ | |
255 | gc_list_move(wrasgc, &wrcb_to_call); | |
ee14ee66 | 256 | @@ -703,11 +777,11 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) |
48d9a6a0 MT |
257 | |
258 | gc = wrcb_to_call.gc.gc_next; | |
259 | op = FROM_GC(gc); | |
260 | - assert(IS_REACHABLE(op)); | |
261 | - assert(PyWeakref_Check(op)); | |
262 | + PyObject_ASSERT(op, IS_REACHABLE(op)); | |
263 | + PyObject_ASSERT(op, PyWeakref_Check(op)); | |
264 | wr = (PyWeakReference *)op; | |
265 | callback = wr->wr_callback; | |
266 | - assert(callback != NULL); | |
267 | + PyObject_ASSERT(op, callback != NULL); | |
268 | ||
269 | /* copy-paste of weakrefobject.c's handle_callback() */ | |
270 | temp = PyObject_CallFunctionObjArgs(callback, wr, NULL); | |
ee14ee66 | 271 | @@ -810,7 +884,7 @@ delete_garbage(PyGC_Head *collectable, PyGC_Head *old) |
48d9a6a0 MT |
272 | PyGC_Head *gc = collectable->gc.gc_next; |
273 | PyObject *op = FROM_GC(gc); | |
274 | ||
275 | - assert(IS_TENTATIVELY_UNREACHABLE(op)); | |
276 | + PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op)); | |
277 | if (debug & DEBUG_SAVEALL) { | |
278 | PyList_Append(garbage, op); | |
279 | } |