Skip to content

Commit 0fc91ee

Browse files
authored
bpo-36389: Add _PyObject_CheckConsistency() function (GH-12803)
Add a new _PyObject_CheckConsistency() function which can be used to help debugging. The function is available in release mode. Add a 'check_content' parameter to _PyDict_CheckConsistency().
1 parent 23a683a commit 0fc91ee

File tree

6 files changed

+158
-114
lines changed

6 files changed

+158
-114
lines changed

Include/cpython/object.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,21 @@ PyAPI_FUNC(void) _PyObject_AssertFailed(
446446
int line,
447447
const char *function);
448448

449+
/* Check if an object is consistent. For example, ensure that the reference
450+
counter is greater than or equal to 1, and ensure that ob_type is not NULL.
451+
452+
Call _PyObject_AssertFailed() if the object is inconsistent.
453+
454+
If check_content is zero, only check header fields: reduce the overhead.
455+
456+
The function always return 1. The return value is just here to be able to
457+
write:
458+
459+
assert(_PyObject_CheckConsistency(obj, 1)); */
460+
PyAPI_FUNC(int) _PyObject_CheckConsistency(
461+
PyObject *op,
462+
int check_content);
463+
449464
#ifdef __cplusplus
450465
}
451466
#endif

Include/internal/pycore_object.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ extern "C" {
1010

1111
#include "pycore_pystate.h" /* _PyRuntime */
1212

13+
PyAPI_FUNC(int) _PyType_CheckConsistency(PyTypeObject *type);
14+
PyAPI_FUNC(int) _PyUnicode_CheckConsistency(PyObject *op, int check_content);
15+
PyAPI_FUNC(int) _PyDict_CheckConsistency(PyObject *mp, int check_content);
16+
1317
/* Tell the GC to track this object.
1418
*
1519
* NB: While the object is tracked by the collector, it must be safe to call the

Objects/dictobject.c

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -449,77 +449,77 @@ static PyObject *empty_values[1] = { NULL };
449449
/* Uncomment to check the dict content in _PyDict_CheckConsistency() */
450450
/* #define DEBUG_PYDICT */
451451

452+
#ifdef DEBUG_PYDICT
453+
# define ASSERT_CONSISTENT(op) assert(_PyDict_CheckConsistency((PyObject *)(op), 1))
454+
#else
455+
# define ASSERT_CONSISTENT(op) assert(_PyDict_CheckConsistency((PyObject *)(op), 0))
456+
#endif
452457

453-
#ifndef NDEBUG
454-
static int
455-
_PyDict_CheckConsistency(PyDictObject *mp)
458+
459+
int
460+
_PyDict_CheckConsistency(PyObject *op, int check_content)
456461
{
457-
#define ASSERT(expr) _PyObject_ASSERT((PyObject *)mp, (expr))
462+
_PyObject_ASSERT(op, PyDict_Check(op));
463+
PyDictObject *mp = (PyDictObject *)op;
458464

459465
PyDictKeysObject *keys = mp->ma_keys;
460466
int splitted = _PyDict_HasSplitTable(mp);
461467
Py_ssize_t usable = USABLE_FRACTION(keys->dk_size);
462-
#ifdef DEBUG_PYDICT
463-
PyDictKeyEntry *entries = DK_ENTRIES(keys);
464-
Py_ssize_t i;
465-
#endif
466468

467-
ASSERT(0 <= mp->ma_used && mp->ma_used <= usable);
468-
ASSERT(IS_POWER_OF_2(keys->dk_size));
469-
ASSERT(0 <= keys->dk_usable
470-
&& keys->dk_usable <= usable);
471-
ASSERT(0 <= keys->dk_nentries
472-
&& keys->dk_nentries <= usable);
473-
ASSERT(keys->dk_usable + keys->dk_nentries <= usable);
469+
_PyObject_ASSERT(op, 0 <= mp->ma_used && mp->ma_used <= usable);
470+
_PyObject_ASSERT(op, IS_POWER_OF_2(keys->dk_size));
471+
_PyObject_ASSERT(op, 0 <= keys->dk_usable && keys->dk_usable <= usable);
472+
_PyObject_ASSERT(op, 0 <= keys->dk_nentries && keys->dk_nentries <= usable);
473+
_PyObject_ASSERT(op, keys->dk_usable + keys->dk_nentries <= usable);
474474

475475
if (!splitted) {
476476
/* combined table */
477-
ASSERT(keys->dk_refcnt == 1);
477+
_PyObject_ASSERT(op, keys->dk_refcnt == 1);
478478
}
479479

480-
#ifdef DEBUG_PYDICT
481-
for (i=0; i < keys->dk_size; i++) {
482-
Py_ssize_t ix = dictkeys_get_index(keys, i);
483-
ASSERT(DKIX_DUMMY <= ix && ix <= usable);
484-
}
480+
if (check_content) {
481+
PyDictKeyEntry *entries = DK_ENTRIES(keys);
482+
Py_ssize_t i;
483+
484+
for (i=0; i < keys->dk_size; i++) {
485+
Py_ssize_t ix = dictkeys_get_index(keys, i);
486+
_PyObject_ASSERT(op, DKIX_DUMMY <= ix && ix <= usable);
487+
}
485488

486-
for (i=0; i < usable; i++) {
487-
PyDictKeyEntry *entry = &entries[i];
488-
PyObject *key = entry->me_key;
489+
for (i=0; i < usable; i++) {
490+
PyDictKeyEntry *entry = &entries[i];
491+
PyObject *key = entry->me_key;
489492

490-
if (key != NULL) {
491-
if (PyUnicode_CheckExact(key)) {
492-
Py_hash_t hash = ((PyASCIIObject *)key)->hash;
493-
ASSERT(hash != -1);
494-
ASSERT(entry->me_hash == hash);
495-
}
496-
else {
497-
/* test_dict fails if PyObject_Hash() is called again */
498-
ASSERT(entry->me_hash != -1);
493+
if (key != NULL) {
494+
if (PyUnicode_CheckExact(key)) {
495+
Py_hash_t hash = ((PyASCIIObject *)key)->hash;
496+
_PyObject_ASSERT(op, hash != -1);
497+
_PyObject_ASSERT(op, entry->me_hash == hash);
498+
}
499+
else {
500+
/* test_dict fails if PyObject_Hash() is called again */
501+
_PyObject_ASSERT(op, entry->me_hash != -1);
502+
}
503+
if (!splitted) {
504+
_PyObject_ASSERT(op, entry->me_value != NULL);
505+
}
499506
}
500-
if (!splitted) {
501-
ASSERT(entry->me_value != NULL);
507+
508+
if (splitted) {
509+
_PyObject_ASSERT(op, entry->me_value == NULL);
502510
}
503511
}
504512

505513
if (splitted) {
506-
ASSERT(entry->me_value == NULL);
514+
/* splitted table */
515+
for (i=0; i < mp->ma_used; i++) {
516+
_PyObject_ASSERT(op, mp->ma_values[i] != NULL);
517+
}
507518
}
508519
}
509520

510-
if (splitted) {
511-
/* splitted table */
512-
for (i=0; i < mp->ma_used; i++) {
513-
ASSERT(mp->ma_values[i] != NULL);
514-
}
515-
}
516-
#endif
517-
518521
return 1;
519-
520-
#undef ASSERT
521522
}
522-
#endif
523523

524524

525525
static PyDictKeysObject *new_keys_object(Py_ssize_t size)
@@ -614,7 +614,7 @@ new_dict(PyDictKeysObject *keys, PyObject **values)
614614
mp->ma_values = values;
615615
mp->ma_used = 0;
616616
mp->ma_version_tag = DICT_NEXT_VERSION();
617-
assert(_PyDict_CheckConsistency(mp));
617+
ASSERT_CONSISTENT(mp);
618618
return (PyObject *)mp;
619619
}
620620

@@ -675,7 +675,7 @@ clone_combined_dict(PyDictObject *orig)
675675
return NULL;
676676
}
677677
new->ma_used = orig->ma_used;
678-
assert(_PyDict_CheckConsistency(new));
678+
ASSERT_CONSISTENT(new);
679679
if (_PyObject_GC_IS_TRACKED(orig)) {
680680
/* Maintain tracking. */
681681
_PyObject_GC_TRACK(new);
@@ -1075,7 +1075,7 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
10751075
mp->ma_keys->dk_usable--;
10761076
mp->ma_keys->dk_nentries++;
10771077
assert(mp->ma_keys->dk_usable >= 0);
1078-
assert(_PyDict_CheckConsistency(mp));
1078+
ASSERT_CONSISTENT(mp);
10791079
return 0;
10801080
}
10811081

@@ -1094,7 +1094,7 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
10941094

10951095
mp->ma_version_tag = DICT_NEXT_VERSION();
10961096
Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */
1097-
assert(_PyDict_CheckConsistency(mp));
1097+
ASSERT_CONSISTENT(mp);
10981098
Py_DECREF(key);
10991099
return 0;
11001100

@@ -1582,7 +1582,7 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
15821582
Py_DECREF(old_key);
15831583
Py_DECREF(old_value);
15841584

1585-
assert(_PyDict_CheckConsistency(mp));
1585+
ASSERT_CONSISTENT(mp);
15861586
return 0;
15871587
}
15881588

@@ -1722,7 +1722,7 @@ PyDict_Clear(PyObject *op)
17221722
assert(oldkeys->dk_refcnt == 1);
17231723
dictkeys_decref(oldkeys);
17241724
}
1725-
assert(_PyDict_CheckConsistency(mp));
1725+
ASSERT_CONSISTENT(mp);
17261726
}
17271727

