-
- Notifications
You must be signed in to change notification settings - Fork 33k
Description
The _PyObject_SIZE() and _PyObject_VAR_SIZE() macros should be converted to functions: see PEP 670 for the rationale.
My problem is that I don't know if the return type should be signed (Py_ssize_t) or unsigned (size_t).
CPython usage of _PyObject_SIZE():
- Signed: 18. Implementation of
__sizeof__()
methods. - Unsigned: 0
- Implicit cast to unsigned: 2. Calls
PyObject_Malloc(_PyObject_SIZE(tp))
andgc_alloc(_PyObject_SIZE(tp), presize)
where the first argument type issize_t
.
CPython usage of _PyObject_VAR_SIZE():
- Signed: 5
- Unsigned: 1
- Implicit cast to unsigned: 1. Call
_PyDebugAllocatorStats(..., _PyObject_VAR_SIZE(&PyTuple_Type, len))
where the argument type issize_t
.
To get a container length, the C API uses signed type (Py_ssize_t
): PyList_Size(), PyDict_Size(), Py_SIZE(), etc.
To allocate memory, the C API prefers unsigned type (size_t
): PyMem_Malloc(), PyObject_Realloc(), etc.
Python allocator functions reject size greater than PY_SSIZE_T_MAX
:
void * PyMem_RawMalloc(size_t size) { /* * Limit ourselves to PY_SSIZE_T_MAX bytes to prevent security holes. * Most python internals blindly use a signed Py_ssize_t to track * things without checking for overflows or negatives. * As size_t is unsigned, checking for size < 0 is not required. */ if (size > (size_t)PY_SSIZE_T_MAX) return NULL; return _PyMem_Raw.malloc(_PyMem_Raw.ctx, size); }
Some "sizeof" functions freely mix signed and unsigned types. Example:
static PyObject * deque_sizeof(dequeobject *deque, void *unused) { Py_ssize_t res; Py_ssize_t blocks; res = _PyObject_SIZE(Py_TYPE(deque)); blocks = (size_t)(deque->leftindex + Py_SIZE(deque) + BLOCKLEN - 1) / BLOCKLEN; assert(deque->leftindex + Py_SIZE(deque) - 1 == (blocks - 1) * BLOCKLEN + deque->rightindex); res += blocks * sizeof(block); return PyLong_FromSsize_t(res); }
blocks
and sizeof(block)
are unsigned, but res
is signed.
Another problem is that _PyObject_VAR_SIZE() has an undefined behavior on integer overflow. Maybe it should return SIZE_MAX
on oveflow, to make sure that Python allocator function fail (return NULL
)?
Linked PRs
- gh-99845: Use size_t type in __sizeof__() methods #99846
- gh-99845: Clean up _PyObject_VAR_SIZE() usage #99847
- gh-99845: Change _PyDict_KeysSize() return type to size_t #99848
- gh-99845: PEP 670: Convert PyObject macros to functions #99850
- gh-99845: _PySys_GetSizeOf() uses size_t #99903
- [3.11] gh-99845: _PyObject_DictPointer(): fix dictoffset cast #99922
- [3.10] gh-99845: _PyObject_DictPointer(): fix dictoffset cast (GH-99922) #99924