gh-74929: Implement PEP 667 (GH-115153)

This commit is contained in:
Tian Gao 2024-05-04 04:12:10 -07:00 committed by GitHub
parent 1ab6356ebe
commit b034f14a4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 921 additions and 257 deletions

View File

@ -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,,

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);
bool
_PyFrame_HasHiddenLocals(_PyInterpreterFrame *frame);
PyObject *
_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden);
int
_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
void
_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear);
_PyFrame_GetLocals(_PyInterpreterFrame *frame);
static inline bool
_PyThreadState_HasStackSpace(PyThreadState *tstate, int size)

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -227,6 +227,9 @@ SYMBOL_NAMES = (
"PyEval_EvalFrameEx",
"PyEval_GetBuiltins",
"PyEval_GetFrame",
"PyEval_GetFrameBuiltins",
"PyEval_GetFrameGlobals",
"PyEval_GetFrameLocals",
"PyEval_GetFuncDesc",
"PyEval_GetFuncName",
"PyEval_GetGlobals",

View File

@ -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'))

View File

@ -0,0 +1 @@
Implement PEP 667 - converted ``frame.f_locals`` to a write through proxy

View File

@ -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'

View File

@ -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;
/*
* 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);
/* 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);
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 (value != NULL) {
return true;
}
}
}
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;
}
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

View File

@ -2235,6 +2235,7 @@ static PyTypeObject* static_types[] = {
&PyFilter_Type,
&PyFloat_Type,
&PyFrame_Type,
&PyFrameLocalsProxy_Type,
&PyFrozenSet_Type,
&PyFunction_Type,
&PyGen_Type,

3
PC/python3dll.c generated
View File

@ -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)

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 -

Can't render this file because it has a wrong number of fields in line 4.