https://github.com/python/cpython/commit/c21e7c45a9770fba9ea3b8d5abb52c19274a08ef
commit: c21e7c45a9770fba9ea3b8d5abb52c19274a08ef
branch: 3.14
author: Timofei Ivankov <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-08-05T07:24:50-07:00
summary:
[3.14] gh-154902: Type-check the SET_ADD operand (#155072)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-08-02-11-47-15.gh-issue-154902.DIi6sf.rst
M Lib/test/test_type_annotations.py
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h
diff --git a/Lib/test/test_type_annotations.py
b/Lib/test/test_type_annotations.py
index c98b99e98e9c8e6..960756263d26735 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/Python/bytecodes.c b/Python/bytecodes.c
index 509a5a6e13f8570..e17c77828389a30 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -1056,7 +1056,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 f5c11e0f98dae68..0312157af76df39 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -1609,8 +1609,21 @@
oparg = CURRENT_OPARG();
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());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(v);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ JUMP_TO_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/generated_cases.c.h b/Python/generated_cases.c.h
index 9e825c529c9dd2d..6269016801264db 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -10859,8 +10859,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());
+ _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) {
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]