python / python/cpython

ABCs with bogus __subclasses__ break instance/subclass checks for other ABCs

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

还没有人认领这个 Issue。

type-bug
主要语言
Python
星标
77.2k
派生
35.9k
PR 合并指标
PR 指标待抓取

描述

Bug report

Bug description:

Consider this evil class:

import abc

class EvilABC(abc.ABC):
    __subclasses__ = ...

Now all instance/subclass checks for other ABCs fail:

>>> isinstance(..., abc.ABC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    isinstance(..., abc.ABC)
    ~~~~~~~~~~^^^^^^^^^^^^^^
  File "<frozen abc>", line 119, in __instancecheck__
  File "<frozen abc>", line 123, in __subclasscheck__
  File "<frozen abc>", line 123, in __subclasscheck__
TypeError: attribute of type 'ellipsis' is not callable

This happens because the recursive call to __subclasses__ in https://github.com/python/cpython/blob/v3.13.0a5/Lib/_py_abc.py#L141 trusts that __subclasses__ will always be callable and return an iterable (or a list in the C implementation).


You might argue that the EvilABC class is evil and whoever does that is doomed to be punished. However, it can easily be done by a mistake. This class has a broken __subclasses__ without explicitly defining it:

class Crossbreed(abc.ABCMeta, os.PathLike):
    ...
>>> isinstance("/", os.PathLike)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    isinstance("/", os.PathLike)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "<frozen abc>", line 119, in __instancecheck__
  File "<frozen abc>", line 123, in __subclasscheck__
  File "<frozen abc>", line 123, in __subclasscheck__
TypeError: unbound method type.__subclasses__() needs an argument

It is very surprising that by defining a class we can break checks for other classes and hence third party code, even from the standard library:

$ python3 -c 'import abc, os, subprocess
> class A(abc.ABCMeta, os.PathLike): pass
> subprocess.run(("echo",))'
Traceback (most recent call last):
  File "<string>", line 3, in <module>
  File "/usr/lib64/python3.12/subprocess.py", line 548, in run
    with Popen(*popenargs, **kwargs) as process:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/subprocess.py", line 1026, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib64/python3.12/subprocess.py", line 1802, in _execute_child
    elif isinstance(args, os.PathLike):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen abc>", line 119, in __instancecheck__
  File "<frozen abc>", line 123, in __subclasscheck__
  File "<frozen abc>", line 123, in __subclasscheck__
TypeError: unbound method type.__subclasses__() needs an argument

In my opinion, the __subclasses__ call should be guarded by a try-except and either:

  • skip broken subclasses, or
  • skip broken subclasses and warn about them, or
  • raise a better exception message, saying which subclass is broken

See also https://discuss.python.org/t/abcmeta-change-isinstancecheck-of-additional-parent-class/19908

CPython versions tested on:

3.8, 3.9, 3.10, 3.11, 3.12, 3.13, CPython main branch

Operating systems tested on:

No response

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 Lib/_py_abc.py 中递归调用 subclasses 的位置开始,重现 issue 中的两个示例。然后将这条路径与报告中提到的 C 实现进行比较。当有问题的 subclasses 定义不再导致不相关的 ABC 实例或子类检查失败,并且为报告的案例提供回归覆盖时,即可视为完成。

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

评估

技术栈
python
领域
backend
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
38/100

把新 issue 发到你的邮箱

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