17281728
/* Internal version of PyDict_Next that returns a hash value in addition
@@ -1852,7 +1852,7 @@ _PyDict_Pop_KnownHash(PyObject *dict, PyObject *key, Py_hash_t hash, PyObject *d
18521852
ep->me_value = NULL;
18531853
Py_DECREF(old_key);
18541854

1855-
assert(_PyDict_CheckConsistency(mp));
1855+
ASSERT_CONSISTENT(mp);
18561856
return old_value;
18571857
}
18581858

@@ -2434,7 +2434,7 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
24342434
}
24352435

24362436
i = 0;
2437-
assert(_PyDict_CheckConsistency((PyDictObject *)d));
2437+
ASSERT_CONSISTENT(d);
24382438
goto Return;
24392439
Fail:
24402440
Py_XDECREF(item);
@@ -2586,7 +2586,7 @@ dict_merge(PyObject *a, PyObject *b, int override)
25862586
/* Iterator completed, via error */
25872587
return -1;
25882588
}
2589-
assert(_PyDict_CheckConsistency((PyDictObject *)a));
2589+
ASSERT_CONSISTENT(a);
25902590
return 0;
25912591
}
25922592

@@ -2950,7 +2950,7 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
29502950
mp->ma_version_tag = DICT_NEXT_VERSION();
29512951
}
29522952

