Skip to content
Open
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
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions Include/cpython/objimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,40 @@ static inline size_t _PyObject_SIZE(PyTypeObject *type) {

Note that there's no memory wastage in doing this, as malloc has to
return (at worst) pointer-aligned memory anyway.

Return SIZE_MAX on integer overflow (if the result would be greater than
PY_SSIZE_T_MAX).
*/
#if ((SIZEOF_VOID_P - 1) & SIZEOF_VOID_P) != 0
# error "_PyObject_VAR_SIZE requires SIZEOF_VOID_P be a power of 2"
#endif

static inline size_t _PyObject_VAR_SIZE(PyTypeObject *type, Py_ssize_t nitems) {
size_t size = _Py_STATIC_CAST(size_t, type->tp_basicsize);
size += _Py_STATIC_CAST(size_t, nitems) * _Py_STATIC_CAST(size_t, type->tp_itemsize);
return _Py_SIZE_ROUND_UP(size, SIZEOF_VOID_P);
static inline size_t
_PyObject_VAR_SIZE(PyTypeObject *type, Py_ssize_t nitems_signed)
{
size_t basicsize;
size_t size;

assert(type->tp_itemsize >= 0);
basicsize = _Py_STATIC_CAST(size_t, type->tp_basicsize);

if (type->tp_itemsize != 0) {
size_t nitems = _Py_STATIC_CAST(size_t, nitems_signed);
size_t itemsize = _Py_STATIC_CAST(size_t, type->tp_itemsize);
if (nitems > ((size_t)PY_SSIZE_T_MAX - basicsize) / itemsize) {
return SIZE_MAX;
}
size = basicsize + nitems * itemsize;
}
else {
size = basicsize;
}

size = _Py_SIZE_ROUND_UP(size, SIZEOF_VOID_P);
if (size > (size_t)PY_SSIZE_T_MAX) {
return SIZE_MAX;
}
return size;
}


Expand Down
2 changes: 0 additions & 2 deletions Include/objimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ PyAPI_FUNC(PyVarObject *) _PyObject_GC_Resize(PyVarObject *, Py_ssize_t);
#define PyObject_GC_Resize(type, op, n) \
( (type *) _PyObject_GC_Resize(_PyVarObject_CAST(op), (n)) )



PyAPI_FUNC(PyObject *) _PyObject_GC_New(PyTypeObject *);
PyAPI_FUNC(PyVarObject *) _PyObject_GC_NewVar(PyTypeObject *, Py_ssize_t);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:c:func:`!_PyObject_VAR_SIZE` now returns ``SIZE_MAX`` on integer overflow (if
the result would be greater than ``PY_SSIZE_T_MAX``). Patch by Victor Stinner.
18 changes: 3 additions & 15 deletions Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ tuple_alloc(Py_ssize_t size)
return op;
}
}
/* Check for overflow */
if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) -
sizeof(PyObject *))) / sizeof(PyObject *)) {
return (PyTupleObject *)PyErr_NoMemory();
}
PyTupleObject *result = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size);
if (result != NULL) {
_PyTuple_RESET_HASH_CACHE(result);
Expand Down Expand Up @@ -1079,14 +1074,6 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
*pv = PyTuple_New(newsize);
return *pv == NULL ? -1 : 0;
}
/* Check for overflow */
if ((size_t)newsize > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) -
sizeof(PyObject *))) / sizeof(PyObject *)) {
*pv = NULL;
Py_DECREF(v);
PyErr_NoMemory();
return -1;
}

