Skip to content

Commit 6876257

Browse files
authored
bpo-36389: _PyObject_CheckConsistency() available in release mode (GH-16612)
bpo-36389, bpo-38376: The _PyObject_CheckConsistency() function is now also available in release mode. For example, it can be used to debug a crash in the visit_decref() function of the GC. Modify the following functions to also work in release mode: * _PyDict_CheckConsistency() * _PyObject_CheckConsistency() * _PyType_CheckConsistency() * _PyUnicode_CheckConsistency() Other changes: * _PyMem_IsPtrFreed(ptr) now also returns 1 if ptr is NULL (equals to 0). * _PyBytesWriter_CheckConsistency() now returns 1 and is only used with assert(). * Reorder _PyObject_Dump() to write safe fields first, and only attempt to render repr() at the end.
1 parent 321def8 commit 6876257

File tree

14 files changed

+148
-133
lines changed

14 files changed

+148
-133
lines changed

Include/cpython/unicodeobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ typedef struct {
246246
} data; /* Canonical, smallest-form Unicode buffer */
247247
} PyUnicodeObject;
248248

249+
PyAPI_FUNC(int) _PyUnicode_CheckConsistency(
250+
PyObject *op,
251+
int check_content);
252+
249253
/* Fast access macros */
250254
#define PyUnicode_WSTR_LENGTH(op) \
251255
(PyUnicode_IS_COMPACT_ASCII(op) ? \

Include/internal/pycore_object.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ extern "C" {
1111
#include "pycore_pystate.h" /* _PyRuntime.gc */
1212

1313
PyAPI_FUNC(int) _PyType_CheckConsistency(PyTypeObject *type);
14-
PyAPI_FUNC(int) _PyUnicode_CheckConsistency(PyObject *op, int check_content);
1514
PyAPI_FUNC(int) _PyDict_CheckConsistency(PyObject *mp, int check_content);
1615

1716
/* Tell the GC to track this object.

Include/internal/pycore_pymem.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,9 @@ PyAPI_FUNC(int) _PyMem_SetDefaultAllocator(
155155
PyMemAllocatorEx *old_alloc);
156156

157157
/* Heuristic checking if a pointer value is newly allocated
158-
(uninitialized) or newly freed. The pointer is not dereferenced, only the
159-
pointer value is checked.
158+
(uninitialized), newly freed or NULL (is equal to zero).
159+
160+
The pointer is not dereferenced, only the pointer value is checked.
160161
161162
The heuristic relies on the debug hooks on Python memory allocators which
162163
fills newly allocated memory with CLEANBYTE (0xCD) and newly freed memory
@@ -166,11 +167,13 @@ static inline int _PyMem_IsPtrFreed(void *ptr)
166167
{
167168
uintptr_t value = (uintptr_t)ptr;
168169
#if SIZEOF_VOID_P == 8
169-
return (value == (uintptr_t)0xCDCDCDCDCDCDCDCD
170+
return (value == 0
171+
|| value == (uintptr_t)0xCDCDCDCDCDCDCDCD
170172
|| value == (uintptr_t)0xDDDDDDDDDDDDDDDD
171173
|| value == (uintptr_t)0xFDFDFDFDFDFDFDFD);
172174
#elif SIZEOF_VOID_P == 4
173-
return (value == (uintptr_t)0xCDCDCDCD
175+
return (value == 0
176+
|| value == (uintptr_t)0xCDCDCDCD
174177
|| value == (uintptr_t)0xDDDDDDDD
175178
|| value == (uintptr_t)0xFDFDFDFD);
176179
#else

Include/unicodeobject.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,16 +1032,6 @@ PyAPI_FUNC(int) PyUnicode_IsIdentifier(PyObject *s);
10321032

10331033
/* === Characters Type APIs =============================================== */
10341034

1035-
#if defined(Py_DEBUG) && !defined(Py_LIMITED_API)
1036-
PyAPI_FUNC(int) _PyUnicode_CheckConsistency(
1037-
PyObject *op,
1038-
int check_content);
1039-
#elif !defined(NDEBUG)
1040-
/* For asserts that call _PyUnicode_CheckConsistency(), which would
1041-
* otherwise be a problem when building with asserts but without Py_DEBUG. */
1042-
#define _PyUnicode_CheckConsistency(op, check_content) PyUnicode_Check(op)
1043-
#endif
1044-
10451035
#ifndef Py_LIMITED_API
10461036
# define Py_CPYTHON_UNICODEOBJECT_H
10471037
# include "cpython/unicodeobject.h"

Lib/test/test_capi.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,9 @@ def check_pyobject_is_freed(self, func_name):
719719
''')
720720
assert_python_ok('-c', code, PYTHONMALLOC=self.PYTHONMALLOC)
721721

722+
def test_pyobject_null_is_freed(self):
723+
self.check_pyobject_is_freed('check_pyobject_null_is_freed')
724+
722725
def test_pyobject_uninitialized_is_freed(self):
723726
self.check_pyobject_is_freed('check_pyobject_uninitialized_is_freed')
724727

Lib/test/test_gc.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -985,16 +985,19 @@ def test_refcount_errors(self):
985985
br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.')
986986
self.assertRegex(stderr,
987987
br'refcount is too small')
988+
# "address : 0x7fb5062efc18"
989+
# "address : 7FB5062EFC18"
990+
address_regex = br'[0-9a-fA-Fx]+'
988991
self.assertRegex(stderr,
989-
br'object : \[1, 2, 3\]')
992+
br'object address : ' + address_regex)
990993
self.assertRegex(stderr,
991-
br'type : list')
994+
br'object refcount : 1')
992995
self.assertRegex(stderr,
993-
br'refcount: 1')
994-
# "address : 0x7fb5062efc18"
995-
# "address : 7FB5062EFC18"
996+
br'object type : ' + address_regex)
997+
self.assertRegex(stderr,
998+
br'object type name: list')
996999
self.assertRegex(stderr,
997-
br'address : [0-9a-fA-Fx]+')
1000+
br'object repr : \[1, 2, 3\]')
9981001

9991002

10001003
class GCTogglingTests(unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The ``_PyObject_CheckConsistency()`` function is now also available in release
2+
mode. For example, it can be used to debug a crash in the ``visit_decref()``
3+
function of the GC.

Modules/_testcapimodule.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,6 +4611,14 @@ test_pyobject_is_freed(const char *test_name, PyObject *op)
46114611
}
46124612

46134613

4614+
static PyObject*
4615+
check_pyobject_null_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
4616+
{
4617+
PyObject *op = NULL;
4618+
return test_pyobject_is_freed("check_pyobject_null_is_freed", op);
4619+
}
4620+
4621+
46144622
static PyObject*
46154623
check_pyobject_uninitialized_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
46164624
{
@@ -5475,6 +5483,7 @@ static PyMethodDef TestMethods[] = {
54755483
{"pymem_api_misuse", pymem_api_misuse, METH_NOARGS},
54765484
{"pymem_malloc_without_gil", pymem_malloc_without_gil, METH_NOARGS},
54775485
{"pymem_getallocatorsname", test_pymem_getallocatorsname, METH_NOARGS},
5486+
{"check_pyobject_null_is_freed", check_pyobject_null_is_freed, METH_NOARGS},
54785487
{"check_pyobject_uninitialized_is_freed", check_pyobject_uninitialized_is_freed, METH_NOARGS},
54795488
{"check_pyobject_forbidden_bytes_is_freed", check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
54805489
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},

Modules/gcmodule.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,6 @@ update_refs(PyGC_Head *containers)
375375
static int
376376
visit_decref(PyObject *op, void *data)
377377
{
378-
assert(op != NULL);
379378
_PyObject_ASSERT(op, !_PyObject_IsFreed(op));
380379

381380
if (PyObject_IS_GC(op)) {

Objects/bytesobject.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,10 +3206,10 @@ _PyBytesWriter_GetSize(_PyBytesWriter *writer, char *str)
32063206
return str - start;
32073207
}
32083208

3209-
Py_LOCAL_INLINE(void)
3209+
#ifndef NDEBUG
3210+
Py_LOCAL_INLINE(int)
32103211
_PyBytesWriter_CheckConsistency(_PyBytesWriter *writer, char *str)
32113212
{
3212-
#ifdef Py_DEBUG
32133213
char *start, *end;
32143214

32153215
if (writer->use_small_buffer) {
@@ -3239,15 +3239,16 @@ _PyBytesWriter_CheckConsistency(_PyBytesWriter *writer, char *str)
32393239
end = start + writer->allocated;
32403240
assert(str != NULL);
32413241
assert(start <= str && str <= end);
3242-
#endif
3242+
return 1;
32433243
}
3244+
#endif
32443245

32453246
void*
32463247
_PyBytesWriter_Resize(_PyBytesWriter *writer, void *str, Py_ssize_t size)
32473248
{
32483249
Py_ssize_t allocated, pos;
32493250

3250-
_PyBytesWriter_CheckConsistency(writer, str);
3251+
assert(_PyBytesWriter_CheckConsistency(writer, str));
32513252
assert(writer->allocated < size);
32523253

32533254
allocated = size;
@@ -3303,7 +3304,7 @@ _PyBytesWriter_Resize(_PyBytesWriter *writer, void *str, Py_ssize_t size)
33033304
writer->allocated = allocated;
33043305

33053306
str = _PyBytesWriter_AsString(writer) + pos;
3306-
_PyBytesWriter_CheckConsistency(writer, str);
3307+
assert(_PyBytesWriter_CheckConsistency(writer, str));
33073308
return str;
33083309

33093310
error:
@@ -3316,7 +3317,7 @@ _PyBytesWriter_Prepare(_PyBytesWriter *writer, void *str, Py_ssize_t size)
33163317
{
33173318
Py_ssize_t new_min_size;
33183319

3319-
_PyBytesWriter_CheckConsistency(writer, str);
3320+
assert(_PyBytesWriter_CheckConsistency(writer, str));
33203321
assert(size >= 0);
33213322

33223323
if (size == 0) {
@@ -3377,7 +3378,7 @@ _PyBytesWriter_Finish(_PyBytesWriter *writer, void *str)
33773378
Py_ssize_t size;
33783379
PyObject *result;
33793380

3380-
_PyBytesWriter_CheckConsistency(writer, str);
3381+
assert(_PyBytesWriter_CheckConsistency(writer, str));
33813382

33823383
size = _PyBytesWriter_GetSize(writer, str);
33833384
if (size == 0 && !writer->use_bytearray) {

0 commit comments

Comments
 (0)