Skip to content

Commit 600f3fe

Browse files
vstinnerZeroIntensitykumaraditya303encukou
authored
pythongh-141070: Add PyUnstable_Object_Dump() function (python#141072)
* Promote _PyObject_Dump() as a public function. * Keep _PyObject_Dump() alias to PyUnstable_Object_Dump() for backward compatibility. * Replace _PyObject_Dump() with PyUnstable_Object_Dump(). Co-authored-by: Peter Bierma <[email protected]> Co-authored-by: Kumar Aditya <[email protected]> Co-authored-by: Petr Viktorin <[email protected]>
1 parent 4695ec1 commit 600f3fe

File tree

11 files changed

+135
-18
lines changed

11 files changed

+135
-18
lines changed

Doc/c-api/object.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,35 @@ Object Protocol
8585
instead of the :func:`repr`.
8686
8787
88+
.. c:function:: void PyUnstable_Object_Dump(PyObject *op)
89+
90+
Dump an object *op* to ``stderr``. This should only be used for debugging.
91+
92+
The output is intended to try dumping objects even after memory corruption:
93+
94+
* Information is written starting with fields that are the least likely to
95+
crash when accessed.
96+
* This function can be called without an :term:`attached thread state`, but
97+
it's not recommended to do so: it can cause deadlocks.
98+
* An object that does not belong to the current interpreter may be dumped,
99+
but this may also cause crashes or unintended behavior.
100+
* Implement a heuristic to detect if the object memory has been freed. Don't
101+
display the object contents in this case, only its memory address.
102+
* The output format may change at any time.
103+
104+
Example of output:
105+
106+
.. code-block:: output
107+
108+
object address : 0x7f80124702c0
109+
object refcount : 2
110+
object type : 0x9902e0
111+
object type name: str
112+
object repr : 'abcdef'
113+
114+
.. versionadded:: next
115+
116+
88117
.. c:function:: int PyObject_HasAttrWithError(PyObject *o, PyObject *attr_name)
89118
90119
Returns ``1`` if *o* has the attribute *attr_name*, and ``0`` otherwise.

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,19 +1084,23 @@ New features
10841084

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

1087+
* Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating
1088+
a module from a *spec* and *initfunc*.
1089+
(Contributed by Itamar Oren in :gh:`116146`.)
1090+
10871091
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
10881092
(Contributed by Victor Stinner in :gh:`111489`.)
10891093

1094+
* Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``.
1095+
It should only be used for debugging.
1096+
(Contributed by Victor Stinner in :gh:`141070`.)
1097+
10901098
* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and
10911099
:c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set
10921100
the stack protection base address and stack protection size of a Python
10931101
thread state.
10941102
(Contributed by Victor Stinner in :gh:`139653`.)
10951103

1096-
* Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating
1097-
a module from a *spec* and *initfunc*.
1098-
(Contributed by Itamar Oren in :gh:`116146`.)
1099-
11001104

11011105
Changed C APIs
11021106
--------------

Include/cpython/object.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@ PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *);
295295

296296
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
297297
PyAPI_FUNC(void) _Py_BreakPoint(void);
298-
PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
298+
PyAPI_FUNC(void) PyUnstable_Object_Dump(PyObject *);
299+
300+
// Alias for backward compatibility
301+
#define _PyObject_Dump PyUnstable_Object_Dump
299302

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

@@ -387,10 +390,11 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
387390
process with a message on stderr if the given condition fails to hold,
388391
but compile away to nothing if NDEBUG is defined.
389392
390-
However, before aborting, Python will also try to call _PyObject_Dump() on
391-
the given object. This may be of use when investigating bugs in which a
392-
particular object is corrupt (e.g. buggy a tp_visit method in an extension
393-
module breaking the garbage collector), to help locate the broken objects.
393+
However, before aborting, Python will also try to call
394+
PyUnstable_Object_Dump() on the given object. This may be of use when
395+
investigating bugs in which a particular object is corrupt (e.g. buggy a
396+
tp_visit method in an extension module breaking the garbage collector), to
397+
help locate the broken objects.
394398
395399
The WITH_MSG variant allows you to supply an additional message that Python
396400
will attempt to print to stderr, after the object dump. */

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_capi/test_object.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import enum
2+
import os
23
import sys
34
import textwrap
45
import unittest
@@ -13,6 +14,9 @@
1314
_testcapi = import_helper.import_module('_testcapi')
1415
_testinternalcapi = import_helper.import_module('_testinternalcapi')
1516

17+
NULL = None
18+
STDERR_FD = 2
19+
1620

