Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupting class creation in __init_subclass__ may lead to incorrect isinstance() and issubclass() results #82266

Open
xitop mannequin opened this issue Sep 10, 2019 · 9 comments
Labels
3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@xitop
Copy link
Mannequin

xitop mannequin commented Sep 10, 2019

BPO 38085
Nosy @rhettinger, @cfbolz, @ambv, @hongweipeng
PRs
  • bpo-38085: Fix throw exception in __init__subclass__ causes wrong isinstance() a… #30112
  • Files
  • x.py
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2019-09-10.09:09:41.066>
    labels = ['interpreter-core', '3.8', 'type-bug', '3.7', '3.9']
    title = 'Interrupting class creation in __init_subclass__ may lead to incorrect isinstance() and issubclass() results'
    updated_at = <Date 2021-12-23.20:42:44.132>
    user = 'https://bugs.python.org/xitop'

    bugs.python.org fields:

    activity = <Date 2021-12-23.20:42:44.132>
    actor = 'Carl.Friedrich.Bolz'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Interpreter Core']
    creation = <Date 2019-09-10.09:09:41.066>
    creator = 'xitop'
    dependencies = []
    files = ['50515']
    hgrepos = []
    issue_num = 38085
    keywords = ['patch']
    message_count = 9.0
    messages = ['351599', '355037', '358966', '381210', '383796', '401784', '401998', '409106', '409107']
    nosy_count = 5.0
    nosy_names = ['rhettinger', 'Carl.Friedrich.Bolz', 'lukasz.langa', 'hongweipeng', 'xitop']
    pr_nums = ['30112']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue38085'
    versions = ['Python 3.6', 'Python 3.7', 'Python 3.8', 'Python 3.9']

    @xitop
    Copy link
    Mannequin Author

    xitop mannequin commented Sep 10, 2019

    An exception in __init__subclass__ leads under certain circumstances to wrong isinstance() and issubclass() results. The exception probably leaves Python internal data in inconsistent state.

    Here is a demonstration program from Stack Overflow:

    from abc import ABCMeta
    
    class Animal(metaclass=ABCMeta):
        pass
    
    class Plant(metaclass=ABCMeta):
        def __init_subclass__(cls):
            assert not issubclass(cls, Animal), "Plants cannot be Animals"
    
    class Dog(Animal):
        pass
    
    try:
        class Triffid(Animal, Plant):
            pass
    except Exception:
        pass
    
    print("Dog is Animal?", issubclass(Dog, Animal))
    print("Dog is Plant?", issubclass(Dog, Plant))

    Result is:

    Dog is Animal? True
    Dog is Plant? True

    Changing the order of the print statements will result in:

    Dog is Plant? False
    Dog is Animal? False

    Another ill-behaving program and a partial analysis can be found at SO: https://stackoverflow.com/q/57848663/5378816

    @xitop xitop mannequin added 3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error labels Sep 10, 2019
    @xitop
    Copy link
    Mannequin Author

    xitop mannequin commented Oct 21, 2019

    Same problem also in Python 3.6.8 and the new 3.8.0.

    @xitop xitop mannequin added the 3.8 only security fixes label Oct 21, 2019
    @xitop
    Copy link
    Mannequin Author

    xitop mannequin commented Dec 28, 2019

    Please, could some experienced Python developer take a look at this issue reported more than 3 month ago?

    @hongweipeng
    Copy link
    Mannequin

    hongweipeng mannequin commented Nov 17, 2020

    Class Triffid failed to create in __init_subclass__, so Triffid._abc_cache uses its base class _abc_cache.

    @xitop
    Copy link
    Mannequin Author

    xitop mannequin commented Dec 26, 2020

    Python 3.9.1 is affected too.

    @xitop xitop mannequin added the 3.9 only security fixes label Dec 26, 2020
    @xitop
    Copy link
    Mannequin Author

    xitop mannequin commented Sep 14, 2021

    2nd anniversary. Any reaction from developers would be appreciated. Thank you.

    @rhettinger
    Copy link
    Contributor

    Confirmed that this weird behavior is still present. Am not sure when someone will have the time and inclination to drill into the cause.

    @cfbolz
    Copy link
    Mannequin

    cfbolz mannequin commented Dec 23, 2021

    hm, I think I figured it out. The root cause is that even though the creation of the class Triffid fails, it can still be found via Animal.__subclasses__(), which the special subclass logic for ABCs is looking at. Triffid fills its _abc_impl data with some content, but Triffid._abc_impl was never successfully initialized, therefore it mutates the _abc_impl of its first base class Animal.

    My conclusion would be that if a class is not successfully created, it shouldn't appear in the .__subclasses__() list of its bases.

    See attached script for some illuminating prints.

    @cfbolz
    Copy link
    Mannequin

    cfbolz mannequin commented Dec 23, 2021

    Or, in other words, in my opinion this is the root cause of the bug:

    class Base:
        def __init_subclass__(cls):
            global broken_class
            broken_class = cls
            assert 0
    try:
        class Broken(Base): pass
    except: pass
    assert broken_class not in Base.__subclasses__()

    The assert fails, which imo it shouldn't.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    1 participant