Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,35 @@ Object Protocol
instead of the :func:`repr`.


.. c:function:: void PyObject_Dump(PyObject *op)

Dump an object *op* to ``stderr``. This should only be used for debugging.

The output is intended to try dumping objects even after memory corruption:

* Information is written starting with fields that are the least likely to
crash when accessed.
* This function can be called without an :term:`attached thread state`, but
it's not recommended to do so: it can cause deadlocks.
* An object that does not belong to the current interpreter may be dumped,
but this may also cause crashes or unintended behavior.
* Implement a heuristic to detect if the object memory has been freed. Don't
display the object contents in this case, only its memory address.
* The output format may change at any time.

Example of output:

.. code-block:: output

object address : 0x7f80124702c0
object refcount : 2
object type : 0x9902e0
object type name: str
object repr : 'abcdef'

.. versionadded:: next


.. c:function:: int PyObject_HasAttrWithError(PyObject *o, PyObject *attr_name)

Returns ``1`` if *o* has the attribute *attr_name*, and ``0`` otherwise.
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ New features

(Contributed by Victor Stinner in :gh:`129813`.)

* Add :c:func:`PyObject_Dump` to dump an object to ``stderr``. Function used
for debugging.
(Contributed by Victor Stinner in :gh:`141070`.)

* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
(Contributed by Victor Stinner in :gh:`111489`.)

Expand Down
7 changes: 5 additions & 2 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *);

PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
PyAPI_FUNC(void) _Py_BreakPoint(void);
PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
PyAPI_FUNC(void) PyObject_Dump(PyObject *);

// Alias for backward compatibility
#define _PyObject_Dump PyObject_Dump

PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *);

Expand Down Expand Up @@ -387,7 +390,7 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
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
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.
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import enum
import os
import sys
import textwrap
import unittest
Expand All @@ -13,6 +14,9 @@
_testcapi = import_helper.import_module('_testcapi')
_testinternalcapi = import_helper.import_module('_testinternalcapi')

NULL = None
STDERR_FD = 2


class Constant(enum.IntEnum):
Py_CONSTANT_NONE = 0
Expand Down Expand Up @@ -247,5 +251,55 @@ def func(x):

func(object())

def pyobject_dump(self, obj, release_gil=False):
pyobject_dump = _testcapi.pyobject_dump

try:
old_stderr = os.dup(STDERR_FD)
except OSError as exc:
# os.dup(STDERR_FD) is not supported on WASI
self.skipTest(f"os.dup() failed with {exc!r}")

filename = os_helper.TESTFN
try:
try:
with open(filename, "wb") as fp:
fd = fp.fileno()
os.dup2(fd, STDERR_FD)
pyobject_dump(obj, release_gil)
finally:
os.dup2(old_stderr, STDERR_FD)
os.close(old_stderr)

with open(filename) as fp:
return fp.read().rstrip()
finally:
os_helper.unlink(filename)

def test_pyobject_dump(self):
# test string object
str_obj = 'test string'
output = self.pyobject_dump(str_obj)
hex_regex = r'(0x)?[0-9a-fA-F]+'
regex = (
fr"object address : {hex_regex}\n"
r"object refcount : [0-9]+\n"
fr"object type : {hex_regex}\n"
r"object type name: str\n"
r"object repr : 'test string'"
)
self.assertRegex(output, regex)

# release the GIL
output = self.pyobject_dump(str_obj, release_gil=True)
self.assertRegex(output, regex)

# test NULL object
output = self.pyobject_dump(NULL)
hex_regex = r'(0x)?[0-9a-fA-F]+'
self.assertRegex(output,
r'<object at (\(nil\)|0x0|0+) is freed>')


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyObject_Dump` to dump an object to ``stderr``. Function used
for debugging. Patch by Victor Stinner.
25 changes: 25 additions & 0 deletions Modules/_testcapi/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,30 @@ is_uniquely_referenced(PyObject *self, PyObject *op)
}


static PyObject *
pyobject_dump(PyObject *self, PyObject *args)
{
PyObject *op;
int release_gil = 0;

if (!PyArg_ParseTuple(args, "O|i", &op, &release_gil)) {
return NULL;
}
NULLABLE(op);

if (release_gil) {
Py_BEGIN_ALLOW_THREADS
PyObject_Dump(op);
Py_END_ALLOW_THREADS

}
else {
PyObject_Dump(op);
}
Py_RETURN_NONE;
}


static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
Expand All @@ -511,6 +535,7 @@ static PyMethodDef test_methods[] = {
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
{"pyobject_dump", pyobject_dump, METH_VARARGS},
{NULL},
};

Expand Down
4 changes: 2 additions & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ _PyObject_IsFreed(PyObject *op)

/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
void
_PyObject_Dump(PyObject* op)
PyObject_Dump(PyObject* op)
{
if (_PyObject_IsFreed(op)) {
/* It seems like the object memory has been freed:
Expand Down Expand Up @@ -3150,7 +3150,7 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,

/* 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);
PyObject_Dump(obj);

fprintf(stderr, "\n");
fflush(stderr);
Expand Down
2 changes: 1 addition & 1 deletion Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ unicode_check_encoding_errors(const char *encoding, const char *errors)
}

/* Disable checks during Python finalization. For example, it allows to
call _PyObject_Dump() during finalization for debugging purpose. */
call PyObject_Dump() during finalization for debugging purpose. */
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
return 0;
}
Expand Down
2 changes: 1 addition & 1 deletion Python/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2235,7 +2235,7 @@ _PyGC_Fini(PyInterpreterState *interp)
void
_PyGC_Dump(PyGC_Head *g)
{
_PyObject_Dump(FROM_GC(g));
PyObject_Dump(FROM_GC(g));
}


Expand Down
8 changes: 4 additions & 4 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,7 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
}
if (print_exception_recursive(&ctx, value) < 0) {
PyErr_Clear();
_PyObject_Dump(value);
PyObject_Dump(value);
fprintf(stderr, "lost sys.stderr\n");
}
Py_XDECREF(ctx.seen);
Expand All @@ -1199,14 +1199,14 @@ PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb)
PyObject *file;
if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
PyObject *exc = PyErr_GetRaisedException();
_PyObject_Dump(value);
PyObject_Dump(value);
fprintf(stderr, "lost sys.stderr\n");
_PyObject_Dump(exc);
PyObject_Dump(exc);
Py_DECREF(exc);
return;
}
if (file == NULL) {
_PyObject_Dump(value);
PyObject_Dump(value);
fprintf(stderr, "lost sys.stderr\n");
return;
}
Expand Down
Loading