Skip to content

Commit 0d6dfd6

Browse files
authored
gh-106320: Remove private _PyObject C API (#107147)
Move private debug _PyObject functions to the internal C API (pycore_object.h): * _PyDebugAllocatorStats() * _PyObject_CheckConsistency() * _PyObject_DebugTypeStats() * _PyObject_IsFreed() No longer export most of these functions, except of _PyObject_IsFreed(). Move test functions using _PyObject_IsFreed() from _testcapi to _testinternalcapi. check_pyobject_is_freed() test no longer catch _testcapi.error: the tested function cannot raise _testcapi.error.
1 parent 0810b0c commit 0d6dfd6

File tree

12 files changed

+116
-112
lines changed

12 files changed

+116
-112
lines changed

Include/cpython/object.h

-22
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,6 @@ PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *);
289289
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
290290
PyAPI_FUNC(void) _Py_BreakPoint(void);
291291
PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
292-
PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
293292

294293
PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *);
295294
PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, _Py_Identifier *);
@@ -377,12 +376,6 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
377376
#endif
378377

379378

380-
PyAPI_FUNC(void)
381-
_PyDebugAllocatorStats(FILE *out, const char *block_name, int num_blocks,
382-
size_t sizeof_block);
383-
PyAPI_FUNC(void)
384-
_PyObject_DebugTypeStats(FILE *out);
385-
386379
/* Define a pair of assertion macros:
387380
_PyObject_ASSERT_FROM(), _PyObject_ASSERT_WITH_MSG() and _PyObject_ASSERT().
388381
@@ -431,21 +424,6 @@ PyAPI_FUNC(void) _Py_NO_RETURN _PyObject_AssertFailed(
431424
int line,
432425
const char *function);
433426

434-
/* Check if an object is consistent. For example, ensure that the reference
435-
counter is greater than or equal to 1, and ensure that ob_type is not NULL.
436-
437-
Call _PyObject_AssertFailed() if the object is inconsistent.
438-
439-
If check_content is zero, only check header fields: reduce the overhead.
440-
441-
The function always return 1. The return value is just here to be able to
442-
write:
443-
444-
assert(_PyObject_CheckConsistency(obj, 1)); */
445-
PyAPI_FUNC(int) _PyObject_CheckConsistency(
446-
PyObject *op,
447-
int check_content);
448-
449427

