Skip to content

PEP 670: Convert _PyObject_SIZE() and _PyObject_VAR_SIZE() macros to functions #99845

Description

@vstinner

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)) and gc_alloc(_PyObject_SIZE(tp), presize) where the first argument type is size_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 is size_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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions