C-API: выделение "PyTypeObject-extension"

Я нашел код в PyCXX, который может содержать ошибки.

Это действительно ошибка, и если да, то как правильно ее исправить?

Вот проблема:

struct PythonClassInstance
{
    PyObject_HEAD
    ExtObjBase* m_pycxx_object;
}
:
{
    :
    table->tp_new = extension_object_new; // PyTypeObject
    :
}
:
static PyObject* extension_object_new( 
                 PyTypeObject* subtype, PyObject* args, PyObject* kwds )
{
    PythonClassInstance* o = reinterpret_cast<PythonClassInstance *>
                                       ( subtype->tp_alloc(subtype,0) );
    if( ! o )
        return nullptr;

    o->m_pycxx_object = nullptr;

    PyObject* self = reinterpret_cast<PyObject* >( o );

    return self;
}

Теперь PyObject_HEAD расширяется до "PyObject ob_base;", поэтому ясно, что PythonClassInstance тривиально расширяет PyObject, чтобы содержать дополнительный указатель (который будет указывать на представление PyCXX для этого PyObject)

tp_alloc выделяет память для хранения PyObject

Затем код типизирует этот указатель на PythonClassInstance, утверждая, что ему необходимо дополнительно 4(или 8?) Байта, которые ему не принадлежат!

И тогда он устанавливает эту дополнительную память на 0.

Это выглядит очень опасно, и я удивлен, что ошибка осталась незамеченной. Риск состоит в том, что какой-то будущий объект будет помещен в это место (то есть предназначено для хранения ExtObjBase*).

Как это исправить?

PythonClassInstance foo{};

PyObject* tmp = subtype->tp_alloc(subtype,0);

// !!! memcpy sizeof(PyObject) bytes starting from location tmp into location (void*)foo

Но теперь я думаю, что, может быть, мне нужно выпустить tmp, и я не думаю, что мне следует играть с памятью напрямую, как это. Я чувствую, что это может поставить под угрозу встроенный механизм Python для управления памятью / сборкой мусора.

Другой вариант - возможно, я смогу убедить tp_alloc выделить 4 лишних байта (или 8 сейчас; достаточно для указателя), минуя 1 вместо 0.

Документация говорит, что этот второй параметр "Py_ssize_t nitems" и:

Если тип tp_itemsize не равен нулю, поле ob_size объекта должно быть инициализировано в nitems, а длина выделенного блока памяти должна быть tp_basicsize + nitemstp_itemsize, округленная до кратного sizeof (void); в противном случае nitems не используется, и длина блока должна быть tp_basicsize.

Похоже, я должен установить:

table->tp_itemsize = sizeof(void*);
:
PyObject* tmp = subtype->tp_alloc(subtype,1);

РЕДАКТИРОВАТЬ: только что попробовал это, и это вызывает сбой

Но затем в документации говорится:

Не используйте эту функцию для какой-либо другой инициализации экземпляра, даже для выделения дополнительной памяти; это должно быть сделано с помощью tp_new.

Теперь я не уверен, принадлежит ли этот код в tp_new или tp_init.

Связанные с:

Передача аргументов в tp_new и tp_init из подтипов в Python C API

Распределение объектов Python C-API

2 ответа

Решение

На самом деле это (незначительная / безвредная) ошибка в PyCXX

SO хотел бы преобразовать этот ответ в комментарий, который не имеет смысла, я не могу присвоить зеленую галочку завершения, поэтому я комментирую. Поэтому я должен бродить, чтобы квалифицировать его. blerh.

Код правильный.

Пока PyTypeObject для объекта расширения правильно инициализирован, он должен работать.

Базовый класс tp_alloc получает subtype поэтому он должен знать, сколько памяти выделить, проверив tp_basicsize член.

Это обычный шаблон Python C/API, как показано в руководстве.

Другие вопросы по тегам