+import copy
import gc
import operator
import re
_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
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()
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, {})
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):
#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)
{
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
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));
}
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;
{
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;
}
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);
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;
}
}
-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);
}
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