https://github.com/python/cpython/commit/12dfd4d25498dcef53d91cc89499e4a6d851962d
commit: 12dfd4d25498dcef53d91cc89499e4a6d851962d
branch: main
author: Neil Schemenauer <[email protected]>
committer: nascheme <[email protected]>
date: 2026-11-04T12:39:52-07:00
summary:
gh-159434: Avoid borrowed references in func_get_annotation_dict() (gh-159436)
Hold strong references to __annotate__ and the annotations tuple while running
code that can replace them. If __annotations__ is set while __annotate__
runs, keep the newly set value.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-19-16-53.gh-issue-159434.T4AsrQ.rst
M Lib/test/test_type_annotations.py
M Objects/funcobject.c
diff --git a/Lib/test/test_type_annotations.py
b/Lib/test/test_type_annotations.py
index 54db123a5dc2bf..cb164ccd147127 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -328,6 +328,18 @@ def check_annotations(self, f):
f.__annotations__ = {"z": 43}
self.assertIs(f.__annotate__, None)
+ def test_annotate_sets_annotations(self):
+ # If __annotate__ sets the function's annotations while it runs,
+ # those are kept instead of the result of the call.
+ def f():
+ pass
+ def annotate(format):
+ f.__annotations__ = {"x": int}
+ return {"y": str}
+ f.__annotate__ = annotate
+ self.assertEqual(f.__annotations__, {"x": int})
+ self.assertIs(f.__annotate__, None)
+
def test_user_defined_annotate(self):
class X:
a: int
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-19-16-53.gh-issue-159434.T4AsrQ.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-19-16-53.gh-issue-159434.T4AsrQ.rst
new file mode 100644
index 00000000000000..f7d7fbffa3371e
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-19-16-53.gh-issue-159434.T4AsrQ.rst
@@ -0,0 +1,4 @@
+Fix reading a function's :attr:`~function.__annotations__` using borrowed
+references to :attr:`~function.__annotate__` and the internal annotations
+tuple while running code that can replace them. If ``__annotations__`` is
+set while ``__annotate__`` runs, the newly set value is now kept.
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index d5caa7fceb05be..99f08863da4604 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -560,11 +560,18 @@ func_get_annotation_dict(PyFunctionObject *op)
{
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
if (op->func_annotations == NULL) {
- if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))
{
+ PyObject *annotate = op->func_annotate;
+ if (annotate == NULL || !PyCallable_Check(annotate)) {
Py_RETURN_NONE;
}
+ // Calling __annotate__ can run arbitrary code which may replace
+ // func_annotate or func_annotations, either directly or from another
+ // thread while the critical section is suspended. Keep the callable
+ // alive during the call.
+ Py_INCREF(annotate);
PyObject *one = _PyLong_GetOne();
- PyObject *ann_dict = _PyObject_CallOneArg(op->func_annotate, one);
+ PyObject *ann_dict = _PyObject_CallOneArg(annotate, one);
+ Py_DECREF(annotate);
if (ann_dict == NULL) {
return NULL;
}
@@ -575,13 +582,22 @@ func_get_annotation_dict(PyFunctionObject *op)
Py_DECREF(ann_dict);
return NULL;
}
+ if (op->func_annotations != NULL) {
+ // Annotations were set during the call, keep them.
+ Py_DECREF(ann_dict);
+ assert(PyDict_Check(op->func_annotations));
+ return op->func_annotations;
+ }
_PyObject_XSetRefDelayed(&op->func_annotations, ann_dict);
return ann_dict;
}
if (PyTuple_CheckExact(op->func_annotations)) {
- PyObject *ann_tuple = op->func_annotations;
+ // Keep the tuple alive while building the dict, since it can be
+ // replaced if the critical section is suspended.
+ PyObject *ann_tuple = Py_NewRef(op->func_annotations);
PyObject *ann_dict = PyDict_New();
if (ann_dict == NULL) {
+ Py_DECREF(ann_tuple);
return NULL;
}
@@ -594,10 +610,12 @@ func_get_annotation_dict(PyFunctionObject *op)
if (err < 0) {
Py_DECREF(ann_dict);
+ Py_DECREF(ann_tuple);
return NULL;
}
}
_PyObject_XSetRefDelayed(&op->func_annotations, ann_dict);
+ _PyObject_XDecRefDelayed(ann_tuple);
}
assert(PyDict_Check(op->func_annotations));
return op->func_annotations;
_______________________________________________
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]