python / python/cpython

subtype_dealloc for heap types is a footgun

オープン
#138,870 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

interpreter-core topic-C-API type-bug
主要言語
Python
スター
77.2k
フォーク
35.9k
PR マージ指標
PR 指標を取得中

説明

Bug report

Bug description:
#include <Python.h>

static PyType_Slot base_slots[] = {
    {0, 0}
};

static PyType_Spec base_spec = {
    .name = "Base",
    .basicsize = 0,
    .itemsize = 0,
    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .slots = base_slots,
};

static void derived_dealloc(PyObject *o) {
    // call base class dealloc
    destructor base_slot = PyType_GetSlot(Py_TYPE(o), Py_tp_dealloc);
    base_slot(o);
}

static PyType_Slot derived_slots[] = {
    {Py_tp_dealloc, derived_dealloc},
    {0, 0}
};

static PyType_Spec derived_spec = {
    .name = "Derived",
    .basicsize = 0,
    .itemsize = 0,
    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .slots = derived_slots,
};

static int
deallocdemo_module_exec(PyObject *m)
{
    PyObject *base_type = PyType_FromModuleAndSpec(
        m,
        &base_spec,
        NULL
    );
    if (!base_type) return -1;
    if (PyModule_AddObjectRef(m, "Base", base_type) < 0) {
        Py_DECREF(base_type);
        return -1;
    }

    PyObject *derived_type = PyType_FromModuleAndSpec(
        m,
        &derived_spec,
        base_type
    );
    Py_DECREF(base_type);
    if (!derived_type) return -1;
    int add_object_res = PyModule_AddObjectRef(m, "Derived", derived_type);
    Py_DECREF(derived_type);

    return add_object_res;
}

static PyModuleDef_Slot deallocdemo_module_slots[] = {
    {Py_mod_exec, deallocdemo_module_exec},
    {0, NULL}
};

static struct PyModuleDef deallocdemo_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "deallocdemo",
    .m_size = 0,
    .m_slots = deallocdemo_module_slots,
};

PyMODINIT_FUNC
PyInit_deallocdemo(void)
{
    return PyModuleDef_Init(&deallocdemo_module);
}

Essentially, subtype_dealloc prevents any derived type from implementing its own dealloc function. In the example above, trying to destroy a Derived object will cause an infinite loop because subtype_dealloc will call derived_dealloc (which calls subtype_dealloc).

This is something that works better for static-types, where the default was to inherit the tp_dealloc of the base class. I appreciate heap types are a little more complex in the inheritance they allow though.


I suspect this is now too late to change easily, and I can't personally think of any obvious solutions to it. It's obvious possible to work around (by creating a custom tp_dealloc at all levels) so the issue is more "it's a footgun" than "it's unworkable". But it seems worth raising the issue in case it can be improved somehow.

CPython versions tested on:

3.13

Operating systems tested on:

Linux

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

この再現プログラムは PyType_FromModuleAndSpec、PyType_GetSlot、Py_tp_dealloc、subtype_dealloc を使用しているため、まず CPython の型実装におけるこのデアロケーションパスを追跡します。提供された Base/Derived 拡張をリグレッションケースとして使用し、ヒープ型のデアロケータについて安全な動作を確立してテストします。この issue ではテストファイルは指定されておらず、解決策も規定されていません。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
c, python
領域
backend-api-design
issue の種類
バグ
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
30/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。