8000 bpo-36389: _PyObject_CheckConsistency() now also works in release mode by vstinner · Pull Request #16612 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-36389: _PyObject_CheckConsistency() now also works in release mode #16612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 7, 2019
Merged
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
8000
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Include/cpython/unicodeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ typedef struct {
} data; /* Canonical, smallest-form Unicode buffer */
} PyUnicodeObject;

PyAPI_FUNC(int) _PyUnicode_CheckConsistency(
PyObject *op,
int check_content);

/* Fast access macros */
#define PyUnicode_WSTR_LENGTH(op) \
(PyUnicode_IS_COMPACT_ASCII(op) ? \
Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ extern "C" {
#include "pycore_pystate.h" /* _PyRuntime.gc */

PyAPI_FUNC(int) _PyType_CheckConsistency(PyTypeObject *type);
PyAPI_FUNC(int) _PyUnicode_CheckConsistency(PyObject *op, int check_content);
PyAPI_FUNC(int) _PyDict_CheckConsistency(PyObject *mp, int check_content);

/* Tell the GC to track this object.
Expand Down
11 changes: 7 additions & 4 deletions Include/internal/pycore_pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ PyAPI_FUNC(int) _PyMem_SetDefaultAllocator(
PyMemAllocatorEx *old_alloc);

/* Heuristic checking if a pointer value is newly allocated
(uninitialized) or newly freed. The pointer is not dereferenced, only the
pointer value is checked.
(uninitialized), newly freed or NULL (is equal to zero).

The pointer is not dereferenced, only the pointer value is checked.

The heuristic relies on the debug hooks on Python memory allocators which
fills newly allocated memory with CLEANBYTE (0xCD) and newly freed memory
Expand All @@ -166,11 +167,13 @@ static inline int _PyMem_IsPtrFreed(void *ptr)
{
uintptr_t value = (uintptr_t)ptr;
#if SIZEOF_VOID_P == 8
return (value == (uintptr_t)0xCDCDCDCDCDCDCDCD
return (value == 0
|| value == (uintptr_t)0xCDCDCDCDCDCDCDCD
|| value == (uintptr_t)0xDDDDDDDDDDDDDDDD
|| value == (uintptr_t)0xFDFDFDFDFDFDFDFD);
#elif SIZEOF_VOID_P == 4
return (value == (uintptr_t)0xCDCDCDCD
return (value == 0
|| value == (uintptr_t)0xCDCDCDCD
|| value == (uintptr_t)0xDDDDDDDD
|| value == (uintptr_t)0xFDFDFDFD);
#else
Expand Down
10 changes: 0 additions & 10 deletions Include/unicodeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -1032,16 +1032,6 @@ PyAPI_FUNC(int) PyUnicode_IsIdentifier(PyObject *s);

/* === Characters Type APIs =============================================== */

#if defined(Py_DEBUG) && !defined(Py_LIMITED_API)
PyAPI_FUNC(int) _PyUnicode_CheckConsistency(
PyObject *op,
int check_content);
#elif !defined(NDEBUG)
/* For asserts that call _PyUnicode_CheckConsistency(), which would
* otherwise be a problem when building with asserts but without Py_DEBUG. */
#define _PyUnicode_CheckConsistency(op, check_content) PyUnicode_Check(op)
#endif

#ifndef Py_LIMITED_API
# define Py_CPYTHON_UNICODEOBJECT_H
# include "cpython/unicodeobject.h"
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@ def check_pyobject_is_freed(self, func_name):
''')
assert_python_ok('-c', code, PYTHONMALLOC=self.PYTHONMALLOC)

def test_pyobject_null_is_freed(self):
self.check_pyobject_is_freed('check_pyobject_null_is_freed')

def test_pyobject_uninitialized_is_freed(self):
self.check_pyobject_is_freed('check_pyobject_uninitialized_is_freed')

Expand Down
15 changes: 9 additions & 6 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,16 +985,19 @@ def test_refcount_errors(self):
br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.')
self.assertRegex(stderr,
br'refcount is too small')
# "address : 0x7fb5062efc18"
# "address : 7FB5062EFC18"
address_regex = br'[0-9a-fA-Fx]+'
self.assertRegex(stderr,
br'object : \[1, 2, 3\]')
br'object address : ' + address_regex)
self.assertRegex(stderr,
br'type : list')
br'object refcount : 1')
self.assertRegex(stderr,
br'refcount: 1')
# "address : 0x7fb5062efc18"
# "address : 7FB5062EFC18"
br'object type : ' + address_regex)
self.assertRegex(stderr,
br'object type name: list')
self.assertRegex(stderr,
br'address : [0-9a-fA-Fx]+')
br'object repr : \[1, 2, 3\]')


class GCTogglingTests(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
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.
9 changes: 9 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4611,6 +4611,14 @@ test_pyobject_is_freed(const char *test_name, PyObject *op)
}


static PyObject*
check_pyobject_null_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
{
PyObject *op = NULL;
return test_pyobject_is_freed("check_pyobject_null_is_freed", op);
}


static PyObject*
check_pyobject_uninitialized_is_freed(PyObject *self, PyObject *Py_UNUSED(args))
{
Expand Down Expand Up @@ -5475,6 +5483,7 @@ static PyMethodDef TestMethods[] = {
{"pymem_api_misuse", pymem_api_misuse, METH_NOARGS},
{"pymem_malloc_without_gil", pymem_malloc_without_gil, METH_NOARGS},
{"pymem_getallocatorsname", test_pymem_getallocatorsname, METH_NOARGS},
{"check_pyobject_null_is_freed", check_pyobject_null_is_freed, METH_NOARGS},
{"check_pyobject_uninitialized_is_freed", check_pyobject_uninitialized_is_freed, METH_NOARGS},
{"check_pyobject_forbidden_bytes_is_freed", check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
Expand Down
1 change: 0 additions & 1 deletion Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ update_refs(PyGC_Head *containers)
static int
visit_decref(PyObject *op, void *data)
{
assert(op != NULL);
_PyObject_ASSERT(op, !_PyObject_IsFreed(op));

if (PyObject_IS_GC(op)) {
Expand Down
15 changes: 8 additions & 7 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3206,10 +3206,10 @@ _PyBytesWriter_GetSize(_PyBytesWriter *writer, char *str)
return str - start;
}

Py_LOCAL_INLINE(void)
#ifndef NDEBUG
Py_LOCAL_INLINE(int)
_PyBytesWriter_CheckConsistency(_PyBytesWriter *writer, char *str)
{
#ifdef Py_DEBUG
char *start, *end;

if (writer->use_small_buffer) {
Expand Down Expand Up @@ -3239,15 +3239,16 @@ _PyBytesWriter_CheckConsistency(_PyBytesWriter *writer, char *str)
end = start + writer->allocated;
assert(str != NULL);
assert(start <= str && str <= end);
#endif
return 1;
}
#endif

void*
_PyBytesWriter_Resize(_PyBytesWriter *writer, void *str, Py_ssize_t size)
{
Py_ssize_t allocated, pos;

_PyBytesWriter_CheckConsistency(writer, str);
assert(_PyBytesWriter_CheckConsistency(writer, str));
assert(writer->allocated < size);

allocated = size;
Expand Down Expand Up @@ -3303,7 +3304,7 @@ _PyBytesWriter_Resize(_PyBytesWriter *writer, void *str, Py_ssize_t size)
writer->allocated = allocated;

str = _PyBytesWriter_AsString(writer) + pos;
_PyBytesWriter_CheckConsistency(writer, str);
assert(_PyBytesWriter_CheckConsistency(writer, str));
return str;

error:
Expand All @@ -3316,7 +3317,7 @@ _PyBytesWriter_Prepare(_PyBytesWriter *writer, void *str, Py_ssize_t size)
{
Py_ssize_t new_min_size;

_PyBytesWriter_CheckConsistency(writer, str);
assert(_PyBytesWriter_CheckConsistency(writer, str));
assert(size >= 0);

if (size == 0) {
Expand Down Expand Up @@ -3377,7 +3378,7 @@ _PyBytesWriter_Finish(_PyBytesWriter *writer, void *str)
Py_ssize_t size;
PyObject *result;

_PyBytesWriter_CheckConsistency(writer, str);
assert(_PyBytesWriter_CheckConsistency(writer, str));

size = _PyBytesWriter_GetSize(writer, str);
if (size == 0 && !writer->use_bytearray) {
Expand Down
36 changes: 20 additions & 16 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,23 +459,26 @@ static PyObject *empty_values[1] = { NULL };
int
_PyDict_CheckConsistency(PyObject *op, int check_content)
{
#ifndef NDEBUG
_PyObject_ASSERT(op, PyDict_Check(op));
#define CHECK(expr) \
do { if (!(expr)) { _PyObject_ASSERT_FAILED_MSG(op, Py_STRINGIFY(expr)); } } while (0)

assert(op != NULL);
CHECK(PyDict_Check(op));
PyDictObject *mp = (PyDictObject *)op;

PyDictKeysObject *keys = mp->ma_keys;
int splitted = _PyDict_HasSplitTable(mp);
Py_ssize_t usable = USABLE_FRACTION(keys->dk_size);

_PyObject_ASSERT(op, 0 <= mp->ma_used && mp->ma_used <= usable);
_PyObject_ASSERT(op, IS_POWER_OF_2(keys->dk_size));
_PyObject_ASSERT(op, 0 <= keys->dk_usable && keys->dk_usable <= usable);
_PyObject_ASSERT(op, 0 <= keys->dk_nentries && keys->dk_nentries <= usable);
_PyObject_ASSERT(op, keys->dk_usable + keys->dk_nentries <= usable);
CHECK(0 <= mp->ma_used && mp->ma_used <= usable);
CHECK(IS_POWER_OF_2(keys->dk_size));
CHECK(0 <= keys->dk_usable && keys->dk_usable <= usable);
CHECK(0 <= keys->dk_nentries && keys->dk_nentries <= usable);
CHECK(keys->dk_usable + keys->dk_nentries <= usable);

if (!splitted) {
/* combined table */
_PyObject_ASSERT(op, keys->dk_refcnt == 1);
CHECK(keys->dk_refcnt == 1);
}

if (check_content) {
Expand All @@ -484,7 +487,7 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)

for (i=0; i < keys->dk_size; i++) {
Py_ssize_t ix = dictkeys_get_index(keys, i);
_PyObject_ASSERT(op, DKIX_DUMMY <= ix && ix <= usable);
CHECK(DKIX_DUMMY <= ix && ix <= usable);
}

for (i=0; i < usable; i++) {
Expand All @@ -494,32 +497,33 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
if (key != NULL) {
if (PyUnicode_CheckExact(key)) {
Py_hash_t hash = ((PyASCIIObject *)key)->hash;
_PyObject_ASSERT(op, hash != -1);
_PyObject_ASSERT(op, entry->me_hash == hash);
CHECK(hash != -1);
CHECK(entry->me_hash == hash);
}
else {
/* test_dict fails if PyObject_Hash() is called again */
_PyObject_ASSERT(op, entry->me_hash != -1);
CHECK(entry->me_hash != -1);
}
if (!splitted) {
_PyObject_ASSERT(op, entry->me_value != NULL);
CHECK(entry->me_value != NULL);
}
}

if (splitted) {
_PyObject_ASSERT(op, entry->me_value == NULL);
CHECK(entry->me_value == NULL);
}
}

if (splitted) {
/* splitted table */
for (i=0; i < mp->ma_used; i++) {
_PyObject_ASSERT(op, mp->ma_values[i] != NULL);
CHECK(mp->ma_values[i] != NULL);
}
}
}
#endif
return 1;

#undef CHECK
}


Expand Down
Loading
0