GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Flawed assumptions about `tp_dictoffset` in inheritance. · Issue #95589 · python/cpython

Flawed assumptions about tp_dictoffset in inheritance.  #95589

New issue
Copy link
New issue
Copy link
Closed
Closed
Flawed assumptions about tp_dictoffset in inheritance. #95589
Copy link
Labels
3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes3.9 (EOL)end of lifeend of lifetype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errortype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

Description

@markshannon
markshannon
opened on Aug 3, 2022
Issue body actions

In Python, the __dict__ and __weakref__ slots are treated specially (slots meaning __slots__, not tp_slots)
They are automatically insert by the VM when creating a class.

class C(list):
    pass

>>> C().__dict__
{}

In order to support inheritance, specifically multiple inheritance, the VM can lay out subclasses in ways that differ from the superclass.
This is OK, provided __dict__ and __weakref__ are only accessed though the tp_dictoffset and tp_weaklistoffset offsets.
But, if either field is accessed directly, then we access invalid memory and 💥

test.py:

from _testcapi import HeapCTypeWithDict
class I3(HeapCTypeWithDict, list): pass

i = I3()
i.append(0)
print(i.dictobj)
$ python3.10 ~/test/test.py 
Segmentation fault (core dumped)

We have (accidentally) fixed this for __dict__ in 3.11, although at the expense breaking backwards compatibility for some C extensions. However, the problem still remains for __weakref__.

Backwards incompatibility

from _testcapi import HeapCTypeWithDict
class I3(HeapCTypeWithDict, list): pass
print("OK")
$ python3.10 test.py 
OK
$ python3.12 test.py 
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    class I3(HeapCTypeWithDict, list): pass
TypeError: multiple bases have instance lay-out conflict

Activity

  1. markshannon
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    3.11only security fixesonly security fixes
    3.10 (EOL)end of lifeend of life
    3.9 (EOL)end of lifeend of life
    type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
    3.12only security fixesonly security fixes
    on Aug 3, 2022
  2. markshannon commented on Aug 3, 2022

    @markshannon
    markshannon
    on Aug 3, 2022
    MemberAuthor
    More actions

    Here's the weakref version:

    from _testcapi import HeapCTypeWithWeakref
    class I3(HeapCTypeWithWeakref, list): pass
    
    i = I3()
    i.append(0)
    i.weakreflist
    
    print("OK")
    
    $ python3.10 test.py 
    Segmentation fault (core dumped)
    $ python3.12 test.py 
    Segmentation fault (core dumped)
    
  3. markshannon commented on Aug 3, 2022

    @markshannon
    markshannon
    on Aug 3, 2022
    MemberAuthor
    More actions

    Proposed fix

    3.11

    Revert the inheritance behavior from 3.10
    Although this introduces possible crashes, it should help pybind11 and mypyc port to 3.11.

    3.12

    For 3.12 we should re-introduce the breaking, but not crashing, change and extend it to weakrefs.

    We then need to provide a better, and properly documented, API for for C extension classes to reliably
    use __dict__s and weakrefs managed by the VM.

  4. encukou commented on Aug 3, 2022

    @encukou
    encukou
    on Aug 3, 2022
    Member
    More actions

    +1. Though IMO it would be better to treat 3.10 behavior (only Py_TYPE(obj)->tp_dictoffset is valid) as the status quo, and the improvements as a change.

    For the better API, my current thinking is adding API for clearing/visiting only a single type's data, so e.g. tp_visit would call each base's tp_visit_data in a loop, with “VM managed” things (dict, weaklist, type) handled only in CPython code. Users could still override all of tp_visit for backcompat (and/or speed?), but we'd discourage it.

  5. markshannon commented on Aug 3, 2022

    @markshannon
    markshannon
    on Aug 3, 2022
    MemberAuthor
    More actions

    As only one base class can have opaque data, there would be only be one valid tp_visit_data, which speeds things up.

    There are only about 10 or so valid permutations of pre-headers, opaque data, and slots, so we can generate a complete set of efficient visit, clear and dealloc functions for use in subclasses.

  6. encukou commented on Aug 3, 2022

    @encukou
    encukou
    on Aug 3, 2022
    Member
    More actions

    Revert the inheritance behavior from 3.10

    Can you do that today/tomorrow, for the RC?

    As only one base class can have opaque data, there would be only be one valid tp_visit_data, which speeds things up.

    Only one direct base, but its base can also have opaque data. Still it'd be only one chain to walk for data&slots.

  7. markshannon commented on Aug 3, 2022

    @markshannon
    markshannon
    on Aug 3, 2022
    MemberAuthor
    More actions

    Can you do that today/tomorrow, for the RC?

    Sure.
    @Fidget-Spinner already has #95242 open, but I'll need to add tests.

    Reacted by Petr Viktorin
  8. markshannon commented on Aug 3, 2022

    @markshannon
    markshannon
    on Aug 3, 2022
    MemberAuthor
    More actions

    Only one direct base, but its base can also have opaque data.

    That's the responsibility of the direct base, since both must be written in C to do that.

  9. encukou commented on Aug 3, 2022

    @encukou
    encukou
    on Aug 3, 2022
    Member
    More actions

    both must be written in C to do that.

    True, but they might be from different libraries with different release schedules. The other library might be CPython, with its changing requirements on what needs to be visited – or it could be pybind11 or numpy or whatever, CPython shouldn't need special treatment here for its pre-headers.
    An alternative would be that you're expected to delegate to the base, but it's surprisingly tricky to do the equivalent of super() correctly (as shown, in part, by subtype_visit with managed dicts).

  10. Fidget-Spinner commented on Aug 3, 2022

    @Fidget-Spinner
    Fidget-Spinner
    on Aug 3, 2022
    Member
    More actions

    Can you do that today/tomorrow, for the RC?

    Sure. @Fidget-Spinner already has #95242 open, but I'll need to add tests.

    Superseded by #95596.

  11. markshannon commented on Aug 5, 2022

    @markshannon
    markshannon
    on Aug 5, 2022
    MemberAuthor
    More actions

    True, but they might be from different libraries with different release schedules.

    That's not possible. If there were then the only way they could be combined is in Python, which would raise a TypeError due to the incompatible layout.

  12. markshannon
    added a commit that references this issue on Aug 17, 2022

    GH-95589: Dont crash when subclassing extension classes with multiple…

    b73e3b6
  13. markshannon
    closed this as completedon Aug 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.10 (EOL)end of lifeend of life3.11only security fixesonly security fixes3.12only security fixesonly security fixes3.9 (EOL)end of lifeend of lifetype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errortype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.