python / python/cpython

subtype_dealloc for heap types is a footgun

未关闭
#138,870 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

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. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

复现程序使用 PyType_FromModuleAndSpec、PyType_GetSlot、Py_tp_dealloc 和 subtype_dealloc;首先跟踪 CPython 类型实现中的这条释放路径。使用所提供的 Base/Derived 扩展作为回归案例,为堆类型释放器确立并测试一种安全行为;该 issue 未指定测试文件,也未规定解决方案。

由索引模型根据 Issue 内容生成。

评估

技术栈
c, python
领域
backend-api-design
Issue 类型
缺陷
难度
5/5
预计耗时
一周以上
活跃度
停滞
描述清晰度
需要澄清
新手友好度
30/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。