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]

Reply via email to