ABCs with bogus __subclasses__ break instance/subclass checks for other ABCs
Dieses Issue hat noch niemand übernommen.
- 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
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Ö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