python / python/cpython

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

Ouverte
#117,255 1 commentaire 2 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

type-bug
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

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. 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

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.