Skip to content

Stale entries in MRO cache if MRO contains non-base classes #127773

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

Open
colesbury opened this issue Dec 9, 2024 · 4 comments
Open

Stale entries in MRO cache if MRO contains non-base classes #127773

colesbury opened this issue Dec 9, 2024 · 4 comments
Assignees
Labels
3.12 only security fixes 3.13 bugs and security fixes 3.14 bugs and security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@colesbury
Copy link
Contributor

colesbury commented Dec 9, 2024

Bug report

For example the following program fails with either assert WeirdClass.value == 2 or assert WeirdClass.value == 3 in recent Python versions:

import sys

class Base:
    value = 1

class Meta(type):
    def mro(cls):
        return (cls, Base, object)

class WeirdClass(metaclass=Meta):
    pass

assert Base.value == 1
assert WeirdClass.value == 1

Base.value = 2
assert Base.value == 2
assert WeirdClass.value == 2

Base.value = 3
assert Base.value == 3
assert WeirdClass.value == 3

Adding intervening calls to sys _clear_internal_caches() makes the test pass.

Version Result
3.7 OK
3.8 OK
3.9 OK
3.10 AssertionError: assert WeirdClass.value == 2
3.11 AssertionError: assert WeirdClass.value == 2
3.12 AssertionError: assert WeirdClass.value == 2
3.13 AssertionError: assert WeirdClass.value == 3
main AssertionError: assert WeirdClass.value == 3

We have code that checks for this case, but it hasn't worked properly in Python 3.10+:

cpython/Objects/typeobject.c

Lines 1107 to 1112 in 5c89adf

static void
type_mro_modified(PyTypeObject *type, PyObject *bases) {
/*
Check that all base classes or elements of the MRO of type are
able to be cached. This function is called after the base
classes or mro of the type are altered.

We also have a test that partly covers this case, but doesn't detect the bug:

def test_freeze_meta(self):
"""test PyType_Freeze() with overridden MRO"""
type_freeze = _testcapi.type_freeze
class Base:
value = 1
class Meta(type):
def mro(cls):
return (cls, Base, object)
class FreezeThis(metaclass=Meta):
"""This has `Base` in the MRO, but not tp_bases"""
self.assertEqual(FreezeThis.value, 1)
with self.assertRaises(TypeError):
type_freeze(FreezeThis)
Base.value = 2
self.assertEqual(FreezeThis.value, 2)
type_freeze(Base)
with self.assertRaises(TypeError):
Base.value = 3
type_freeze(FreezeThis)
self.assertEqual(FreezeThis.value, 2)

Linked PRs

@colesbury colesbury added type-bug An unexpected behavior, bug, or error 3.12 only security fixes 3.13 bugs and security fixes 3.14 bugs and security fixes labels Dec 9, 2024
@Eclips4 Eclips4 added the interpreter-core (Objects, Python, Grammar, and Parser dirs) label Dec 9, 2024
@colesbury
Copy link
Contributor Author

cc @encukou - this broke in a4760cc (GH-27260), which removed Py_TPFLAGS_HAVE_VERSION_TAG.

The reason for removing Py_TPFLAGS_HAVE_VERSION_TAG was because "the field is always present in the type struct", but the flag indicates whether a type supports the method cache (i.e, supports having a valid version tag) and types with MRO entries that aren't superclasses don't support the method cache.

@cfbolz
Copy link
Contributor

cfbolz commented Dec 10, 2024

Great (if very annoying) find, @colesbury! The bug even affects pypy, which has an entirely different architecture for its caches 😳

@colesbury
Copy link
Contributor Author

I think this was fixed by @encukou

@encukou
Copy link
Member

encukou commented Feb 7, 2025

In the main branch. I don't know if I'll get to older branches in time.

@encukou encukou reopened this Feb 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.12 only security fixes 3.13 bugs and security fixes 3.14 bugs and 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

4 participants