450428
/* Trashcan mechanism, thanks to Christian Tismer.
451429

Include/internal/pycore_object.h

+21
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ extern "C" {
1414
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1515
#include "pycore_runtime.h" // _PyRuntime
1616

17+
/* Check if an object is consistent. For example, ensure that the reference
18+
counter is greater than or equal to 1, and ensure that ob_type is not NULL.
19+
20+
Call _PyObject_AssertFailed() if the object is inconsistent.
21+
22+
If check_content is zero, only check header fields: reduce the overhead.
23+
24+
The function always return 1. The return value is just here to be able to
25+
write:
26+
27+
assert(_PyObject_CheckConsistency(obj, 1)); */
28+
extern int _PyObject_CheckConsistency(PyObject *op, int check_content);
29+
30+
extern void _PyDebugAllocatorStats(FILE *out, const char *block_name,
31+
int num_blocks, size_t sizeof_block);
32+
33+
extern void _PyObject_DebugTypeStats(FILE *out);
34+
35+
// Export for shared _testinternalcapi extension
36+
PyAPI_DATA(int) _PyObject_IsFreed(PyObject *);
37+
1738
/* We need to maintain an internal copy of Py{Var}Object_HEAD_INIT to avoid
1839
designated initializer conflicts in C++20. If we use the deinition in
1940
object.h, we will be mixing designated and non-designated initializers in

Lib/test/test_capi/test_mem.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from test.support.script_helper import assert_python_failure, assert_python_ok
99

1010

11-
# Skip this test if the _testcapi module isn't available.
11+
# Skip this test if the _testcapi and _testinternalcapi extensions are not
12+
# available.
1213
_testcapi = import_helper.import_module('_testcapi')
14+
_testinternalcapi = import_helper.import_module('_testinternalcapi')
1315

1416
@requires_subprocess()
1517
class PyMemDebugTests(unittest.TestCase):
@@ -84,16 +86,13 @@ def test_pyobject_malloc_without_gil(self):
8486

8587
def check_pyobject_is_freed(self, func_name):
8688
code = textwrap.dedent(f'''
87-
import gc, os, sys, _testcapi
89+
import gc, os, sys, _testinternalcapi
8890
# Disable the GC to avoid crash on GC collection
8991
gc.disable()
90-
try:
91-
_testcapi.{func_name}()
92-
# Exit immediately to avoid a crash while deallocating
93-
# the invalid object
94-
os._exit(0)
95-
except _testcapi.error:
96-
os._exit(1)
92+
_testinternalcapi.{func_name}()
93+
# Exit immediately to avoid a crash while deallocating
94+
# the invalid object
95+
os._exit(0)
9796
''')
9897
assert_python_ok(
9998
'-c', code,

Modules/_testcapi/mem.c

-75
Original file line numberDiff line numberDiff line change
@@ -526,75 +526,6 @@ pymem_malloc_without_gil(PyObject *self, PyObject *args)
526526
Py_RETURN_NONE;
527527
}
528528

529-
static PyObject *
530-
test_pyobject_is_freed(const char *test_name, PyObject *op)
531-
{
532-
if (!_PyObject_IsFreed(op)) {
533-
PyErr_SetString(PyExc_AssertionError,
534-
"object is not seen as freed");
535-
return NULL;
536-
}
537-
Py_RETURN_NONE;
538-
}
539-
540-
static PyObject *
541-
check_pyobject_null_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
542-
{
543-
PyObject *op = NULL;
544-
return test_pyobject_is_freed("check_pyobject_null_is_freed", op);
545-
}
546-
547-
548-
static PyObject *
549-
check_pyobject_uninitialized_is_freed(PyObject *self,
550-
PyObject *Py_UNUSED(args))
551-
{
552-
PyObject *op = (PyObject *)PyObject_Malloc(sizeof(PyObject));
553-
if (op == NULL) {
554-
return NULL;
555-
}
556-
/* Initialize reference count to avoid early crash in ceval or GC */
557-
Py_SET_REFCNT(op, 1);
558-
/* object fields like ob_type are uninitialized! */
559-
return test_pyobject_is_freed("check_pyobject_uninitialized_is_freed", op);
560-
}
561-
562-
563-
static PyObject *
564-
check_pyobject_forbidden_bytes_is_freed(PyObject *self,
565-
PyObject *Py_UNUSED(args))
566-
{
567-
/* Allocate an incomplete PyObject structure: truncate 'ob_type' field */
568-
PyObject *op = (PyObject *)PyObject_Malloc(offsetof(PyObject, ob_type));
569-
if (op == NULL) {
570-
return NULL;
571-
}
572-
/* Initialize reference count to avoid early crash in ceval or GC */
573-
Py_SET_REFCNT(op, 1);
574-
/* ob_type field is after the memory block: part of "forbidden bytes"
575-
when using debug hooks on memory allocators! */
576-
return test_pyobject_is_freed("check_pyobject_forbidden_bytes_is_freed", op);
577-
}
578-
579-
580-
static PyObject *
581-
check_pyobject_freed_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
582-
{
583-
/* This test would fail if run with the address sanitizer */
584-
#ifdef _Py_ADDRESS_SANITIZER
585-
Py_RETURN_NONE;
586-
#else
587-
PyObject *op = PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type);
588-
if (op == NULL) {
589-
return NULL;
590-
}
591-
Py_TYPE(op)->tp_dealloc(op);
592-
/* Reset reference count to avoid early crash in ceval or GC */
593-
Py_SET_REFCNT(op, 1);
594-
/* object memory is freed! */
595-
return test_pyobject_is_freed("check_pyobject_freed_is_freed", op);
596-
#endif
597-
}
598529

599530
// Tracemalloc tests
600531
static PyObject *
@@ -656,12 +587,6 @@ tracemalloc_untrack(PyObject *self, PyObject *args)
656587
}
657588

