https://github.com/python/cpython/commit/36f166d84b2f496f096ddd755545f3c8e193233e
commit: 36f166d84b2f496f096ddd755545f3c8e193233e
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: nascheme <[email protected]>
date: 2026-11-07T13:45:44-07:00
summary:

[3.14] gh-159434: Avoid borrowed references in func_get_annotation_dict() 
(gh-159436) (gh-161347)

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.

(cherry picked from commit 12dfd4d25498dcef53d91cc89499e4a6d851962d)

Co-authored-by: Neil Schemenauer <[email protected]>

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 960756263d2673..86c519fd9c7c53 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 405b5a455f1203..3caab91749e08e 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -577,11 +577,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;
         }
@@ -591,13 +598,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;
         }
 
@@ -610,10 +626,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