2953-
assert(_PyDict_CheckConsistency(mp));
2953+
ASSERT_CONSISTENT(mp);
29542954
return value;
29552955
}
29562956

@@ -3069,7 +3069,7 @@ dict_popitem_impl(PyDictObject *self)
30693069
self->ma_keys->dk_nentries = i;
30703070
self->ma_used--;
30713071
self->ma_version_tag = DICT_NEXT_VERSION();
3072-
assert(_PyDict_CheckConsistency(self));
3072+
ASSERT_CONSISTENT(self);
30733073
return res;
30743074
}
30753075

@@ -3275,7 +3275,7 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
32753275
Py_DECREF(self);
32763276
return NULL;
32773277
}
3278-
assert(_PyDict_CheckConsistency(d));
3278+
ASSERT_CONSISTENT(d);
32793279
return self;
32803280
}
32813281

Objects/object.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* Generic object operations; and implementation of None */
33

44
#include "Python.h"
5+
#include "pycore_object.h"
56
#include "pycore_pystate.h"
67
#include "pycore_context.h"
78
#include "frameobject.h"
@@ -19,6 +20,28 @@ _Py_IDENTIFIER(__bytes__);
1920
_Py_IDENTIFIER(__dir__);
2021
_Py_IDENTIFIER(__isabstractmethod__);
2122

23+
24+
int
25+
_PyObject_CheckConsistency(PyObject *op, int check_content)
26+
{
27+
_PyObject_ASSERT(op, op != NULL);
28+
_PyObject_ASSERT(op, !_PyObject_IsFreed(op));
29+
_PyObject_ASSERT(op, Py_REFCNT(op) >= 1);
30+
31+
PyTypeObject *type = op->ob_type;
32+
_PyObject_ASSERT(op, type != NULL);
33+
_PyType_CheckConsistency(type);
34+
35+
if (PyUnicode_Check(op)) {
36+
_PyUnicode_CheckConsistency(op, check_content);
37+
}
38+
else if (PyDict_Check(op)) {
39+
_PyDict_CheckConsistency(op, check_content);
40+
}
41+
return 1;
42+
}
43+
44+
2245
#ifdef Py_REF_DEBUG
2346
Py_ssize_t _Py_RefTotal;
2447

@@ -2136,7 +2159,13 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
21362159
else if (_PyObject_IsFreed(obj)) {
21372160
/* It seems like the object memory has been freed:
21382161
don't access it to prevent a segmentation fault. */
2139-
fprintf(stderr, "<Freed object>\n");
2162+
fprintf(stderr, "<object: freed>\n");
2163+
}
2164+
else if (Py_TYPE(obj) == NULL) {
2165+
fprintf(stderr, "<object: ob_type=NULL>\n");
2166+
}
2167+
else if (_PyObject_IsFreed((PyObject *)Py_TYPE(obj))) {
2168+
fprintf(stderr, "<object: freed type %p>\n", Py_TYPE(obj));
21402169
}
21412170
else {
21422171
/* Diplay the traceback where the object has been allocated.

Objects/typeobject.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,7 @@ skip_signature(const char *doc)
131131
return NULL;
132132
}
133133

134-
#ifndef NDEBUG
135-
static int
134+
int
136135
_PyType_CheckConsistency(PyTypeObject *type)
137136
{
138137
#define ASSERT(expr) _PyObject_ASSERT((PyObject *)type, (expr))
@@ -142,14 +141,16 @@ _PyType_CheckConsistency(PyTypeObject *type)
142141
return 1;
143142
}
144143

144+
ASSERT(!_PyObject_IsFreed((PyObject *)type));
145+
ASSERT(Py_REFCNT(type) >= 1);
146+
ASSERT(PyType_Check(type));
147+
145148
ASSERT(!(type->tp_flags & Py_TPFLAGS_READYING));
146-
ASSERT(type->tp_mro != NULL && PyTuple_Check(type->tp_mro));
147149
ASSERT(type->tp_dict != NULL);
148-
return 1;
149150

151+
return 1;
150152
#undef ASSERT
151153
}
152-
#endif
153154

154155
static const char *
155156
_PyType_DocWithoutSignature(const char *name, const char *internal_doc)

0 commit comments

Comments
 (0)