summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTian Gao <gaogaotiantian@hotmail.com>2024-05-04 11:12:10 (GMT)
committerGitHub <noreply@github.com>2024-05-04 11:12:10 (GMT)
commitb034f14a4b6e9197d3926046721b8b4b4b4f5b3d (patch)
tree2d641189f3401526d264d13e5b855c576c29dc63
parent1ab6356ebec25f216a0eddbd81225abcb93f2d55 (diff)
downloadcpython-b034f14a4b6e9197d3926046721b8b4b4b4f5b3d.zip
cpython-b034f14a4b6e9197d3926046721b8b4b4b4f5b3d.tar.gz
cpython-b034f14a4b6e9197d3926046721b8b4b4b4f5b3d.tar.bz2
gh-74929: Implement PEP 667 (GH-115153)
-rw-r--r--Doc/data/stable_abi.dat3
-rw-r--r--Include/ceval.h4
-rw-r--r--Include/cpython/frameobject.h6
-rw-r--r--Include/cpython/pyframe.h2
-rw-r--r--Include/internal/pycore_frame.h13
-rw-r--r--Lib/test/test_frame.py186
-rw-r--r--Lib/test/test_listcomps.py7
-rw-r--r--Lib/test/test_peepholer.py17
-rw-r--r--Lib/test/test_stable_abi_ctypes.py3
-rw-r--r--Lib/test/test_sys.py2
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst1
-rw-r--r--Misc/stable_abi.toml6
-rw-r--r--Objects/frameobject.c860
-rw-r--r--Objects/object.c1
-rwxr-xr-xPC/python3dll.c3
-rw-r--r--Python/ceval.c48
-rw-r--r--Python/intrinsics.c7
-rw-r--r--Python/sysmodule.c8
-rw-r--r--Tools/c-analyzer/cpython/globals-to-fix.tsv1
19 files changed, 921 insertions, 257 deletions
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 8c8a378..76a035f 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -188,6 +188,9 @@ function,PyEval_EvalFrame,3.2,,
function,PyEval_EvalFrameEx,3.2,,
function,PyEval_GetBuiltins,3.2,,
function,PyEval_GetFrame,3.2,,
+function,PyEval_GetFrameBuiltins,3.13,,
+function,PyEval_GetFrameGlobals,3.13,,
+function,PyEval_GetFrameLocals,3.13,,
function,PyEval_GetFuncDesc,3.2,,
function,PyEval_GetFuncName,3.2,,
function,PyEval_GetGlobals,3.2,,
diff --git a/Include/ceval.h b/Include/ceval.h
index 8ea9da8..1ec746c 100644
--- a/Include/ceval.h
+++ b/Include/ceval.h
@@ -22,6 +22,10 @@ PyAPI_FUNC(PyObject *) PyEval_GetGlobals(void);
PyAPI_FUNC(PyObject *) PyEval_GetLocals(void);
PyAPI_FUNC(PyFrameObject *) PyEval_GetFrame(void);
+PyAPI_FUNC(PyObject *) PyEval_GetFrameBuiltins(void);
+PyAPI_FUNC(PyObject *) PyEval_GetFrameGlobals(void);
+PyAPI_FUNC(PyObject *) PyEval_GetFrameLocals(void);
+
PyAPI_FUNC(int) Py_AddPendingCall(int (*func)(void *), void *arg);
PyAPI_FUNC(int) Py_MakePendingCalls(void);
diff --git a/Include/cpython/frameobject.h b/Include/cpython/frameobject.h
index 4e19535..dbbfbb5 100644
--- a/Include/cpython/frameobject.h
+++ b/Include/cpython/frameobject.h
@@ -27,3 +27,9 @@ PyAPI_FUNC(int) _PyFrame_IsEntryFrame(PyFrameObject *frame);
PyAPI_FUNC(int) PyFrame_FastToLocalsWithError(PyFrameObject *f);
PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
+
+
+typedef struct {
+ PyObject_HEAD
+ PyFrameObject* frame;
+} PyFrameLocalsProxyObject;
diff --git a/Include/cpython/pyframe.h b/Include/cpython/pyframe.h
index c5adbbe..eeafbb1 100644
--- a/Include/cpython/pyframe.h
+++ b/Include/cpython/pyframe.h
@@ -3,8 +3,10 @@
#endif
PyAPI_DATA(PyTypeObject) PyFrame_Type;
+PyAPI_DATA(PyTypeObject) PyFrameLocalsProxy_Type;
#define PyFrame_Check(op) Py_IS_TYPE((op), &PyFrame_Type)
+#define PyFrameLocalsProxy_Check(op) Py_IS_TYPE((op), &PyFrameLocalsProxy_Type)
PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame);
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index e13fdd9..994900c 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -25,7 +25,7 @@ struct _frame {
int f_lineno; /* Current line number. Only valid if non-zero */
char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode trace events? */
- char f_fast_as_locals; /* Have the fast locals of this frame been converted to a dict? */
+ PyObject *f_extra_locals; /* Dict for locals set by users using f_locals, could be NULL */
/* The frame data, if this frame object owns the frame */
PyObject *_f_frame_data[1];
};
@@ -245,14 +245,11 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
int
_PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg);
-PyObject *
-_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden);
-
-int
-_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
+bool
+_PyFrame_HasHiddenLocals(_PyInterpreterFrame *frame);
-void
-_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear);
+PyObject *
+_PyFrame_GetLocals(_PyInterpreterFrame *frame);
static inline bool
_PyThreadState_HasStackSpace(PyThreadState *tstate, int size)
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index 8e744a1..93d0ea8 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -1,3 +1,4 @@
+import copy
import gc
import operator
import re
@@ -13,7 +14,7 @@ except ImportError:
_testcapi = None
from test import support
-from test.support import threading_helper, Py_GIL_DISABLED
+from test.support import import_helper, threading_helper, Py_GIL_DISABLED
from test.support.script_helper import assert_python_ok
@@ -198,14 +199,6 @@ class FrameAttrsTest(unittest.TestCase):
tb = tb.tb_next
return frames
- def test_locals(self):
- f, outer, inner = self.make_frames()
- outer_locals = outer.f_locals
- self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType)
- self.assertEqual(outer_locals, {'x': 5, 'y': 6})
- inner_locals = inner.f_locals
- self.assertEqual(inner_locals, {'x': 5, 'z': 7})
-
def test_clear_locals(self):
# Test f_locals after clear() (issue #21897)
f, outer, inner = self.make_frames()
@@ -217,8 +210,8 @@ class FrameAttrsTest(unittest.TestCase):
def test_locals_clear_locals(self):
# Test f_locals before and after clear() (to exercise caching)
f, outer, inner = self.make_frames()
- outer.f_locals
- inner.f_locals
+ self.assertNotEqual(outer.f_locals, {})
+ self.assertNotEqual(inner.f_locals, {})
outer.clear()
inner.clear()
self.assertEqual(outer.f_locals, {})
@@ -269,6 +262,177 @@ class ReprTest(unittest.TestCase):
r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$"
% (file_repr, offset + 5))
+class TestFrameLocals(unittest.TestCase):
+ def test_scope(self):
+ class A:
+ x = 1
+ sys._getframe().f_locals['x'] = 2
+ sys._getframe().f_locals['y'] = 2
+
+ self.assertEqual(A.x, 2)
+ self.assertEqual(A.y, 2)
+
+ def f():
+ x = 1
+ sys._getframe().f_locals['x'] = 2
+ sys._getframe().f_locals['y'] = 2
+ self.assertEqual(x, 2)
+ self.assertEqual(locals()['y'], 2)
+ f()
+
+ def test_closure(self):
+ x = 1
+ y = 2
+
+ def f():
+ z = x + y
+ d = sys._getframe().f_locals
+ self.assertEqual(d['x'], 1)
+ self.assertEqual(d['y'], 2)
+ d['x'] = 2
+ d['y'] = 3
+
+ f()
+ self.assertEqual(x, 2)
+ self.assertEqual(y, 3)
+
+ def test_as_dict(self):
+ x = 1
+ y = 2
+ d = sys._getframe().f_locals
+ # self, x, y, d
+ self.assertEqual(len(d), 4)
+ self.assertIs(d['d'], d)
+ self.assertEqual(set(d.keys()), set(['x', 'y', 'd', 'self']))
+ self.assertEqual(len(d.values()), 4)
+ self.assertIn(1, d.values())
+ self.assertEqual(len(d.items()), 4)
+ self.assertIn(('x', 1), d.items())
+ self.assertEqual(d.__getitem__('x'), 1)
+ d.__setitem__('x', 2)
+ self.assertEqual(d['x'], 2)
+ self.assertEqual(d.get('x'), 2)
+ self.assertIs(d.get('non_exist', None), None)
+ self.assertEqual(d.__len__(), 4)
+ self.assertEqual(set([key for key in d]), set(['x', 'y', 'd', 'self']))
+ self.assertIn('x', d)
+ self.assertTrue(d.__contains__('x'))
+
+ self.assertEqual(reversed(d), list(reversed(d.keys())))
+
+ d.update({'x': 3, 'z': 4})
+ self.assertEqual(d['x'], 3)
+ self.assertEqual(d['z'], 4)
+
+ with self.assertRaises(TypeError):
+ d.update([1, 2])
+
+ self.assertEqual(d.setdefault('x', 5), 3)
+ self.assertEqual(d.setdefault('new', 5), 5)
+ self.assertEqual(d['new'], 5)
+
+ with self.assertRaises(KeyError):
+ d['non_exist']
+
+ def test_as_number(self):
+ x = 1
+ y = 2
+ d = sys._getframe().f_locals
+ self.assertIn('z', d | {'z': 3})
+ d |= {'z': 3}
+ self.assertEqual(d['z'], 3)
+ d |= {'y': 3}
+ self.assertEqual(d['y'], 3)
+ with self.assertRaises(TypeError):
+ d |= 3
+ with self.assertRaises(TypeError):
+ _ = d | [3]
+
+ def test_non_string_key(self):
+ d = sys._getframe().f_locals
+ d[1] = 2
+ self.assertEqual(d[1], 2)
+
+ def test_write_with_hidden(self):
+ def f():
+ f_locals = [sys._getframe().f_locals for b in [0]][0]
+ f_locals['b'] = 2
+ f_locals['c'] = 3
+ self.assertEqual(b, 2)
+ self.assertEqual(c, 3)
+ b = 0
+ c = 0
+ f()
+
+ def test_repr(self):
+ x = 1
+ # Introduce a reference cycle
+ frame = sys._getframe()
+ self.assertEqual(repr(frame.f_locals), repr(dict(frame.f_locals)))
+
+ def test_delete(self):
+ x = 1
+ d = sys._getframe().f_locals
+ with self.assertRaises(TypeError):
+ del d['x']
+
+ with self.assertRaises(AttributeError):
+ d.clear()
+
+ with self.assertRaises(AttributeError):
+ d.pop('x')
+
+ @support.cpython_only
+ def test_sizeof(self):
+ proxy = sys._getframe().f_locals
+ support.check_sizeof(self, proxy, support.calcobjsize("P"))
+
+ def test_unsupport(self):
+ x = 1
+ d = sys._getframe().f_locals
+ with self.assertRaises(AttributeError):
+ d.copy()
+
+ with self.assertRaises(TypeError):
+ copy.copy(d)
+
+ with self.assertRaises(TypeError):
+ copy.deepcopy(d)
+
+
+class TestFrameCApi(unittest.TestCase):
+ def test_basic(self):
+ x = 1
+ ctypes = import_helper.import_module('ctypes')
+ PyEval_GetFrameLocals = ctypes.pythonapi.PyEval_GetFrameLocals
+ PyEval_GetFrameLocals.restype = ctypes.py_object
+ frame_locals = PyEval_GetFrameLocals()
+ self.assertTrue(type(frame_locals), dict)
+ self.assertEqual(frame_locals['x'], 1)
+ frame_locals['x'] = 2
+ self.assertEqual(x, 1)
+
+ PyEval_GetFrameGlobals = ctypes.pythonapi.PyEval_GetFrameGlobals
+ PyEval_GetFrameGlobals.restype = ctypes.py_object
+ frame_globals = PyEval_GetFrameGlobals()
+ self.assertTrue(type(frame_globals), dict)
+ self.assertIs(frame_globals, globals())
+
+ PyEval_GetFrameBuiltins = ctypes.pythonapi.PyEval_GetFrameBuiltins
+ PyEval_GetFrameBuiltins.restype = ctypes.py_object
+ frame_builtins = PyEval_GetFrameBuiltins()
+ self.assertEqual(frame_builtins, __builtins__)
+
+ PyFrame_GetLocals = ctypes.pythonapi.PyFrame_GetLocals
+ PyFrame_GetLocals.argtypes = [ctypes.py_object]
+ PyFrame_GetLocals.restype = ctypes.py_object
+ frame = sys._getframe()
+ f_locals = PyFrame_GetLocals(frame)
+ self.assertTrue(f_locals['x'], 1)
+ f_locals['x'] = 2
+ self.assertEqual(x, 2)
+
+
class TestIncompleteFrameAreInvisible(unittest.TestCase):
def test_issue95818(self):
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index df1debf..ec2aac8 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -622,9 +622,14 @@ class ListComprehensionTest(unittest.TestCase):
def test_frame_locals(self):
code = """
- val = [sys._getframe().f_locals for a in [0]][0]["a"]
+ val = "a" in [sys._getframe().f_locals for a in [0]][0]
"""
import sys
+ self._check_in_scopes(code, {"val": False}, ns={"sys": sys})
+
+ code = """
+ val = [sys._getframe().f_locals["a"] for a in [0]][0]
+ """
self._check_in_scopes(code, {"val": 0}, ns={"sys": sys})
def _recursive_replace(self, maybe_code):
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 6989aaf..6c27ee4 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -933,23 +933,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
return f
- def test_deleting_local_warns_and_assigns_none(self):
- f = self.make_function_with_no_checks()
- co_code = f.__code__.co_code
- def trace(frame, event, arg):
- if event == 'line' and frame.f_lineno == 4:
- del frame.f_locals["x"]
- sys.settrace(None)
- return None
- return trace
- e = r"assigning None to unbound local 'x'"
- with self.assertWarnsRegex(RuntimeWarning, e):
- sys.settrace(trace)
- f()
- self.assertInBytecode(f, "LOAD_FAST")
- self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
- self.assertEqual(f.__code__.co_code, co_code)
-
def test_modifying_local_does_not_add_check(self):
f = self.make_function_with_no_checks()
def trace(frame, event, arg):
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index d226981..c06c285 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -227,6 +227,9 @@ SYMBOL_NAMES = (
"PyEval_EvalFrameEx",
"PyEval_GetBuiltins",
"PyEval_GetFrame",
+ "PyEval_GetFrameBuiltins",
+ "PyEval_GetFrameGlobals",
+ "PyEval_GetFrameLocals",
"PyEval_GetFuncDesc",
"PyEval_GetFuncName",
"PyEval_GetGlobals",
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 7391276..944e84e 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1561,7 +1561,7 @@ class SizeofTest(unittest.TestCase):
def func():
return sys._getframe()
x = func()
- check(x, size('3Pi3c7P2ic??2P'))
+ check(x, size('3Pi2cP7P2ic??2P'))
# function
def func(): pass
check(func, size('15Pi'))
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst
new file mode 100644
index 0000000..46e628f
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst
@@ -0,0 +1 @@
+Implement PEP 667 - converted ``frame.f_locals`` to a write through proxy
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 5c29e98..7747366 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2501,3 +2501,9 @@
added = '3.13'
[function.PyType_GetModuleByDef]
added = '3.13'
+[function.PyEval_GetFrameBuiltins]
+ added = '3.13'
+[function.PyEval_GetFrameGlobals]
+ added = '3.13'
+[function.PyEval_GetFrameLocals]
+ added = '3.13'
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 36538b1..8030ecb 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -16,12 +16,633 @@
#define OFF(x) offsetof(PyFrameObject, x)
+
+// Returns borrowed reference or NULL
+static PyObject *
+framelocalsproxy_getval(_PyInterpreterFrame *frame, PyCodeObject *co, int i)
+{
+ PyObject **fast = _PyFrame_GetLocalsArray(frame);
+ _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+
+ PyObject *value = fast[i];
+ PyObject *cell = NULL;
+
+ if (value == NULL) {
+ return NULL;
+ }
+
+ if (kind == CO_FAST_FREE || kind & CO_FAST_CELL) {
+ // The cell was set when the frame was created from
+ // the function's closure.
+ assert(PyCell_Check(value));
+ cell = value;
+ }
+
+ if (cell != NULL) {
+ value = PyCell_GET(cell);
+ }
+
+ if (value == NULL) {
+ return NULL;
+ }
+
+ return value;
+}
+
+static int
+framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject* key, bool read)
+{
+ /*
+ * Returns the fast locals index of the key
+ * - if read == true, returns the index if the value is not NULL
+ * - if read == false, returns the index if the value is not hidden
+ */
+
+ assert(PyUnicode_CheckExact(key));
+
+ PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+ int found_key = false;
+
+ // We do 2 loops here because it's highly possible the key is interned
+ // and we can do a pointer comparison.
+ for (int i = 0; i < co->co_nlocalsplus; i++) {
+ PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+ if (name == key) {
+ found_key = true;
+ if (read) {
+ if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) {
+ return i;
+ }
+ } else {
+ if (!(_PyLocals_GetKind(co->co_localspluskinds, i) & CO_FAST_HIDDEN)) {
+ return i;
+ }
+ }
+ }
+ }
+
+ if (!found_key) {
+ // This is unlikely, but we need to make sure. This means the key
+ // is not interned.
+ for (int i = 0; i < co->co_nlocalsplus; i++) {
+ PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+ if (_PyUnicode_EQ(name, key)) {
+ if (read) {
+ if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) {
+ return i;
+ }
+ } else {
+ if (!(_PyLocals_GetKind(co->co_localspluskinds, i) & CO_FAST_HIDDEN)) {
+ return i;
+ }
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+static PyObject *
+framelocalsproxy_getitem(PyObject *self, PyObject *key)
+{
+ PyFrameObject* frame = ((PyFrameLocalsProxyObject*)self)->frame;
+ PyCodeObject* co = _PyFrame_GetCode(frame->f_frame);
+
+ if (PyUnicode_CheckExact(key)) {
+ int i = framelocalsproxy_getkeyindex(frame, key, true);
+ if (i >= 0) {
+ PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
+ assert(value != NULL);
+ return Py_NewRef(value);
+ }
+ }
+
+ // Okay not in the fast locals, try extra locals
+
+ PyObject *extra = frame->f_extra_locals;
+ if (extra != NULL) {
+ PyObject *value = PyDict_GetItem(extra, key);
+ if (value != NULL) {
+ return Py_NewRef(value);
+ }
+ }
+
+ PyErr_Format(PyExc_KeyError, "local variable '%R' is not defined", key);
+ return NULL;
+}
+
+static int
+framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
+{
+ /* Merge locals into fast locals */
+ PyFrameObject* frame = ((PyFrameLocalsProxyObject*)self)->frame;
+ PyObject** fast = _PyFrame_GetLocalsArray(frame->f_frame);
+ PyCodeObject* co = _PyFrame_GetCode(frame->f_frame);
+
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError, "cannot remove variables from FrameLocalsProxy");
+ return -1;
+ }
+
+ if (PyUnicode_CheckExact(key)) {
+ int i = framelocalsproxy_getkeyindex(frame, key, false);
+ if (i >= 0) {
+ _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+
+ PyObject *oldvalue = fast[i];
+ PyObject *cell = NULL;
+ if (kind == CO_FAST_FREE) {
+ // The cell was set when the frame was created from
+ // the function's closure.
+ assert(oldvalue != NULL && PyCell_Check(oldvalue));
+ cell = oldvalue;
+ } else if (kind & CO_FAST_CELL && oldvalue != NULL) {
+ if (PyCell_Check(oldvalue)) {
+ cell = oldvalue;
+ }
+ }
+ if (cell != NULL) {
+ oldvalue = PyCell_GET(cell);
+ if (value != oldvalue) {
+ PyCell_SET(cell, Py_XNewRef(value));
+ Py_XDECREF(oldvalue);
+ }
+ } else if (value != oldvalue) {
+ Py_XSETREF(fast[i], Py_NewRef(value));
+ }
+ Py_XDECREF(value);
+ return 0;
+ }
+ }
+
+ // Okay not in the fast locals, try extra locals
+
+ PyObject *extra = frame->f_extra_locals;
+
+ if (extra == NULL) {
+ extra = PyDict_New();
+ if (extra == NULL) {
+ return -1;
+ }
+ frame->f_extra_locals = extra;
+ }
+
+ assert(PyDict_Check(extra));
+
+ return PyDict_SetItem(extra, key, value) < 0;
+}
+
+static int
+framelocalsproxy_merge(PyObject* self, PyObject* other)
+{
+ if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
+ return -1;
+ }
+
+ PyObject *keys = PyMapping_Keys(other);
+ PyObject *iter = NULL;
+ PyObject *key = NULL;
+ PyObject *value = NULL;
+
+ assert(keys != NULL);
+
+ iter = PyObject_GetIter(keys);
+ Py_DECREF(keys);
+
+ if (iter == NULL) {
+ return -1;
+ }
+
+ while ((key = PyIter_Next(iter)) != NULL) {
+ value = PyObject_GetItem(other, key);
+ if (value == NULL) {
+ Py_DECREF(key);
+ Py_DECREF(iter);
+ return -1;
+ }
+
+ if (framelocalsproxy_setitem(self, key, value) < 0) {
+ Py_DECREF(key);
+ Py_DECREF(value);
+ Py_DECREF(iter);
+ return -1;
+ }
+
+ Py_DECREF(key);
+ Py_DECREF(value);
+ }
+
+ return 0;
+}
+
+static PyObject *
+framelocalsproxy_keys(PyObject *self, PyObject *__unused)
+{
+ PyObject *names = PyList_New(0);
+ PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+ PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+
+ for (int i = 0; i < co->co_nlocalsplus; i++) {
+ PyObject *val = framelocalsproxy_getval(frame->f_frame, co, i);
+ if (val) {
+ PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+ PyList_Append(names, name);
+ }
+ }
+
+ // Iterate through the extra locals
+ Py_ssize_t i = 0;
+ PyObject *key = NULL;
+ PyObject *value = NULL;
+
+ if (frame->f_extra_locals) {
+ assert(PyDict_Check(frame->f_extra_locals));
+ while (PyDict_Next(frame->f_extra_locals, &i, &key, &value)) {
+ PyList_Append(names, key);
+ }
+ }
+
+ return names;
+}
+
+static void
+framelocalsproxy_dealloc(PyObject *self)
+{
+ PyObject_GC_UnTrack(self);
+ Py_CLEAR(((PyFrameLocalsProxyObject*)self)->frame);
+ Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+framelocalsproxy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyFrameLocalsProxyObject *self = (PyFrameLocalsProxyObject *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ PyFrameObject *frame = (PyFrameObject*)PyTuple_GET_ITEM(args, 0);
+ assert(PyFrame_Check(frame));
+
+ ((PyFrameLocalsProxyObject*)self)->frame = (PyFrameObject*)Py_NewRef(frame);
+
+ return (PyObject *)self;
+}
+
+static int
+framelocalsproxy_tp_clear(PyObject *self)
+{
+ Py_CLEAR(((PyFrameLocalsProxyObject*)self)->frame);
+ return 0;
+}
+
+static int
+framelocalsproxy_visit(PyObject *self, visitproc visit, void *arg)
+{
+ Py_VISIT(((PyFrameLocalsProxyObject*)self)->frame);
+ return 0;
+}
+
+static PyObject *
+framelocalsproxy_iter(PyObject *self)
+{
+ return PyObject_GetIter(framelocalsproxy_keys(self, NULL));
+}
+
+static PyObject *
+framelocalsproxy_richcompare(PyObject *self, PyObject *other, int op)
+{
+ if (PyFrameLocalsProxy_Check(other)) {
+ bool result = ((PyFrameLocalsProxyObject*)self)->frame == ((PyFrameLocalsProxyObject*)other)->frame;
+ if (op == Py_EQ) {
+ return PyBool_FromLong(result);
+ } else if (op == Py_NE) {
+ return PyBool_FromLong(!result);
+ }
+ } else if (PyDict_Check(other)) {
+ PyObject *dct = PyDict_New();
+ PyObject *result = NULL;
+ PyDict_Update(dct, self);
+ result = PyObject_RichCompare(dct, other, op);
+ Py_DECREF(dct);
+ return result;
+ }
+
+ Py_RETURN_NOTIMPLEMENTED;
+}
+
+static PyObject *
+framelocalsproxy_repr(PyObject *self)
+{
+ int i = Py_ReprEnter(self);
+ if (i != 0) {
+ return i > 0 ? PyUnicode_FromString("{...}") : NULL;
+ }
+
+ PyObject *dct = PyDict_New();
+ PyObject *repr = NULL;
+
+ if (PyDict_Update(dct, self) == 0) {
+ repr = PyObject_Repr(dct);
+ }
+ Py_ReprLeave(self);
+
+ Py_DECREF(dct);
+ return repr;
+}
+
+static PyObject*
+framelocalsproxy_or(PyObject *self, PyObject *other)
+{
+ if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
+ Py_RETURN_NOTIMPLEMENTED;
+ }
+
+ PyObject *result = PyDict_New();
+ if (PyDict_Update(result, self) < 0) {
+ Py_DECREF(result);
+ return NULL;
+ }
+
+ if (PyDict_Update(result, other) < 0) {
+ Py_DECREF(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+static PyObject*
+framelocalsproxy_inplace_or(PyObject *self, PyObject *other)
+{
+ if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
+ Py_RETURN_NOTIMPLEMENTED;
+ }
+
+ if (framelocalsproxy_merge(self, other) < 0) {
+ Py_RETURN_NOTIMPLEMENTED;
+ }
+
+ return Py_NewRef(self);
+}
+
+static PyObject*
+framelocalsproxy_values(PyObject *self, PyObject *__unused)
+{
+ PyObject *values = PyList_New(0);
+ PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+ PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+
+ for (int i = 0; i < co->co_nlocalsplus; i++) {
+ PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
+ if (value) {
+ PyList_Append(values, value);
+ }
+ }
+
+ // Iterate through the extra locals
+ Py_ssize_t j = 0;
+ PyObject *key = NULL;
+ PyObject *value = NULL;
+
+ if (frame->f_extra_locals) {
+ while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
+ PyList_Append(values, value);
+ }
+ }
+
+ return values;
+}
+
+static PyObject *
+framelocalsproxy_items(PyObject *self, PyObject *__unused)
+{
+ PyObject *items = PyList_New(0);
+ PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+ PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+
+ for (int i = 0; i < co->co_nlocalsplus; i++) {
+ PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+ PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
+
+ if (value) {
+ PyObject *pair = PyTuple_Pack(2, name, value);
+ PyList_Append(items, pair);
+ Py_DECREF(pair);
+ }
+ }
+
+ // Iterate through the extra locals
+ Py_ssize_t j = 0;
+ PyObject *key = NULL;
+ PyObject *value = NULL;
+
+ if (frame->f_extra_locals) {
+ while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
+ PyObject *pair = PyTuple_Pack(2, key, value);
+ PyList_Append(items, pair);
+ Py_DECREF(pair);
+ }
+ }
+
+ return items;
+}
+
+static Py_ssize_t
+framelocalsproxy_length(PyObject *self)
+{
+ PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+ PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+ Py_ssize_t size = 0;
+
+ if (frame->f_extra_locals != NULL) {
+ assert(PyDict_Check(frame->f_extra_locals));
+ size += PyDict_Size(frame->f_extra_locals);
+ }
+
+ for (int i = 0; i < co->co_nlocalsplus; i++) {
+ if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) {
+ size++;
+ }
+ }
+ return size;
+}
+
+static PyObject*
+framelocalsproxy_contains(PyObject *self, PyObject *key)
+{
+ PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+
+ if (PyUnicode_CheckExact(key)) {
+ int i = framelocalsproxy_getkeyindex(frame, key, true);
+ if (i >= 0) {
+ Py_RETURN_TRUE;
+ }
+ }
+
+ PyObject *extra = ((PyFrameObject*)frame)->f_extra_locals;
+ if (extra != NULL) {
+ int result = PyDict_Contains(extra, key);
+ if (result < 0) {
+ return NULL;
+ } else if (result > 0) {
+ Py_RETURN_TRUE;
+ }
+ }
+
+ Py_RETURN_FALSE;
+}
+
+static PyObject*
+framelocalsproxy_update(PyObject *self, PyObject *other)
+{
+ if (framelocalsproxy_merge(self, other) < 0) {
+ PyErr_SetString(PyExc_TypeError, "update() argument must be dict or another FrameLocalsProxy");
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject*
+framelocalsproxy_get(PyObject* self, PyObject *const *args, Py_ssize_t nargs)
+{
+ if (nargs < 1 || nargs > 2) {
+ PyErr_SetString(PyExc_TypeError, "get expected 1 or 2 arguments");
+ return NULL;
+ }
+
+ PyObject *key = args[0];
+ PyObject *default_value = Py_None;
+
+ if (nargs == 2) {
+ default_value = args[1];
+ }
+
+ PyObject *result = framelocalsproxy_getitem(self, key);
+
+ if (result == NULL) {
+ if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+ PyErr_Clear();
+ return Py_XNewRef(default_value);
+ }
+ return NULL;
+ }
+
+ return result;
+}
+
+static PyObject*
+framelocalsproxy_setdefault(PyObject* self, PyObject *const *args, Py_ssize_t nargs)
+{
+ if (nargs < 1 || nargs > 2) {
+ PyErr_SetString(PyExc_TypeError, "setdefault expected 1 or 2 arguments");
+ return NULL;
+ }
+
+ PyObject *key = args[0];
+ PyObject *default_value = Py_None;
+
+ if (nargs == 2) {
+ default_value = args[1];
+ }
+
+ PyObject *result = framelocalsproxy_getitem(self, key);
+
+ if (result == NULL) {
+ if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+ PyErr_Clear();
+ if (framelocalsproxy_setitem(self, key, default_value) < 0) {
+ return NULL;
+ }
+ return Py_XNewRef(default_value);
+ }
+ return NULL;
+ }
+
+ return result;
+}
+
+static PyObject*
+framelocalsproxy_reversed(PyObject *self, PyObject *__unused)
+{
+ PyObject *result = framelocalsproxy_keys(self, NULL);
+ if (PyList_Reverse(result) < 0) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ return result;
+}
+
+static PyNumberMethods framelocalsproxy_as_number = {
+ .nb_or = framelocalsproxy_or,
+ .nb_inplace_or = framelocalsproxy_inplace_or,
+};
+
+static PyMappingMethods framelocalsproxy_as_mapping = {
+ framelocalsproxy_length, // mp_length
+ framelocalsproxy_getitem, // mp_subscript
+ framelocalsproxy_setitem, // mp_ass_subscript
+};
+
+static PyMethodDef framelocalsproxy_methods[] = {
+ {"__contains__", framelocalsproxy_contains, METH_O | METH_COEXIST,
+ NULL},
+ {"__getitem__", framelocalsproxy_getitem, METH_O | METH_COEXIST,
+ NULL},
+ {"__reversed__", framelocalsproxy_reversed, METH_NOARGS,
+ NULL},
+ {"keys", framelocalsproxy_keys, METH_NOARGS,
+ NULL},
+ {"values", framelocalsproxy_values, METH_NOARGS,
+ NULL},
+ {"items", framelocalsproxy_items, METH_NOARGS,
+ NULL},
+ {"update", framelocalsproxy_update, METH_O,
+ NULL},
+ {"get", _PyCFunction_CAST(framelocalsproxy_get), METH_FASTCALL,
+ NULL},
+ {"setdefault", _PyCFunction_CAST(framelocalsproxy_setdefault), METH_FASTCALL,
+ NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+PyTypeObject PyFrameLocalsProxy_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ .tp_name = "FrameLocalsProxy",
+ .tp_basicsize = sizeof(PyFrameLocalsProxyObject),
+ .tp_dealloc = (destructor)framelocalsproxy_dealloc,
+ .tp_repr = &framelocalsproxy_repr,
+ .tp_as_number = &framelocalsproxy_as_number,
+ .tp_as_mapping = &framelocalsproxy_as_mapping,
+ .tp_getattro = PyObject_GenericGetAttr,
+ .tp_setattro = PyObject_GenericSetAttr,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+ .tp_traverse = framelocalsproxy_visit,
+ .tp_clear = framelocalsproxy_tp_clear,
+ .tp_richcompare = framelocalsproxy_richcompare,
+ .tp_iter = framelocalsproxy_iter,
+ .tp_methods = framelocalsproxy_methods,
+ .tp_alloc = PyType_GenericAlloc,
+ .tp_new = framelocalsproxy_new,
+ .tp_free = PyObject_GC_Del,
+};
+
+PyObject *
+_PyFrameLocalsProxy_New(PyFrameObject *frame)
+{
+ PyObject* args = PyTuple_Pack(1, frame);
+ PyObject* proxy = (PyObject*)framelocalsproxy_new(&PyFrameLocalsProxy_Type, args, NULL);
+ Py_DECREF(args);
+ return proxy;
+}
+
static PyMemberDef frame_memberlist[] = {
{"f_trace_lines", Py_T_BOOL, OFF(f_trace_lines), 0},
{NULL} /* Sentinel */
};
-
static PyObject *
frame_getlocals(PyFrameObject *f, void *closure)
{
@@ -30,11 +651,14 @@ frame_getlocals(PyFrameObject *f, void *closure)
return NULL;
}
assert(!_PyFrame_IsIncomplete(f->f_frame));
- PyObject *locals = _PyFrame_GetLocals(f->f_frame, 1);
- if (locals) {
- f->f_fast_as_locals = 1;
+
+ PyCodeObject *co = _PyFrame_GetCode(f->f_frame);
+
+ if (!(co->co_flags & CO_OPTIMIZED) && !_PyFrame_HasHiddenLocals(f->f_frame)) {
+ return Py_NewRef(f->f_frame->f_locals);
}
- return locals;
+
+ return _PyFrameLocalsProxy_New(f);
}
int
@@ -595,20 +1219,6 @@ first_line_not_before(int *lines, int len, int line)
return result;
}
-static bool
-frame_is_cleared(PyFrameObject *frame)
-{
- assert(!_PyFrame_IsIncomplete(frame->f_frame));
- if (frame->f_frame->stacktop == 0) {
- return true;
- }
- if (frame->f_frame->owner == FRAME_OWNED_BY_GENERATOR) {
- PyGenObject *gen = _PyFrame_GetGenerator(frame->f_frame);
- return gen->gi_frame_state == FRAME_CLEARED;
- }
- return false;
-}
-
static bool frame_is_suspended(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
@@ -900,6 +1510,7 @@ frame_dealloc(PyFrameObject *f)
}
Py_CLEAR(f->f_back);
Py_CLEAR(f->f_trace);
+ Py_CLEAR(f->f_extra_locals);
PyObject_GC_Del(f);
Py_XDECREF(co);
Py_TRASHCAN_END;
@@ -910,6 +1521,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
{
Py_VISIT(f->f_back);
Py_VISIT(f->f_trace);
+ Py_VISIT(f->f_extra_locals);
if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
return 0;
}
@@ -921,6 +1533,7 @@ static int
frame_tp_clear(PyFrameObject *f)
{
Py_CLEAR(f->f_trace);
+ Py_CLEAR(f->f_extra_locals);
/* locals and stack */
PyObject **locals = _PyFrame_GetLocalsArray(f->f_frame);
@@ -1056,8 +1669,8 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
f->f_trace = NULL;
f->f_trace_lines = 1;
f->f_trace_opcodes = 0;
- f->f_fast_as_locals = 0;
f->f_lineno = 0;
+ f->f_extra_locals = NULL;
return f;
}
@@ -1204,103 +1817,45 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
}
-PyObject *
-_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden)
+bool
+_PyFrame_HasHiddenLocals(_PyInterpreterFrame *frame)
{
- /* Merge fast locals into f->f_locals */
- PyObject *locals = frame->f_locals;
- if (locals == NULL) {
- locals = frame->f_locals = PyDict_New();
- if (locals == NULL) {
- return NULL;
- }
- }
- PyObject *hidden = NULL;
-
- /* If include_hidden, "hidden" fast locals (from inlined comprehensions in
- module/class scopes) will be included in the returned dict, but not in
- frame->f_locals; the returned dict will be a modified copy. Non-hidden
- locals will still be updated in frame->f_locals. */
- if (include_hidden) {
- hidden = PyDict_New();
- if (hidden == NULL) {
- return NULL;
- }
- }
-
- frame_init_get_vars(frame);
+ /*
+ * This function returns if there are hidden locals introduced by PEP 709,
+ * which are the isolated fast locals for inline comprehensions
+ */
+ PyCodeObject* co = _PyFrame_GetCode(frame);
- PyCodeObject *co = _PyFrame_GetCode(frame);
for (int i = 0; i < co->co_nlocalsplus; i++) {
- PyObject *value; // borrowed reference
- if (!frame_get_var(frame, co, i, &value)) {
- continue;
- }
-
- PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+
if (kind & CO_FAST_HIDDEN) {
- if (include_hidden && value != NULL) {
- if (PyObject_SetItem(hidden, name, value) != 0) {
- goto error;
- }
- }
- continue;
- }
- if (value == NULL) {
- if (PyObject_DelItem(locals, name) != 0) {
- if (PyErr_ExceptionMatches(PyExc_KeyError)) {
- PyErr_Clear();
- }
- else {
- goto error;
- }
- }
- }
- else {
- if (PyObject_SetItem(locals, name, value) != 0) {
- goto error;
- }
- }
- }
+ PyObject* value = framelocalsproxy_getval(frame, co, i);
- if (include_hidden && PyDict_Size(hidden)) {
- PyObject *innerlocals = PyDict_New();
- if (innerlocals == NULL) {
- goto error;
- }
- if (PyDict_Merge(innerlocals, locals, 1) != 0) {
- Py_DECREF(innerlocals);
- goto error;
- }
- if (PyDict_Merge(innerlocals, hidden, 1) != 0) {
- Py_DECREF(innerlocals);
- goto error;
+ if (value != NULL) {
+ return true;
+ }
}
- locals = innerlocals;
- }
- else {
- Py_INCREF(locals);
}
- Py_CLEAR(hidden);
- return locals;
-
- error:
- Py_XDECREF(hidden);
- return NULL;
+ return false;
}
-int
-_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
+PyObject *
+_PyFrame_GetLocals(_PyInterpreterFrame *frame)
{
- PyObject *locals = _PyFrame_GetLocals(frame, 0);
- if (locals == NULL) {
- return -1;
+ // We should try to avoid creating the FrameObject if possible.
+ // So we check if the frame is a module or class level scope
+ PyCodeObject *co = _PyFrame_GetCode(frame);
+
+ if (!(co->co_flags & CO_OPTIMIZED) && !_PyFrame_HasHiddenLocals(frame)) {
+ return Py_NewRef(frame->f_locals);
}
- Py_DECREF(locals);
- return 0;
+
+ PyFrameObject* f = _PyFrame_GetFrameObject(frame);
+
+ return _PyFrameLocalsProxy_New(f);
}
@@ -1354,112 +1909,19 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name)
int
PyFrame_FastToLocalsWithError(PyFrameObject *f)
{
- if (f == NULL) {
- PyErr_BadInternalCall();
- return -1;
- }
- assert(!_PyFrame_IsIncomplete(f->f_frame));
- int err = _PyFrame_FastToLocalsWithError(f->f_frame);
- if (err == 0) {
- f->f_fast_as_locals = 1;
- }
- return err;
+ return 0;
}
void
PyFrame_FastToLocals(PyFrameObject *f)
{
- int res;
- assert(!_PyFrame_IsIncomplete(f->f_frame));
- assert(!PyErr_Occurred());
-
- res = PyFrame_FastToLocalsWithError(f);
- if (res < 0)
- PyErr_Clear();
-}
-
-void
-_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
-{
- /* Merge locals into fast locals */
- PyObject *locals;
- PyObject **fast;
- PyCodeObject *co;
- locals = frame->f_locals;
- if (locals == NULL) {
- return;
- }
- fast = _PyFrame_GetLocalsArray(frame);
- co = _PyFrame_GetCode(frame);
-
- PyObject *exc = PyErr_GetRaisedException();
- for (int i = 0; i < co->co_nlocalsplus; i++) {
- _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
-
- /* Same test as in PyFrame_FastToLocals() above. */
- if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED)) {
- continue;
- }
- PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
- PyObject *value = PyObject_GetItem(locals, name);
- /* We only care about NULLs if clear is true. */
- if (value == NULL) {
- PyErr_Clear();
- if (!clear) {
- continue;
- }
- }
- PyObject *oldvalue = fast[i];
- PyObject *cell = NULL;
- if (kind == CO_FAST_FREE) {
- // The cell was set when the frame was created from
- // the function's closure.
- assert(oldvalue != NULL && PyCell_Check(oldvalue));
- cell = oldvalue;
- }
- else if (kind & CO_FAST_CELL && oldvalue != NULL) {
- /* Same test as in PyFrame_FastToLocals() above. */
- if (PyCell_Check(oldvalue) &&
- _PyFrame_OpAlreadyRan(frame, MAKE_CELL, i)) {
- // (likely) MAKE_CELL must have executed already.
- cell = oldvalue;
- }
- // (unlikely) Otherwise, it must have been set to some
- // initial value by an earlier call to PyFrame_LocalsToFast().
- }
- if (cell != NULL) {
- oldvalue = PyCell_GET(cell);
- if (value != oldvalue) {
- PyCell_SET(cell, Py_XNewRef(value));
- Py_XDECREF(oldvalue);
- }
- }
- else if (value != oldvalue) {
- if (value == NULL) {
- // Probably can't delete this, since the compiler's flow
- // analysis may have already "proven" that it exists here:
- const char *e = "assigning None to unbound local %R";
- if (PyErr_WarnFormat(PyExc_RuntimeWarning, 0, e, name)) {
- // It's okay if frame_obj is NULL, just try anyways:
- PyErr_WriteUnraisable((PyObject *)frame->frame_obj);
- }
- value = Py_NewRef(Py_None);
- }
- Py_XSETREF(fast[i], Py_NewRef(value));
- }
- Py_XDECREF(value);
- }
- PyErr_SetRaisedException(exc);
+ return;
}
void
PyFrame_LocalsToFast(PyFrameObject *f, int clear)
{
- assert(!_PyFrame_IsIncomplete(f->f_frame));
- if (f && f->f_fast_as_locals && !frame_is_cleared(f)) {
- _PyFrame_LocalsToFast(f->f_frame, clear);
- f->f_fast_as_locals = 0;
- }
+ return;
}
int
diff --git a/Objects/object.c b/Objects/object.c
index 79e4fb4..1bf0e65 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2235,6 +2235,7 @@ static PyTypeObject* static_types[] = {
&PyFilter_Type,
&PyFloat_Type,
&PyFrame_Type,
+ &PyFrameLocalsProxy_Type,
&PyFrozenSet_Type,
&PyFunction_Type,
&PyGen_Type,
diff --git a/PC/python3dll.c b/PC/python3dll.c
index c6fdc0b..86c8884 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -253,6 +253,9 @@ EXPORT_FUNC(PyEval_EvalFrame)
EXPORT_FUNC(PyEval_EvalFrameEx)
EXPORT_FUNC(PyEval_GetBuiltins)
EXPORT_FUNC(PyEval_GetFrame)
+EXPORT_FUNC(PyEval_GetFrameBuiltins)
+EXPORT_FUNC(PyEval_GetFrameGlobals)
+EXPORT_FUNC(PyEval_GetFrameLocals)
EXPORT_FUNC(PyEval_GetFuncDesc)
EXPORT_FUNC(PyEval_GetFuncName)
EXPORT_FUNC(PyEval_GetGlobals)
diff --git a/Python/ceval.c b/Python/ceval.c
index 3626ffb..0d02a98 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2475,12 +2475,7 @@ PyEval_GetLocals(void)
return NULL;
}
- if (_PyFrame_FastToLocalsWithError(current_frame) < 0) {
- return NULL;
- }
-
- PyObject *locals = current_frame->f_locals;
- assert(locals != NULL);
+ PyObject *locals = _PyEval_GetFrameLocals();
return locals;
}
@@ -2494,7 +2489,24 @@ _PyEval_GetFrameLocals(void)
return NULL;
}
- return _PyFrame_GetLocals(current_frame, 1);
+ PyObject *locals = _PyFrame_GetLocals(current_frame);
+ if (locals == NULL) {
+ return NULL;
+ }
+
+ if (PyFrameLocalsProxy_Check(locals)) {
+ PyObject* ret = PyDict_New();
+ if (PyDict_Update(ret, locals)) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ Py_DECREF(locals);
+ return ret;
+ } else if (PyMapping_Check(locals)) {
+ return locals;
+ }
+
+ return NULL;
}
PyObject *
@@ -2508,6 +2520,28 @@ PyEval_GetGlobals(void)
return current_frame->f_globals;
}
+PyObject*
+PyEval_GetFrameLocals(void)
+{
+ return _PyEval_GetFrameLocals();
+}
+
+PyObject* PyEval_GetFrameGlobals(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
+ if (current_frame == NULL) {
+ return NULL;
+ }
+ return Py_XNewRef(current_frame->f_globals);
+}
+
+PyObject* PyEval_GetFrameBuiltins(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ return Py_XNewRef(_PyEval_GetBuiltins(tstate));
+}
+
int
PyEval_MergeCompilerFlags(PyCompilerFlags *cf)
{
diff --git a/Python/intrinsics.c b/Python/intrinsics.c
index 5b10c3c..a6b2c10 100644
--- a/Python/intrinsics.c
+++ b/Python/intrinsics.c
@@ -123,18 +123,15 @@ static PyObject *
import_star(PyThreadState* tstate, PyObject *from)
{
_PyInterpreterFrame *frame = tstate->current_frame;
- if (_PyFrame_FastToLocalsWithError(frame) < 0) {
- return NULL;
- }
- PyObject *locals = frame->f_locals;
+ PyObject *locals = _PyFrame_GetLocals(frame);
if (locals == NULL) {
_PyErr_SetString(tstate, PyExc_SystemError,
"no locals found during 'import *'");
return NULL;
}
int err = import_all_from(tstate, locals, from);
- _PyFrame_LocalsToFast(frame, 0);
+ Py_DECREF(locals);
if (err < 0) {
return NULL;
}
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 645b76fc..bd7f821 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -1022,13 +1022,6 @@ static PyObject *
call_trampoline(PyThreadState *tstate, PyObject* callback,
PyFrameObject *frame, int what, PyObject *arg)
{
- /* Discard any previous modifications the frame's fast locals */
- if (frame->f_fast_as_locals) {
- if (PyFrame_FastToLocalsWithError(frame) < 0) {
- return NULL;
- }
- }
-
/* call the Python-level function */
if (arg == NULL) {
arg = Py_None;
@@ -1036,7 +1029,6 @@ call_trampoline(PyThreadState *tstate, PyObject* callback,
PyObject *args[3] = {(PyObject *)frame, whatstrings[what], arg};
PyObject *result = _PyObject_VectorcallTstate(tstate, callback, args, 3, NULL);
- PyFrame_LocalsToFast(frame, 1);
return result;
}
diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv
index 285129f..1b8cccf 100644
--- a/Tools/c-analyzer/cpython/globals-to-fix.tsv
+++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv
@@ -43,6 +43,7 @@ Objects/enumobject.c - PyReversed_Type -
Objects/fileobject.c - PyStdPrinter_Type -
Objects/floatobject.c - PyFloat_Type -
Objects/frameobject.c - PyFrame_Type -
+Objects/frameobject.c - PyFrameLocalsProxy_Type -
Objects/funcobject.c - PyClassMethod_Type -
Objects/funcobject.c - PyFunction_Type -
Objects/funcobject.c - PyStaticMethod_Type -