diff --git a/Doc/lib/libgc.tex b/Doc/lib/libgc.tex index 310c5822b8c..eea8d8c0f47 100644 --- a/Doc/lib/libgc.tex +++ b/Doc/lib/libgc.tex @@ -99,6 +99,19 @@ objects, call \function{collect()} before calling \versionadded{2.2} \end{funcdesc} +\begin{funcdesc}{get_referrents}{*objs} +Return a list of objects directly referred to by any of the arguments. +The referrents returned are those objects visited by the arguments' +C-level \cfunction{tp_traverse} methods (if any), and may not be all +objects actually directly reachable. \cfunction{tp_traverse} methods +are supported only by objects that support garbage collection, and are +only required to visit objects that may be involved in a cycle. So, +for example, if an integer is directly reachable from an argument, that +integer object may or may not appear in the result list. + +\versionadded{2.3} +\end{funcdesc} + The following variable is provided for read-only access (you can mutate its value but should not rebind it): diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 1fbd5081cce..f0d5e19b430 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -4,7 +4,7 @@ import gc def expect(actual, expected, name): if actual != expected: - raise TestFailed, "test_%s: actual %d, expected %d" % ( + raise TestFailed, "test_%s: actual %r, expected %r" % ( name, actual, expected) def expect_nonzero(actual, name): @@ -304,6 +304,29 @@ def test_boom2(): expect(gc.collect(), 4, "boom2") expect(len(gc.garbage), garbagelen, "boom2") +def test_get_referrents(): + alist = [1, 3, 5] + got = gc.get_referrents(alist) + got.sort() + expect(got, alist, "get_referrents") + + atuple = tuple(alist) + got = gc.get_referrents(atuple) + got.sort() + expect(got, alist, "get_referrents") + + adict = {1: 3, 5: 7} + expected = [1, 3, 5, 7] + got = gc.get_referrents(adict) + got.sort() + expect(got, expected, "get_referrents") + + got = gc.get_referrents([1, 2], {3: 4}, (0, 0, 0)) + got.sort() + expect(got, [0, 0] + range(5), "get_referrents") + + expect(gc.get_referrents(1, 'a', 4j), [], "get_referrents") + def test_all(): gc.collect() # Delete 2nd generation garbage run_test("lists", test_list) @@ -324,6 +347,7 @@ def test_all(): run_test("trashcan", test_trashcan) run_test("boom", test_boom) run_test("boom2", test_boom2) + run_test("get_referrents", test_get_referrents) def test(): if verbose: diff --git a/Misc/NEWS b/Misc/NEWS index 667c89eac19..7b8f8c0347a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -49,6 +49,11 @@ Core and builtins Extension modules ----------------- +- New function gc.get_referrents(obj) returns a list of objects + directly referenced by obj. In effect, it exposes what the object's + tp_traverse slot does, and can be helpful when debugging memory + leaks. + - The iconv module has been removed from this release. - The platform-independent routines for packing floats in IEEE formats diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 9017363bbdf..b0faad26c0d 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -857,12 +857,11 @@ gc_get_referrers(PyObject *self, PyObject *args) return result; } +/* Append obj to list; return true if error (out of memory), false if OK. */ static int referrentsvisit(PyObject *obj, PyObject *list) { - if (PyList_Append(list, obj) < 0) - return 1; - return 0; + return PyList_Append(list, obj) < 0; } PyDoc_STRVAR(gc_get_referrents__doc__, @@ -874,13 +873,23 @@ gc_get_referrents(PyObject *self, PyObject *args) { int i; PyObject *result = PyList_New(0); + + if (result == NULL) + return NULL; + for (i = 0; i < PyTuple_GET_SIZE(args); i++) { + traverseproc traverse; PyObject *obj = PyTuple_GET_ITEM(args, i); - traverseproc traverse = obj->ob_type->tp_traverse; - if (!traverse) + + if (! PyObject_IS_GC(obj)) continue; - if (traverse(obj, (visitproc)referrentsvisit, result)) + traverse = obj->ob_type->tp_traverse; + if (! traverse) + continue; + if (traverse(obj, (visitproc)referrentsvisit, result)) { + Py_DECREF(result); return NULL; + } } return result; }