if (_PyObject_GC_IS_TRACKED(v)) {
_PyObject_GC_UNTRACK(v);
Expand Down Expand Up @@ -1315,12 +1302,13 @@ maybe_freelist_push(PyTupleObject *op)
void
_PyTuple_DebugMallocStats(FILE *out)
{
PyTypeObject *type = &PyTuple_Type;
for (int i = 0; i < PyTuple_MAXSAVESIZE; i++) {
int len = i + 1;
char buf[128];
PyOS_snprintf(buf, sizeof(buf),
"free %d-sized PyTupleObject", len);
_PyDebugAllocatorStats(out, buf, _Py_FREELIST_SIZE(tuples[i]),
_PyType_PreHeaderSize(&PyTuple_Type) + _PyObject_VAR_SIZE(&PyTuple_Type, len));
size_t size = _PyType_PreHeaderSize(type) + _PyObject_VAR_SIZE(type, len);
_PyDebugAllocatorStats(out, buf, _Py_FREELIST_SIZE(tuples[i]), size);
}
}
28 changes: 23 additions & 5 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2537,25 +2537,43 @@ _PyType_NewManagedObject(PyTypeObject *type)
PyObject *
_PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems)
{
PyObject *obj;
#ifdef Py_DEBUG
if (nitems < 0) {
PyErr_BadInternalCall();
return NULL;
}
#endif

/* The +1 on nitems is needed for most types but not all. We could save a
* bit of space by allocating one less item in certain cases, depending on
* the type. However, given the extra complexity (e.g. an additional type
* flag to indicate when that is safe) it does not seem worth the memory
* savings. An example type that doesn't need the +1 is a subclass of
* tuple. See GH-100659 and GH-81381. */
if (nitems > PY_SSIZE_T_MAX - 1) {
return PyErr_NoMemory();
}
size_t size = _PyObject_VAR_SIZE(type, nitems+1);

const size_t presize = _PyType_PreHeaderSize(type);
if (type->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
assert(type->tp_itemsize == 0);
size += _PyInlineValuesSize(type);
size_t inline_values_size = _PyInlineValuesSize(type);
if (size > (size_t)PY_SSIZE_T_MAX - inline_values_size) {
return PyErr_NoMemory();
}
size += inline_values_size;
}

const size_t presize = _PyType_PreHeaderSize(type);
if (size > (size_t)PY_SSIZE_T_MAX - presize) {
return PyErr_NoMemory();
}

char *alloc = _PyObject_MallocWithType(type, size + presize);
if (alloc == NULL) {
if (alloc == NULL) {
return PyErr_NoMemory();
}
obj = (PyObject *)(alloc + presize);
PyObject *obj = (PyObject *)(alloc + presize);
if (presize) {
((PyObject **)alloc)[0] = NULL;
((PyObject **)alloc)[1] = NULL;
Expand Down
9 changes: 8 additions & 1 deletion Python/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2067,7 +2067,7 @@ static PyObject *
gc_alloc(PyTypeObject *tp, size_t basicsize, size_t presize)
{
PyThreadState *tstate = _PyThreadState_GET();
if (basicsize > PY_SSIZE_T_MAX - presize) {
if (basicsize > (size_t)PY_SSIZE_T_MAX - presize) {
return _PyErr_NoMemory(tstate);
}
size_t size = presize + basicsize;
Expand Down Expand Up @@ -2138,6 +2138,13 @@ PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *tp, size_t extra_size)
PyVarObject *
_PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems)
{
#ifdef Py_DEBUG
if (nitems < 0) {
PyErr_BadInternalCall();
return NULL;
}
#endif

const size_t basicsize = _PyObject_VAR_SIZE(Py_TYPE(op), nitems);
const size_t presize = _PyType_PreHeaderSize(Py_TYPE(op));
_PyObject_ASSERT((PyObject *)op, !_PyObject_GC_IS_TRACKED(op));
Expand Down
9 changes: 8 additions & 1 deletion Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -2775,7 +2775,7 @@ static PyObject *
gc_alloc(PyTypeObject *tp, size_t basicsize, size_t presize)
{
PyThreadState *tstate = _PyThreadState_GET();
if (basicsize > PY_SSIZE_T_MAX - presize) {
if (basicsize > (size_t)PY_SSIZE_T_MAX - presize) {
return _PyErr_NoMemory(tstate);
}
size_t size = presize + basicsize;
Expand Down Expand Up @@ -2847,6 +2847,13 @@ PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *tp, size_t extra_size)
PyVarObject *
_PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems)
{
#ifdef Py_DEBUG
if (nitems < 0) {
PyErr_BadInternalCall();
return NULL;
}
#endif

const size_t basicsize = _PyObject_VAR_SIZE(Py_TYPE(op), nitems);
const size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type);
_PyObject_ASSERT((PyObject *)op, !_PyObject_GC_IS_TRACKED(op));
Expand Down
Loading