diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index b751f825bb97d59..54db123a5dc2bf3 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,5 +1,6 @@ import annotationlib import inspect +import itertools import textwrap import types import unittest @@ -896,3 +897,18 @@ class Generic: mod = build_module(code) annos = mod.__annotations__ self.assertEqual(annos, {"annotated_name": 0}) + + # gh-154902 + def test_conditional_annotations_rebound(self): + # user code can rebind __conditional_annotations__ to any object + lefts = ("__conditional_annotations__", + 'globals()["__conditional_annotations__"]') + values = ("0", "{}", "[]", "''", "object()", "frozenset()") + for left, value in itertools.product(lefts, values): + with self.subTest(left=left, value=value): + code = f""" + {left} = {value} + x: int + """ + with self.assertRaises(TypeError): + run_code(code) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-11-47-15.gh-issue-154902.DIi6sf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-11-47-15.gh-issue-154902.DIi6sf.rst new file mode 100644 index 000000000000000..49f34eaeddec4e4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-11-47-15.gh-issue-154902.DIi6sf.rst @@ -0,0 +1,2 @@ +Fix a crash when ``__conditional_annotations__`` is rebound to a non-set +object. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 12f028618f5c69a..98e84c335a0b5f9 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -11424,8 +11424,21 @@ _PyStackRef v; v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + if (!PySet_CheckExact(set_o)) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyErr_Format(tstate, PyExc_TypeError, + "'%T' object is not a set", set_o); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(v); + stack_pointer = _PyFrame_GetStackPointer(frame); + JUMP_TO_LABEL(error); + } _PyFrame_SetStackPointer(frame, stack_pointer); - int err = _PySet_AddTakeRef((PySetObject *)PyStackRef_AsPyObjectBorrow(set), + int err = _PySet_AddTakeRef((PySetObject *)set_o, PyStackRef_AsPyObjectSteal(v)); stack_pointer = _PyFrame_GetStackPointer(frame); if (err) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 4f8a67d33fff529..2ee764a5a4ebcba 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1378,7 +1378,15 @@ dummy_func( } inst(SET_ADD, (set, unused[oparg-1], v -- set, unused[oparg-1])) { - int err = _PySet_AddTakeRef((PySetObject *)PyStackRef_AsPyObjectBorrow(set), + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + // gh-154902: user code can rebind __conditional_annotations__ + if (!PySet_CheckExact(set_o)) { + _PyErr_Format(tstate, PyExc_TypeError, + "'%T' object is not a set", set_o); + PyStackRef_CLOSE(v); + ERROR_IF(true); + } + int err = _PySet_AddTakeRef((PySetObject *)set_o, PyStackRef_AsPyObjectSteal(v)); ERROR_IF(err); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 4d5a8ab6b8af0b9..7644c2d38d7f068 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -8212,11 +8212,28 @@ oparg = CURRENT_OPARG(); v = _stack_item_0; set = stack_pointer[-1 - (oparg-1)]; + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + if (!PySet_CheckExact(set_o)) { + stack_pointer[0] = v; + stack_pointer += 1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyErr_Format(tstate, PyExc_TypeError, + "'%T' object is not a set", set_o); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(v); + stack_pointer = _PyFrame_GetStackPointer(frame); + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_ERROR(); + } stack_pointer[0] = v; stack_pointer += 1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - int err = _PySet_AddTakeRef((PySetObject *)PyStackRef_AsPyObjectBorrow(set), + int err = _PySet_AddTakeRef((PySetObject *)set_o, PyStackRef_AsPyObjectSteal(v)); stack_pointer = _PyFrame_GetStackPointer(frame); if (err) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 24ffb07830adf47..58444c6bd6dd08f 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -11421,8 +11421,21 @@ _PyStackRef v; v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + if (!PySet_CheckExact(set_o)) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyErr_Format(tstate, PyExc_TypeError, + "'%T' object is not a set", set_o); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(v); + stack_pointer = _PyFrame_GetStackPointer(frame); + JUMP_TO_LABEL(error); + } _PyFrame_SetStackPointer(frame, stack_pointer); - int err = _PySet_AddTakeRef((PySetObject *)PyStackRef_AsPyObjectBorrow(set), + int err = _PySet_AddTakeRef((PySetObject *)set_o, PyStackRef_AsPyObjectSteal(v)); stack_pointer = _PyFrame_GetStackPointer(frame); if (err) {