python / python/cpython

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

Offen
#117,255 1 Kommentar 2 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

type-bug
Vorherrschende Sprache
Python
Sterne
77.2k
Forks
35.9k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

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

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne bei Lib/_py_abc.py mit dem rekursiven Aufruf von subclasses und reproduziere beide Beispiele aus dem Issue. Vergleiche diesen Pfad anschließend mit der im Bericht erwähnten C-Implementierung. Die Arbeit ist abgeschlossen, wenn fehlerhafte subclasses-Definitionen nicht mehr dazu führen, dass unabhängige ABC-Instanz- oder Subklassenprüfungen fehlschlagen, und eine Regressionstestabdeckung für die gemeldeten Fälle vorhanden ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
backend
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
38/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.