From da973aff2adab60a9e516d3202c111dbdde1a50f Mon Sep 17 00:00:00 2001
From: Alexander Shadchin <alexandr.shadchin@gmail.com>
Date: Sun, 14 Aug 2022 21:13:49 +0300
Subject: [PATCH] Fix build with Python 3.11

The PyFrameObject structure members have been removed from the public C API.
---
 python/google/protobuf/pyext/descriptor.cc | 75 ++++++++++++++++++----
 1 file changed, 62 insertions(+), 13 deletions(-)

diff --git a/python/google/protobuf/pyext/descriptor.cc b/python/google/protobuf/pyext/descriptor.cc
index fc83acf01a78abce470b5d34a94258ff0733a6bb..fc97b0fa6c11966f9215eecf7690f00aa7cb02c9 100644
--- google/protobuf/pyext/descriptor.cc
+++ google/protobuf/pyext/descriptor.cc
@@ -58,6 +58,37 @@
               : 0)                                               \
        : PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
 
+#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
+static PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)
+{
+    Py_INCREF(frame->f_code);
+    return frame->f_code;
+}
+
+static PyFrameObject* PyFrame_GetBack(PyFrameObject *frame)
+{
+    Py_XINCREF(frame->f_back);
+    return frame->f_back;
+}
+#endif
+
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static PyObject* PyFrame_GetLocals(PyFrameObject *frame)
+{
+    if (PyFrame_FastToLocalsWithError(frame) < 0) {
+        return NULL;
+    }
+    Py_INCREF(frame->f_locals);
+    return frame->f_locals;
+}
+
+static PyObject* PyFrame_GetGlobals(PyFrameObject *frame)
+{
+    Py_INCREF(frame->f_globals);
+    return frame->f_globals;
+}
+#endif
+
 namespace google {
 namespace protobuf {
 namespace python {
@@ -96,48 +127,66 @@ bool _CalledFromGeneratedFile(int stacklevel) {
   // This check is not critical and is somewhat difficult to implement correctly
   // in PyPy.
   PyFrameObject* frame = PyEval_GetFrame();
+  PyCodeObject* frame_code = nullptr;
+  PyObject* frame_globals = nullptr;
+  PyObject* frame_locals = nullptr;
+  bool result = false;
+
   if (frame == nullptr) {
-    return false;
+    goto exit;
   }
+  Py_INCREF(frame);
   while (stacklevel-- > 0) {
-    frame = frame->f_back;
+    PyFrameObject* next_frame = PyFrame_GetBack(frame);
+    Py_DECREF(frame);
+    frame = next_frame;
     if (frame == nullptr) {
-      return false;
+      goto exit;
     }
   }
 
-  if (frame->f_code->co_filename == nullptr) {
-    return false;
+  frame_code = PyFrame_GetCode(frame);
+  if (frame_code->co_filename == nullptr) {
+    goto exit;
   }
   char* filename;
   Py_ssize_t filename_size;
-  if (PyString_AsStringAndSize(frame->f_code->co_filename,
+  if (PyString_AsStringAndSize(frame_code->co_filename,
                                &filename, &filename_size) < 0) {
     // filename is not a string.
     PyErr_Clear();
-    return false;
+    goto exit;
   }
   if ((filename_size < 3) ||
       (strcmp(&filename[filename_size - 3], ".py") != 0)) {
     // Cython's stack does not have .py file name and is not at global module
     // scope.
-    return true;
+    result = true;
+    goto exit;
   }
   if (filename_size < 7) {
     // filename is too short.
-    return false;
+    goto exit;
   }
   if (strcmp(&filename[filename_size - 7], "_pb2.py") != 0) {
     // Filename is not ending with _pb2.
-    return false;
+    goto exit;
   }
 
-  if (frame->f_globals != frame->f_locals) {
+  frame_globals = PyFrame_GetGlobals(frame);
+  frame_locals = PyFrame_GetLocals(frame);
+  if (frame_globals != frame_locals) {
     // Not at global module scope
-    return false;
+    goto exit;
   }
 #endif
-  return true;
+  result = true;
+exit:
+  Py_XDECREF(frame_globals);
+  Py_XDECREF(frame_locals);
+  Py_XDECREF(frame_code);
+  Py_XDECREF(frame);
+  return result;
 }
 
 // If the calling code is not a _pb2.py file, raise AttributeError.
