ABCs with bogus __subclasses__ break instance/subclass checks for other ABCs
Personne n'a encore pris cette issue.
- Langage dominant
- Python
- Étoiles
- 77.2k
- Forks
- 35.9k
- Métriques de merge des PR
- Métriques de PR en attente
Description
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
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez par Lib/_py_abc.py, au niveau de l’appel récursif à subclasses, et reproduisez les deux exemples de l’issue. Comparez ensuite ce chemin avec l’implémentation en C mentionnée dans le rapport. Le travail est terminé lorsque des définitions défectueuses de subclasses ne font plus échouer des vérifications d’instances ou de sous-classes ABC sans rapport, avec une couverture de régression pour les cas signalés.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- python
- Domaine
- backend
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- À l'abandon
- Clarté
- Plutôt claire
- Accessibilité débutants
- 38/100