1721
class Constant(enum.IntEnum):
1822
Py_CONSTANT_NONE = 0
@@ -247,5 +251,53 @@ def func(x):
247251

248252
func(object())
249253

254+
def pyobject_dump(self, obj, release_gil=False):
255+
pyobject_dump = _testcapi.pyobject_dump
256+
257+
try:
258+
old_stderr = os.dup(STDERR_FD)
259+
except OSError as exc:
260+
# os.dup(STDERR_FD) is not supported on WASI
261+
self.skipTest(f"os.dup() failed with {exc!r}")
262+
263+
filename = os_helper.TESTFN
264+
try:
265+
try:
266+
with open(filename, "wb") as fp:
267+
fd = fp.fileno()
268+
os.dup2(fd, STDERR_FD)
269+
pyobject_dump(obj, release_gil)
270+
finally:
271+
os.dup2(old_stderr, STDERR_FD)
272+
os.close(old_stderr)
273+
274+
with open(filename) as fp:
275+
return fp.read().rstrip()
276+
finally:
277+
os_helper.unlink(filename)
278+
279+
def test_pyobject_dump(self):
280+
# test string object
281+
str_obj = 'test string'
282+
output = self.pyobject_dump(str_obj)
283+
hex_regex = r'(0x)?[0-9a-fA-F]+'
284+
regex = (
285+
fr"object address : {hex_regex}\n"
286+
r"object refcount : [0-9]+\n"
287+
fr"object type : {hex_regex}\n"
288+
r"object type name: str\n"
289+
r"object repr : 'test string'"
290+
)
291+
self.assertRegex(output, regex)
292+
293+
# release the GIL
294+
output = self.pyobject_dump(str_obj, release_gil=True)
295+
self.assertRegex(output, regex)
296+
297+
# test NULL object
298+
output = self.pyobject_dump(NULL)
299+
self.assertRegex(output, r'<object at .* is freed>')
300+
301+
250302
if __name__ == "__main__":
251303
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``. It should
2+
only be used for debugging. Patch by Victor Stinner.

Modules/_testcapi/object.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,30 @@ is_uniquely_referenced(PyObject *self, PyObject *op)
485485
}
486486

487487

488+
static PyObject *
489+
pyobject_dump(PyObject *self, PyObject *args)
490+
{
491+
PyObject *op;
492+
int release_gil = 0;
493+
494+
if (!PyArg_ParseTuple(args, "O|i", &op, &release_gil)) {
495+
return NULL;
496+
}
497+
NULLABLE(op);
498+
499+
if (release_gil) {
500+
Py_BEGIN_ALLOW_THREADS
501+
PyUnstable_Object_Dump(op);
502+
Py_END_ALLOW_THREADS
503+
504+
}
505+
else {
506+
PyUnstable_Object_Dump(op);
507+
}
508+
Py_RETURN_NONE;
509+
}
510+
511+
488512
static PyMethodDef test_methods[] = {
489513
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
490514
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
@@ -511,6 +535,7 @@ static PyMethodDef test_methods[] = {
511535
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
512536
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
513537
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
538+
{"pyobject_dump", pyobject_dump, METH_VARARGS},
514539
{NULL},
515540
};
516541

Objects/object.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ _PyObject_IsFreed(PyObject *op)
713713

714714
/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
715715
void
716-
_PyObject_Dump(PyObject* op)
716+
PyUnstable_Object_Dump(PyObject* op)
717717
{
718718
if (_PyObject_IsFreed(op)) {
719719
/* It seems like the object memory has been freed:
@@ -3150,7 +3150,7 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
31503150

31513151
/* This might succeed or fail, but we're about to abort, so at least
31523152
try to provide any extra info we can: */
3153-
_PyObject_Dump(obj);
3153+
PyUnstable_Object_Dump(obj);
31543154

31553155
fprintf(stderr, "\n");
31563156
fflush(stderr);

Objects/unicodeobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,8 @@ unicode_check_encoding_errors(const char *encoding, const char *errors)
547547
}
548548

549549
/* Disable checks during Python finalization. For example, it allows to
550-
call _PyObject_Dump() during finalization for debugging purpose. */
550+
* call PyUnstable_Object_Dump() during finalization for debugging purpose.
551+
*/
551552
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
552553
return 0;
553554
}

Python/gc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2237,7 +2237,7 @@ _PyGC_Fini(PyInterpreterState *interp)
22372237
void
22382238
_PyGC_Dump(PyGC_Head *g)
22392239
{
2240-
_PyObject_Dump(FROM_GC(g));
2240+
PyUnstable_Object_Dump(FROM_GC(g));
22412241
}
22422242

22432243

0 commit comments

Comments
 (0)