Skip to content

Panic in lookup_slot_in_mro when a class whose mro() failed is still alive #9526

Closed

Description

@taxor03

Summary

RustPython panics (exit code 101) on a valid program. A metaclass mro() tries to subclass the class being created, which correctly fails with TypeError: Cannot extend an incomplete type. If the half-created subclass is kept alive, creating the outer class then panics in crates/vm/src/types/slot.rs.

Reproducer

keep = []
class M(type):
    def mro(cls):
        if cls.__name__ == 'X':
            keep.append(cls)
        if cls.__mro__ is None and cls.__name__ != 'X':
            try:
                class X(cls):
                    pass
            except TypeError as e:
                print('TypeError:', e)
        return type.mro(cls)

class A(metaclass=M):
    pass
print(A.__mro__)
print([(k, k.__mro__) for k in keep])

Expected

CPython 3.12.13:

TypeError: Cannot extend an incomplete type 'A'
(<class '__main__.A'>, <class 'object'>)
[(<class '__main__.M.mro.<locals>.X'>, None)]

Actual

RustPython main (ef023bc):

thread 'main' panicked at crates/vm/src/types/slot.rs:2031:28:
range start index 1 out of range for slice of length 0

Without the keep.append(cls) line the program works (this is CPython's test_descr.MroTest.test_incomplete_extend, which passes).

Cause

With a custom mro(), PyType::new_heap_inner creates the type with an empty MRO and adds it to its bases' subclasses before mro() runs (type.rs#L1152-L1165). When mro() then fails for X, X stays in A's subclass list with an empty MRO. Creating A later calls init_slots -> update_slot -> update_subclasses (slot.rs#L938-L959), which reaches X through the weak reference, and lookup_slot_in_mro slices mro[1..] (slot.rs#L2030-L2031). Backtrace:

<rustpython_vm::builtins::type_::PyType>::update_one_slot::<true>
<rustpython_vm::builtins::type_::PyType>::update_subclasses::<true>
<rustpython_vm::builtins::type_::PyType>::update_slot::<true>
<rustpython_vm::builtins::type_::PyType>::init_slots
<rustpython_vm::builtins::type_::PyType as rustpython_vm::types::slot::Constructor>::slot_new

CPython computes the MRO before it registers the new type as a subclass of its bases, so a type whose mro() failed is never in a subclass list. Either registering the subclass only after the MRO is set, or skipping subclasses with an empty MRO in update_subclasses, would avoid the panic.

Python Documentation

type.mro(): "This method can be overridden by a metaclass to customize the method resolution order for its instances. It is called at class instantiation, and its result is stored in __mro__."

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions