Issue #18408: Add a new PyFrame_FastToLocalsWithError() function to handle
exceptions when merging fast locals into f_locals of a frame.
PyEval_GetLocals() now raises an exception and return NULL on failure.
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index a62a45e..76e77b8 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -21,7 +21,8 @@
 static PyObject *
 frame_getlocals(PyFrameObject *f, void *closure)
 {
-    PyFrame_FastToLocals(f);
+    if (PyFrame_FastToLocalsWithError(f) < 0)
+        return NULL;
     Py_INCREF(f->f_locals);
     return f->f_locals;
 }
@@ -772,12 +773,9 @@
    If deref is true, then the values being copied are cell variables
    and the value is extracted from the cell variable before being put
    in dict.
-
-   Exceptions raised while modifying the dict are silently ignored,
-   because there is no good way to report them.
  */
 
-static void
+static int
 map_to_dict(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,
             int deref)
 {
@@ -794,14 +792,19 @@
             value = PyCell_GET(value);
         }
         if (value == NULL) {
-            if (PyObject_DelItem(dict, key) != 0)
-                PyErr_Clear();
+            if (PyObject_DelItem(dict, key) != 0) {
+                if (PyErr_ExceptionMatches(PyExc_KeyError))
+                    PyErr_Clear();
+                else
+                    return -1;
+            }
         }
         else {
             if (PyObject_SetItem(dict, key, value) != 0)
-                PyErr_Clear();
+                return -1;
         }
     }
+    return 0;
 }
 
 /* Copy values from the "locals" dict into the fast locals.
@@ -858,42 +861,49 @@
     }
 }
 
-void
-PyFrame_FastToLocals(PyFrameObject *f)
+int
+PyFrame_FastToLocalsWithError(PyFrameObject *f)
 {
     /* Merge fast locals into f->f_locals */
     PyObject *locals, *map;
     PyObject **fast;
-    PyObject *error_type, *error_value, *error_traceback;
     PyCodeObject *co;
     Py_ssize_t j;
     Py_ssize_t ncells, nfreevars;
-    if (f == NULL)
-        return;
+
+    if (f == NULL) {
+        PyErr_BadInternalCall();
+        return -1;
+    }
     locals = f->f_locals;
     if (locals == NULL) {
         locals = f->f_locals = PyDict_New();
-        if (locals == NULL) {
-            PyErr_Clear(); /* Can't report it :-( */
-            return;
-        }
+        if (locals == NULL)
+            return -1;
     }
     co = f->f_code;
     map = co->co_varnames;
-    if (!PyTuple_Check(map))
-        return;
-    PyErr_Fetch(&error_type, &error_value, &error_traceback);
+    if (!PyTuple_Check(map)) {
+        PyErr_Format(PyExc_SystemError,
+                     "co_varnames must be a tuple, not %s",
+                     Py_TYPE(map)->tp_name);
+        return -1;
+    }
     fast = f->f_localsplus;
     j = PyTuple_GET_SIZE(map);
     if (j > co->co_nlocals)
         j = co->co_nlocals;
-    if (co->co_nlocals)
-        map_to_dict(map, j, locals, fast, 0);
+    if (co->co_nlocals) {
+        if (map_to_dict(map, j, locals, fast, 0) < 0)
+            return -1;
+    }
     ncells = PyTuple_GET_SIZE(co->co_cellvars);
     nfreevars = PyTuple_GET_SIZE(co->co_freevars);
     if (ncells || nfreevars) {
-        map_to_dict(co->co_cellvars, ncells,
-                    locals, fast + co->co_nlocals, 1);
+        if (map_to_dict(co->co_cellvars, ncells,
+                        locals, fast + co->co_nlocals, 1))
+            return -1;
+
         /* If the namespace is unoptimized, then one of the
            following cases applies:
            1. It does not contain free variables, because it
@@ -903,11 +913,24 @@
            into the locals dict used by the class.
         */
         if (co->co_flags & CO_OPTIMIZED) {
-            map_to_dict(co->co_freevars, nfreevars,
-                        locals, fast + co->co_nlocals + ncells, 1);
+            if (map_to_dict(co->co_freevars, nfreevars,
+                            locals, fast + co->co_nlocals + ncells, 1) < 0)
+                return -1;
         }
     }
-    PyErr_Restore(error_type, error_value, error_traceback);
+    return 0;
+}
+
+void
+PyFrame_FastToLocals(PyFrameObject *f)
+{
+    int res;
+
+    assert(!PyErr_Occurred());
+
+    res = PyFrame_FastToLocalsWithError(f);
+    if (res < 0)
+        PyErr_Clear();
 }
 
 void