From 5a28d24132803d5af8c6b9775ef31be691189162 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 2 Aug 2026 11:47:39 +0300 Subject: [PATCH 1/3] gh-154902: Type-check the SET_ADD operand --- Lib/test/test_type_annotations.py | 16 +++++++++++++++ ...-08-02-11-47-15.gh-issue-154902.DIi6sf.rst | 2 ++ Modules/_testinternalcapi/test_cases.c.h | 16 ++++++++++++++- Python/bytecodes.c | 11 +++++++++- Python/executor_cases.c.h | 20 ++++++++++++++++++- Python/generated_cases.c.h | 16 ++++++++++++++- 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-11-47-15.gh-issue-154902.DIi6sf.rst 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..1059192b814b02e 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -11424,8 +11424,22 @@ _PyStackRef v; v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + if (!PySet_Check(set_o)) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyErr_Format(tstate, PyExc_TypeError, + "__conditional_annotations__ must be a set, not %T", + 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..0a64374dc42f5d8 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1378,7 +1378,16 @@ 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_Check(set_o)) { + _PyErr_Format(tstate, PyExc_TypeError, + "__conditional_annotations__ must be a set, not %T", + 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..5e1d9cf12bc962d 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -8212,11 +8212,29 @@ oparg = CURRENT_OPARG(); v = _stack_item_0; set = stack_pointer[-1 - (oparg-1)]; + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + if (!PySet_Check(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, + "__conditional_annotations__ must be a set, not %T", + 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..041efffac931ca5 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -11421,8 +11421,22 @@ _PyStackRef v; v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; + PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); + if (!PySet_Check(set_o)) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyErr_Format(tstate, PyExc_TypeError, + "__conditional_annotations__ must be a set, not %T", + 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) { From 195e64ced37cd60ae5093a01b62c40ca6714a393 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 2 Aug 2026 18:47:52 +0300 Subject: [PATCH 2/3] change exception message --- Modules/_testinternalcapi/test_cases.c.h | 3 +-- Python/bytecodes.c | 3 +-- Python/executor_cases.c.h | 3 +-- Python/generated_cases.c.h | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 1059192b814b02e..513b02b463d53ed 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -11428,8 +11428,7 @@ if (!PySet_Check(set_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); _PyErr_Format(tstate, PyExc_TypeError, - "__conditional_annotations__ must be a set, not %T", - set_o); + "'%T' object is not a set", set_o); stack_pointer = _PyFrame_GetStackPointer(frame); stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 0a64374dc42f5d8..56fcb62a38bc880 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1382,8 +1382,7 @@ dummy_func( // gh-154902: user code can rebind __conditional_annotations__ if (!PySet_Check(set_o)) { _PyErr_Format(tstate, PyExc_TypeError, - "__conditional_annotations__ must be a set, not %T", - set_o); + "'%T' object is not a set", set_o); PyStackRef_CLOSE(v); ERROR_IF(true); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 5e1d9cf12bc962d..9df9dc6fb3ec0a3 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -8219,8 +8219,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyErr_Format(tstate, PyExc_TypeError, - "__conditional_annotations__ must be a set, not %T", - set_o); + "'%T' object is not a set", set_o); stack_pointer = _PyFrame_GetStackPointer(frame); stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 041efffac931ca5..f723affe2ccdb17 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -11425,8 +11425,7 @@ if (!PySet_Check(set_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); _PyErr_Format(tstate, PyExc_TypeError, - "__conditional_annotations__ must be a set, not %T", - set_o); + "'%T' object is not a set", set_o); stack_pointer = _PyFrame_GetStackPointer(frame); stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); From c42817bfc35e5f4dd534f608bd01a03138a9edbf Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 2 Aug 2026 19:11:31 +0300 Subject: [PATCH 3/3] chaged check to PySet_CheckExact --- Modules/_testinternalcapi/test_cases.c.h | 2 +- Python/bytecodes.c | 2 +- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 513b02b463d53ed..98e84c335a0b5f9 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -11425,7 +11425,7 @@ v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); - if (!PySet_Check(set_o)) { + if (!PySet_CheckExact(set_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); _PyErr_Format(tstate, PyExc_TypeError, "'%T' object is not a set", set_o); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 56fcb62a38bc880..2ee764a5a4ebcba 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1380,7 +1380,7 @@ dummy_func( inst(SET_ADD, (set, unused[oparg-1], v -- set, unused[oparg-1])) { PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); // gh-154902: user code can rebind __conditional_annotations__ - if (!PySet_Check(set_o)) { + if (!PySet_CheckExact(set_o)) { _PyErr_Format(tstate, PyExc_TypeError, "'%T' object is not a set", set_o); PyStackRef_CLOSE(v); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 9df9dc6fb3ec0a3..7644c2d38d7f068 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -8213,7 +8213,7 @@ v = _stack_item_0; set = stack_pointer[-1 - (oparg-1)]; PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); - if (!PySet_Check(set_o)) { + if (!PySet_CheckExact(set_o)) { stack_pointer[0] = v; stack_pointer += 1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index f723affe2ccdb17..58444c6bd6dd08f 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -11422,7 +11422,7 @@ v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; PyObject *set_o = PyStackRef_AsPyObjectBorrow(set); - if (!PySet_Check(set_o)) { + if (!PySet_CheckExact(set_o)) { _PyFrame_SetStackPointer(frame, stack_pointer); _PyErr_Format(tstate, PyExc_TypeError, "'%T' object is not a set", set_o);