658589
static PyMethodDef test_methods[] = {
659-
{"check_pyobject_forbidden_bytes_is_freed",
660-
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
661-
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
662-
{"check_pyobject_null_is_freed", check_pyobject_null_is_freed, METH_NOARGS},
663-
{"check_pyobject_uninitialized_is_freed",
664-
check_pyobject_uninitialized_is_freed, METH_NOARGS},
665590
{"pymem_api_misuse", pymem_api_misuse, METH_NOARGS},
666591
{"pymem_buffer_overflow", pymem_buffer_overflow, METH_NOARGS},
667592
{"pymem_getallocatorsname", test_pymem_getallocatorsname, METH_NOARGS},

Modules/_testclinic.c

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#undef NDEBUG
77

88
#include "Python.h"
9+
#include "pycore_object.h" // _PyObject_IsFreed()
910

1011

1112
// Used for clone_with_conv_f1 and clone_with_conv_v2

Modules/_testinternalcapi.c

+80-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#undef NDEBUG
1111

1212
#include "Python.h"
13-
#include "frameobject.h"
1413
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
1514
#include "pycore_bitutils.h" // _Py_bswap32()
1615
#include "pycore_bytesobject.h" // _PyBytes_Find()
@@ -23,9 +22,12 @@
2322
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
2423
#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()
2524
#include "pycore_interp_id.h" // _PyInterpreterID_LookUp()
25+
#include "pycore_object.h" // _PyObject_IsFreed()
2626
#include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal()
2727
#include "pycore_pyerrors.h" // _Py_UTF8_Edit_Cost()
2828
#include "pycore_pystate.h" // _PyThreadState_GET()
29+
30+
#include "frameobject.h"
2931
#include "osdefs.h" // MAXPATHLEN
3032

3133
#include "clinic/_testinternalcapi.c.h"
@@ -1446,6 +1448,77 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
14461448
}
14471449

14481450

1451+
static PyObject *
1452+
test_pyobject_is_freed(const char *test_name, PyObject *op)
1453+
{
1454+
if (!_PyObject_IsFreed(op)) {
1455+
PyErr_SetString(PyExc_AssertionError,
1456+
"object is not seen as freed");
1457+
return NULL;
1458+
}
1459+
Py_RETURN_NONE;
1460+
}
1461+
1462+
static PyObject *
1463+
check_pyobject_null_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
1464+
{
1465+
PyObject *op = NULL;
1466+
return test_pyobject_is_freed("check_pyobject_null_is_freed", op);
1467+
}
1468+
1469+
1470+
static PyObject *
1471+
check_pyobject_uninitialized_is_freed(PyObject *self,
1472+
PyObject *Py_UNUSED(args))
1473+
{
1474+
PyObject *op = (PyObject *)PyObject_Malloc(sizeof(PyObject));
1475+
if (op == NULL) {
1476+
return NULL;
1477+
}
1478+
/* Initialize reference count to avoid early crash in ceval or GC */
1479+
Py_SET_REFCNT(op, 1);
1480+
/* object fields like ob_type are uninitialized! */
1481+
return test_pyobject_is_freed("check_pyobject_uninitialized_is_freed", op);
1482+
}
1483+
1484+
1485+
static PyObject *
1486+
check_pyobject_forbidden_bytes_is_freed(PyObject *self,
1487+
PyObject *Py_UNUSED(args))
1488+
{
1489+
/* Allocate an incomplete PyObject structure: truncate 'ob_type' field */
1490+
PyObject *op = (PyObject *)PyObject_Malloc(offsetof(PyObject, ob_type));
1491+
if (op == NULL) {
1492+
return NULL;
1493+
}
1494+
/* Initialize reference count to avoid early crash in ceval or GC */
1495+
Py_SET_REFCNT(op, 1);
1496+
/* ob_type field is after the memory block: part of "forbidden bytes"
1497+
when using debug hooks on memory allocators! */
1498+
return test_pyobject_is_freed("check_pyobject_forbidden_bytes_is_freed", op);
1499+
}
1500+
1501+
1502+
static PyObject *
1503+
check_pyobject_freed_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
1504+
{
1505+
/* This test would fail if run with the address sanitizer */
1506+
#ifdef _Py_ADDRESS_SANITIZER
1507+
Py_RETURN_NONE;
1508+
#else
1509+
PyObject *op = PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type);
1510+
if (op == NULL) {
1511+
return NULL;
1512+
}
1513+
Py_TYPE(op)->tp_dealloc(op);
1514+
/* Reset reference count to avoid early crash in ceval or GC */
1515+
Py_SET_REFCNT(op, 1);
1516+
/* object memory is freed! */
1517+
return test_pyobject_is_freed("check_pyobject_freed_is_freed", op);
1518+
#endif
1519+
}
1520+
1521+
14491522
static PyMethodDef module_functions[] = {
14501523
{"get_configs", get_configs, METH_NOARGS},
14511524
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@@ -1502,6 +1575,12 @@ static PyMethodDef module_functions[] = {
15021575
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
15031576
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
15041577
{"test_atexit", test_atexit, METH_NOARGS},
1578+
{"check_pyobject_forbidden_bytes_is_freed",
1579+
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
1580+
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
1581+
{"check_pyobject_null_is_freed", check_pyobject_null_is_freed, METH_NOARGS},
1582+
{"check_pyobject_uninitialized_is_freed",
1583+
check_pyobject_uninitialized_is_freed, METH_NOARGS},
15051584
{NULL, NULL} /* sentinel */
15061585
};
15071586

Objects/dictobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ As a consequence of this, split keys have a maximum size of 16.
118118
#include "pycore_code.h" // stats
119119
#include "pycore_dict.h" // PyDictKeysObject
120120
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
121-
#include "pycore_object.h" // _PyObject_GC_TRACK()
121+
#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
122122
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
123123
#include "pycore_pystate.h" // _PyThreadState_GET()
124124
#include "pycore_setobject.h" // _PySet_NextEntry()

Objects/floatobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include "pycore_interp.h" // _PyInterpreterState.float_state
1111
#include "pycore_long.h" // _PyLong_GetOne()
1212
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
13-
#include "pycore_object.h" // _PyObject_Init()
13+
#include "pycore_object.h" // _PyObject_Init(), _PyDebugAllocatorStats()
1414
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
1515
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1616
#include "pycore_structseq.h" // _PyStructSequence_FiniBuiltin()

Objects/listobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include "pycore_list.h" // struct _Py_list_state, _PyListIterObject
77
#include "pycore_long.h" // _PyLong_DigitCount
88
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
9-
#include "pycore_object.h" // _PyObject_GC_TRACK()
9+
#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
1010
#include "pycore_tuple.h" // _PyTuple_FromArray()
1111
#include <stddef.h>
1212

Objects/obmalloc.c

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Python.h"
44
#include "pycore_code.h" // stats
5+
#include "pycore_object.h" // _PyDebugAllocatorStats() definition
56
#include "pycore_obmalloc.h"
67
#include "pycore_pyerrors.h" // _Py_FatalErrorFormat()
78
#include "pycore_pymem.h"

Objects/tupleobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
77
#include "pycore_initconfig.h" // _PyStatus_OK()
88
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
9-
#include "pycore_object.h" // _PyObject_GC_TRACK(), _Py_FatalRefcountError()
9+
#include "pycore_object.h" // _PyObject_GC_TRACK(), _Py_FatalRefcountError(), _PyDebugAllocatorStats()
1010

1111
/*[clinic input]
1212
class tuple "PyTupleObject *" "&PyTuple_Type"

Python/sysmodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Data members:
2222
#include "pycore_long.h" // _PY_LONG_MAX_STR_DIGITS_THRESHOLD
2323
#include "pycore_modsupport.h" // _PyModule_CreateInitialized()
2424
#include "pycore_namespace.h" // _PyNamespace_New()
25-
#include "pycore_object.h" // _PyObject_IS_GC()
25+
#include "pycore_object.h" // _PyObject_IS_GC(), _PyObject_DebugTypeStats()
2626
#include "pycore_pathconfig.h" // _PyPathConfig_ComputeSysPath0()
2727
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
2828
#include "pycore_pylifecycle.h" // _PyErr_WriteUnraisableDefaultHook()

0 commit comments

Comments
 (0)