cpython/Objects/frameobject.c
mpage 2e95c5ba3b
gh-115999: Implement thread-local bytecode and enable specialization for BINARY_OP (#123926)
Each thread specializes a thread-local copy of the bytecode, created on the first RESUME, in free-threaded builds. All copies of the bytecode for a code object are stored in the co_tlbc array on the code object. Threads reserve a globally unique index identifying its copy of the bytecode in all co_tlbc arrays at thread creation and release the index at thread destruction. The first entry in every co_tlbc array always points to the "main" copy of the bytecode that is stored at the end of the code object. This ensures that no bytecode is copied for programs that do not use threads.

Thread-local bytecode can be disabled at runtime by providing either -X tlbc=0 or PYTHON_TLBC=0. Disabling thread-local bytecode also disables specialization.

Concurrent modifications to the bytecode made by the specializing interpreter and instrumentation use atomics, with specialization taking care not to overwrite an instruction that was instrumented concurrently.
2024-11-04 11:13:32 -08:00

2209 lines
64 KiB
C

/* Frame object implementation */
#include "Python.h"
#include "pycore_ceval.h" // _PyEval_SetOpcodeTrace()
#include "pycore_code.h" // CO_FAST_LOCAL, etc.
#include "pycore_dict.h" // _PyDict_LoadBuiltinsFromGlobals()
#include "pycore_function.h" // _PyFunction_FromConstructor()
#include "pycore_moduleobject.h" // _PyModule_GetDict()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches
#include "frameobject.h" // PyFrameObject
#include "pycore_frame.h"
#include "opcode.h" // EXTENDED_ARG
#define OFF(x) offsetof(PyFrameObject, x)
// Returns borrowed reference or NULL
static PyObject *
framelocalsproxy_getval(_PyInterpreterFrame *frame, PyCodeObject *co, int i)
{
_PyStackRef *fast = _PyFrame_GetLocalsArray(frame);
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
PyObject *value = PyStackRef_AsPyObjectBorrow(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 -2 (!) if an error occurred; exception will be set.
* Returns the fast locals index of the key on success:
* - if read == true, returns the index if the value is not NULL
* - if read == false, returns the index if the value is not hidden
* Otherwise returns -1.
*/
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
// Ensure that the key is hashable.
Py_hash_t key_hash = PyObject_Hash(key);
if (key_hash == -1) {
return -2;
}
bool found = 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) {
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;
}
}
found = true;
}
}
if (found) {
// This is an attempt to read an unset local variable or
// write to a variable that is hidden from regular write operations
return -1;
}
// 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);
Py_hash_t name_hash = PyObject_Hash(name);
assert(name_hash != -1); // keys are exact unicode
if (name_hash != key_hash) {
continue;
}
int same = PyObject_RichCompareBool(name, key, Py_EQ);
if (same < 0) {
return -2;
}
if (same) {
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);
int i = framelocalsproxy_getkeyindex(frame, key, true);
if (i == -2) {
return NULL;
}
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;
_PyStackRef *fast = _PyFrame_GetLocalsArray(frame->f_frame);
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
int i = framelocalsproxy_getkeyindex(frame, key, false);
if (i == -2) {
return -1;
}
if (i >= 0) {
if (value == NULL) {
PyErr_SetString(PyExc_ValueError, "cannot remove local variables from FrameLocalsProxy");
return -1;
}
_Py_Executors_InvalidateDependency(PyInterpreterState_Get(), co, 1);
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
_PyStackRef 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.bits != 0 && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue)));
cell = PyStackRef_AsPyObjectBorrow(oldvalue);
} else if (kind & CO_FAST_CELL && oldvalue.bits != 0) {
PyObject *as_obj = PyStackRef_AsPyObjectBorrow(oldvalue);
if (PyCell_Check(as_obj)) {
cell = as_obj;
}
}
if (cell != NULL) {
PyObject *oldvalue_o = PyCell_GET(cell);
if (value != oldvalue_o) {
PyCell_SET(cell, Py_XNewRef(value));
Py_XDECREF(oldvalue_o);
}
} else if (value != PyStackRef_AsPyObjectBorrow(oldvalue)) {
PyStackRef_XCLOSE(fast[i]);
fast[i] = PyStackRef_FromPyObjectNew(value);
}
return 0;
}
// Okay not in the fast locals, try extra locals
PyObject *extra = frame->f_extra_locals;
if (extra == NULL) {
if (value == NULL) {
_PyErr_SetKeyError(key);
return -1;
}
extra = PyDict_New();
if (extra == NULL) {
return -1;
}
frame->f_extra_locals = extra;
}
assert(PyDict_Check(extra));
if (value == NULL) {
return PyDict_DelItem(extra, key);
} else {
return PyDict_SetItem(extra, key, value);
}
}
static int
framelocalsproxy_merge(PyObject* self, PyObject* other)
{
if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
return -1;
}
PyObject *keys = PyMapping_Keys(other);
if (keys == NULL) {
return -1;
}
PyObject *iter = PyObject_GetIter(keys);
Py_DECREF(keys);
if (iter == NULL) {
return -1;
}
PyObject *key = NULL;
PyObject *value = NULL;
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);
}
Py_DECREF(iter);
return 0;
}
static PyObject *
framelocalsproxy_keys(PyObject *self, void *Py_UNUSED(ignored))
{
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
PyObject *names = PyList_New(0);
if (names == NULL) {
return NULL;
}
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);
if (PyList_Append(names, name) < 0) {
Py_DECREF(names);
return NULL;
}
}
}
// Iterate through the extra locals
if (frame->f_extra_locals) {
assert(PyDict_Check(frame->f_extra_locals));
Py_ssize_t i = 0;
PyObject *key = NULL;
PyObject *value = NULL;
while (PyDict_Next(frame->f_extra_locals, &i, &key, &value)) {
if (PyList_Append(names, key) < 0) {
Py_DECREF(names);
return NULL;
}
}
}
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)
{
if (PyTuple_GET_SIZE(args) != 1) {
PyErr_Format(PyExc_TypeError,
"FrameLocalsProxy expected 1 argument, got %zd",
PyTuple_GET_SIZE(args));
return NULL;
}
PyObject *item = PyTuple_GET_ITEM(args, 0);
if (!PyFrame_Check(item)) {
PyErr_Format(PyExc_TypeError, "expect frame, not %T", item);
return NULL;
}
PyFrameObject *frame = (PyFrameObject*)item;
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_SetString(PyExc_TypeError,
"FrameLocalsProxy takes no keyword arguments");
return 0;
}
PyFrameLocalsProxyObject *self = (PyFrameLocalsProxyObject *)type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
((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)
{
PyObject* keys = framelocalsproxy_keys(self, NULL);
if (keys == NULL) {
return NULL;
}
PyObject* iter = PyObject_GetIter(keys);
Py_XDECREF(keys);
return iter;
}
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();
if (dct == NULL) {
return NULL;
}
if (PyDict_Update(dct, self) < 0) {
Py_DECREF(dct);
return NULL;
}
PyObject *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();
if (dct == NULL) {
Py_ReprLeave(self);
return NULL;
}
if (PyDict_Update(dct, self) < 0) {
Py_DECREF(dct);
Py_ReprLeave(self);
return NULL;
}
PyObject *repr = PyObject_Repr(dct);
Py_DECREF(dct);
Py_ReprLeave(self);
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 (result == NULL) {
return NULL;
}
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, void *Py_UNUSED(ignored))
{
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
PyObject *values = PyList_New(0);
if (values == NULL) {
return NULL;
}
for (int i = 0; i < co->co_nlocalsplus; i++) {
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
if (value) {
if (PyList_Append(values, value) < 0) {
Py_DECREF(values);
return NULL;
}
}
}
// Iterate through the extra locals
if (frame->f_extra_locals) {
Py_ssize_t j = 0;
PyObject *key = NULL;
PyObject *value = NULL;
while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
if (PyList_Append(values, value) < 0) {
Py_DECREF(values);
return NULL;
}
}
}
return values;
}
static PyObject *
framelocalsproxy_items(PyObject *self, void *Py_UNUSED(ignored))
{
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
PyObject *items = PyList_New(0);
if (items == NULL) {
return NULL;
}
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);
if (pair == NULL) {
Py_DECREF(items);
return NULL;
}
if (PyList_Append(items, pair) < 0) {
Py_DECREF(items);
Py_DECREF(pair);
return NULL;
}
Py_DECREF(pair);
}
}
// Iterate through the extra locals
if (frame->f_extra_locals) {
Py_ssize_t j = 0;
PyObject *key = NULL;
PyObject *value = NULL;
while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
PyObject *pair = PyTuple_Pack(2, key, value);
if (pair == NULL) {
Py_DECREF(items);
return NULL;
}
if (PyList_Append(items, pair) < 0) {
Py_DECREF(items);
Py_DECREF(pair);
return NULL;
}
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 int
framelocalsproxy_contains(PyObject *self, PyObject *key)
{
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
int i = framelocalsproxy_getkeyindex(frame, key, true);
if (i == -2) {
return -1;
}
if (i >= 0) {
return 1;
}
PyObject *extra = ((PyFrameObject*)frame)->f_extra_locals;
if (extra != NULL) {
return PyDict_Contains(extra, key);
}
return 0;
}
static PyObject* framelocalsproxy___contains__(PyObject *self, PyObject *key)
{
int result = framelocalsproxy_contains(self, key);
if (result < 0) {
return NULL;
}
return PyBool_FromLong(result);
}
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_pop(PyObject* self, PyObject *const *args, Py_ssize_t nargs)
{
if (!_PyArg_CheckPositional("pop", nargs, 1, 2)) {
return NULL;
}
PyObject *key = args[0];
PyObject *default_value = NULL;
if (nargs == 2) {
default_value = args[1];
}
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
int i = framelocalsproxy_getkeyindex(frame, key, false);
if (i == -2) {
return NULL;
}
if (i >= 0) {
PyErr_SetString(PyExc_ValueError, "cannot remove local variables from FrameLocalsProxy");
return NULL;
}
PyObject *result = NULL;
if (frame->f_extra_locals == NULL) {
if (default_value != NULL) {
return Py_XNewRef(default_value);
} else {
_PyErr_SetKeyError(key);
return NULL;
}
}
if (PyDict_Pop(frame->f_extra_locals, key, &result) < 0) {
return NULL;
}
if (result == NULL) {
if (default_value != NULL) {
return Py_XNewRef(default_value);
} else {
_PyErr_SetKeyError(key);
return NULL;
}
}
return result;
}
static PyObject*
framelocalsproxy_copy(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject* result = PyDict_New();
if (result == NULL) {
return NULL;
}
if (PyDict_Update(result, self) < 0) {
Py_DECREF(result);
return NULL;
}
return result;
}
static PyObject*
framelocalsproxy_reversed(PyObject *self, void *Py_UNUSED(ignored))
{
PyObject *result = framelocalsproxy_keys(self, NULL);
if (result == NULL) {
return 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 PySequenceMethods framelocalsproxy_as_sequence = {
.sq_contains = framelocalsproxy_contains,
};
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},
{"update", framelocalsproxy_update, METH_O,
NULL},
{"__reversed__", _PyCFunction_CAST(framelocalsproxy_reversed), METH_NOARGS,
NULL},
{"copy", _PyCFunction_CAST(framelocalsproxy_copy), METH_NOARGS,
NULL},
{"keys", _PyCFunction_CAST(framelocalsproxy_keys), METH_NOARGS,
NULL},
{"values", _PyCFunction_CAST(framelocalsproxy_values), METH_NOARGS,
NULL},
{"items", _PyCFunction_CAST(framelocalsproxy_items), METH_NOARGS,
NULL},
{"get", _PyCFunction_CAST(framelocalsproxy_get), METH_FASTCALL,
NULL},
{"pop", _PyCFunction_CAST(framelocalsproxy_pop), 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_sequence = &framelocalsproxy_as_sequence,
.tp_as_mapping = &framelocalsproxy_as_mapping,
.tp_getattro = PyObject_GenericGetAttr,
.tp_setattro = PyObject_GenericSetAttr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MAPPING,
.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);
if (args == NULL) {
return NULL;
}
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)
{
if (f == NULL) {
PyErr_BadInternalCall();
return NULL;
}
assert(!_PyFrame_IsIncomplete(f->f_frame));
PyCodeObject *co = _PyFrame_GetCode(f->f_frame);
if (!(co->co_flags & CO_OPTIMIZED) && !_PyFrame_HasHiddenLocals(f->f_frame)) {
if (f->f_frame->f_locals == NULL) {
// We found cases when f_locals is NULL for non-optimized code.
// We fill the f_locals with an empty dict to avoid crash until
// we find the root cause.
f->f_frame->f_locals = PyDict_New();
if (f->f_frame->f_locals == NULL) {
return NULL;
}
}
return Py_NewRef(f->f_frame->f_locals);
}
return _PyFrameLocalsProxy_New(f);
}
int
PyFrame_GetLineNumber(PyFrameObject *f)
{
assert(f != NULL);
if (f->f_lineno == -1) {
// We should calculate it once. If we can't get the line number,
// set f->f_lineno to 0.
f->f_lineno = PyUnstable_InterpreterFrame_GetLine(f->f_frame);
if (f->f_lineno < 0) {
f->f_lineno = 0;
return -1;
}
}
if (f->f_lineno > 0) {
return f->f_lineno;
}
return PyUnstable_InterpreterFrame_GetLine(f->f_frame);
}
static PyObject *
frame_getlineno(PyFrameObject *f, void *closure)
{
int lineno = PyFrame_GetLineNumber(f);
if (lineno < 0) {
Py_RETURN_NONE;
}
else {
return PyLong_FromLong(lineno);
}
}
static PyObject *
frame_getlasti(PyFrameObject *f, void *closure)
{
int lasti = _PyInterpreterFrame_LASTI(f->f_frame);
if (lasti < 0) {
return PyLong_FromLong(-1);
}
return PyLong_FromLong(lasti * sizeof(_Py_CODEUNIT));
}
static PyObject *
frame_getglobals(PyFrameObject *f, void *closure)
{
PyObject *globals = f->f_frame->f_globals;
if (globals == NULL) {
globals = Py_None;
}
return Py_NewRef(globals);
}
static PyObject *
frame_getbuiltins(PyFrameObject *f, void *closure)
{
PyObject *builtins = f->f_frame->f_builtins;
if (builtins == NULL) {
builtins = Py_None;
}
return Py_NewRef(builtins);
}
static PyObject *
frame_getcode(PyFrameObject *f, void *closure)
{
if (PySys_Audit("object.__getattr__", "Os", f, "f_code") < 0) {
return NULL;
}
return (PyObject *)PyFrame_GetCode(f);
}
static PyObject *
frame_getback(PyFrameObject *f, void *closure)
{
PyObject *res = (PyObject *)PyFrame_GetBack(f);
if (res == NULL) {
Py_RETURN_NONE;
}
return res;
}
static PyObject *
frame_gettrace_opcodes(PyFrameObject *f, void *closure)
{
PyObject *result = f->f_trace_opcodes ? Py_True : Py_False;
return Py_NewRef(result);
}
static int
frame_settrace_opcodes(PyFrameObject *f, PyObject* value, void *Py_UNUSED(ignored))
{
if (!PyBool_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"attribute value type must be bool");
return -1;
}
if (value == Py_True) {
f->f_trace_opcodes = 1;
if (f->f_trace) {
return _PyEval_SetOpcodeTrace(f, true);
}
}
else {
f->f_trace_opcodes = 0;
return _PyEval_SetOpcodeTrace(f, false);
}
return 0;
}
/* Model the evaluation stack, to determine which jumps
* are safe and how many values needs to be popped.
* The stack is modelled by a 64 integer, treating any
* stack that can't fit into 64 bits as "overflowed".
*/
typedef enum kind {
Iterator = 1,
Except = 2,
Object = 3,
Null = 4,
Lasti = 5,
} Kind;
static int
compatible_kind(Kind from, Kind to) {
if (to == 0) {
return 0;
}
if (to == Object) {
return from != Null;
}
if (to == Null) {
return 1;
}
return from == to;
}
#define BITS_PER_BLOCK 3
#define UNINITIALIZED -2
#define OVERFLOWED -1
#define MAX_STACK_ENTRIES (63/BITS_PER_BLOCK)
#define WILL_OVERFLOW (1ULL<<((MAX_STACK_ENTRIES-1)*BITS_PER_BLOCK))
#define EMPTY_STACK 0
static inline int64_t
push_value(int64_t stack, Kind kind)
{
if (((uint64_t)stack) >= WILL_OVERFLOW) {
return OVERFLOWED;
}
else {
return (stack << BITS_PER_BLOCK) | kind;
}
}
static inline int64_t
pop_value(int64_t stack)
{
return Py_ARITHMETIC_RIGHT_SHIFT(int64_t, stack, BITS_PER_BLOCK);
}
#define MASK ((1<<BITS_PER_BLOCK)-1)
static inline Kind
top_of_stack(int64_t stack)
{
return stack & MASK;
}
static inline Kind
peek(int64_t stack, int n)
{
assert(n >= 1);
return (stack>>(BITS_PER_BLOCK*(n-1))) & MASK;
}
static Kind
stack_swap(int64_t stack, int n)
{
assert(n >= 1);
Kind to_swap = peek(stack, n);
Kind top = top_of_stack(stack);
int shift = BITS_PER_BLOCK*(n-1);
int64_t replaced_low = (stack & ~(MASK << shift)) | (top << shift);
int64_t replaced_top = (replaced_low & ~MASK) | to_swap;
return replaced_top;
}
static int64_t
pop_to_level(int64_t stack, int level) {
if (level == 0) {
return EMPTY_STACK;
}
int64_t max_item = (1<<BITS_PER_BLOCK) - 1;
int64_t level_max_stack = max_item << ((level-1) * BITS_PER_BLOCK);
while (stack > level_max_stack) {
stack = pop_value(stack);
}
return stack;
}
#if 0
/* These functions are useful for debugging the stack marking code */
static char
tos_char(int64_t stack) {
switch(top_of_stack(stack)) {
case Iterator:
return 'I';
case Except:
return 'E';
case Object:
return 'O';
case Lasti:
return 'L';
case Null:
return 'N';
}
return '?';
}
static void
print_stack(int64_t stack) {
if (stack < 0) {
if (stack == UNINITIALIZED) {
printf("---");
}
else if (stack == OVERFLOWED) {
printf("OVERFLOWED");
}
else {
printf("??");
}
return;
}
while (stack) {
printf("%c", tos_char(stack));
stack = pop_value(stack);
}
}
static void
print_stacks(int64_t *stacks, int n) {
for (int i = 0; i < n; i++) {
printf("%d: ", i);
print_stack(stacks[i]);
printf("\n");
}
}
#endif
static int64_t *
mark_stacks(PyCodeObject *code_obj, int len)
{
PyObject *co_code = _PyCode_GetCode(code_obj);
if (co_code == NULL) {
return NULL;
}
int64_t *stacks = PyMem_New(int64_t, len+1);
if (stacks == NULL) {
PyErr_NoMemory();
Py_DECREF(co_code);
return NULL;
}
for (int i = 1; i <= len; i++) {
stacks[i] = UNINITIALIZED;
}
stacks[0] = EMPTY_STACK;
int todo = 1;
while (todo) {
todo = 0;
/* Scan instructions */
for (int i = 0; i < len;) {
int j;
int64_t next_stack = stacks[i];
_Py_CODEUNIT inst = _Py_GetBaseCodeUnit(code_obj, i);
int opcode = inst.op.code;
int oparg = 0;
while (opcode == EXTENDED_ARG) {
oparg = (oparg << 8) | inst.op.arg;
i++;
inst = _Py_GetBaseCodeUnit(code_obj, i);
opcode = inst.op.code;
stacks[i] = next_stack;
}
oparg = (oparg << 8) | inst.op.arg;
int next_i = i + _PyOpcode_Caches[opcode] + 1;
if (next_stack == UNINITIALIZED) {
i = next_i;
continue;
}
switch (opcode) {
case POP_JUMP_IF_FALSE:
case POP_JUMP_IF_TRUE:
case POP_JUMP_IF_NONE:
case POP_JUMP_IF_NOT_NONE:
{
int64_t target_stack;
j = next_i + oparg;
assert(j < len);
next_stack = pop_value(next_stack);
target_stack = next_stack;
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
stacks[j] = target_stack;
stacks[next_i] = next_stack;
break;
}
case SEND:
j = oparg + i + INLINE_CACHE_ENTRIES_SEND + 1;
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
stacks[j] = next_stack;
stacks[next_i] = next_stack;
break;
case JUMP_FORWARD:
j = oparg + i + 1;
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
stacks[j] = next_stack;
break;
case JUMP_BACKWARD:
case JUMP_BACKWARD_NO_INTERRUPT:
j = next_i - oparg;
assert(j >= 0);
assert(j < len);
if (stacks[j] == UNINITIALIZED && j < i) {
todo = 1;
}
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
stacks[j] = next_stack;
break;
case GET_ITER:
case GET_AITER:
next_stack = push_value(pop_value(next_stack), Iterator);
stacks[next_i] = next_stack;
break;
case FOR_ITER:
{
int64_t target_stack = push_value(next_stack, Object);
stacks[next_i] = target_stack;
j = oparg + 1 + INLINE_CACHE_ENTRIES_FOR_ITER + i;
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
stacks[j] = target_stack;
break;
}
case END_ASYNC_FOR:
next_stack = pop_value(pop_value(next_stack));
stacks[next_i] = next_stack;
break;
case PUSH_EXC_INFO:
next_stack = push_value(next_stack, Except);
stacks[next_i] = next_stack;
break;
case POP_EXCEPT:
assert(top_of_stack(next_stack) == Except);
next_stack = pop_value(next_stack);
stacks[next_i] = next_stack;
break;
case RETURN_VALUE:
assert(pop_value(next_stack) == EMPTY_STACK);
assert(top_of_stack(next_stack) == Object);
break;
case RAISE_VARARGS:
break;
case RERAISE:
assert(top_of_stack(next_stack) == Except);
/* End of block */
break;
case PUSH_NULL:
next_stack = push_value(next_stack, Null);
stacks[next_i] = next_stack;
break;
case LOAD_GLOBAL:
{
int j = oparg;
next_stack = push_value(next_stack, Object);
if (j & 1) {
next_stack = push_value(next_stack, Null);
}
stacks[next_i] = next_stack;
break;
}
case LOAD_ATTR:
{
assert(top_of_stack(next_stack) == Object);
int j = oparg;
if (j & 1) {
next_stack = pop_value(next_stack);
next_stack = push_value(next_stack, Object);
next_stack = push_value(next_stack, Null);
}
stacks[next_i] = next_stack;
break;
}
case SWAP:
{
int n = oparg;
next_stack = stack_swap(next_stack, n);
stacks[next_i] = next_stack;
break;
}
case COPY:
{
int n = oparg;
next_stack = push_value(next_stack, peek(next_stack, n));
stacks[next_i] = next_stack;
break;
}
case CACHE:
case RESERVED:
{
assert(0);
}
default:
{
int delta = PyCompile_OpcodeStackEffect(opcode, oparg);
assert(delta != PY_INVALID_STACK_EFFECT);
while (delta < 0) {
next_stack = pop_value(next_stack);
delta++;
}
while (delta > 0) {
next_stack = push_value(next_stack, Object);
delta--;
}
stacks[next_i] = next_stack;
}
}
i = next_i;
}
/* Scan exception table */
unsigned char *start = (unsigned char *)PyBytes_AS_STRING(code_obj->co_exceptiontable);
unsigned char *end = start + PyBytes_GET_SIZE(code_obj->co_exceptiontable);
unsigned char *scan = start;
while (scan < end) {
int start_offset, size, handler;
scan = parse_varint(scan, &start_offset);
assert(start_offset >= 0 && start_offset < len);
scan = parse_varint(scan, &size);
assert(size >= 0 && start_offset+size <= len);
scan = parse_varint(scan, &handler);
assert(handler >= 0 && handler < len);
int depth_and_lasti;
scan = parse_varint(scan, &depth_and_lasti);
int level = depth_and_lasti >> 1;
int lasti = depth_and_lasti & 1;
if (stacks[start_offset] != UNINITIALIZED) {
if (stacks[handler] == UNINITIALIZED) {
todo = 1;
uint64_t target_stack = pop_to_level(stacks[start_offset], level);
if (lasti) {
target_stack = push_value(target_stack, Lasti);
}
target_stack = push_value(target_stack, Except);
stacks[handler] = target_stack;
}
}
}
}
Py_DECREF(co_code);
return stacks;
}
static int
compatible_stack(int64_t from_stack, int64_t to_stack)
{
if (from_stack < 0 || to_stack < 0) {
return 0;
}
while(from_stack > to_stack) {
from_stack = pop_value(from_stack);
}
while(from_stack) {
Kind from_top = top_of_stack(from_stack);
Kind to_top = top_of_stack(to_stack);
if (!compatible_kind(from_top, to_top)) {
return 0;
}
from_stack = pop_value(from_stack);
to_stack = pop_value(to_stack);
}
return to_stack == 0;
}
static const char *
explain_incompatible_stack(int64_t to_stack)
{
assert(to_stack != 0);
if (to_stack == OVERFLOWED) {
return "stack is too deep to analyze";
}
if (to_stack == UNINITIALIZED) {
return "can't jump into an exception handler, or code may be unreachable";
}
Kind target_kind = top_of_stack(to_stack);
switch(target_kind) {
case Except:
return "can't jump into an 'except' block as there's no exception";
case Lasti:
return "can't jump into a re-raising block as there's no location";
case Object:
case Null:
return "incompatible stacks";
case Iterator:
return "can't jump into the body of a for loop";
default:
Py_UNREACHABLE();
}
}
static int *
marklines(PyCodeObject *code, int len)
{
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(code, &bounds);
assert (bounds.ar_end == 0);
int last_line = -1;
int *linestarts = PyMem_New(int, len);
if (linestarts == NULL) {
return NULL;
}
for (int i = 0; i < len; i++) {
linestarts[i] = -1;
}
while (_PyLineTable_NextAddressRange(&bounds)) {
assert(bounds.ar_start / (int)sizeof(_Py_CODEUNIT) < len);
if (bounds.ar_line != last_line && bounds.ar_line != -1) {
linestarts[bounds.ar_start / sizeof(_Py_CODEUNIT)] = bounds.ar_line;
last_line = bounds.ar_line;
}
}
return linestarts;
}
static int
first_line_not_before(int *lines, int len, int line)
{
int result = INT_MAX;
for (int i = 0; i < len; i++) {
if (lines[i] < result && lines[i] >= line) {
result = lines[i];
}
}
if (result == INT_MAX) {
return -1;
}
return result;
}
static bool frame_is_suspended(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
if (frame->f_frame->owner == FRAME_OWNED_BY_GENERATOR) {
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame->f_frame);
return FRAME_STATE_SUSPENDED(gen->gi_frame_state);
}
return false;
}
/* Setter for f_lineno - you can set f_lineno from within a trace function in
* order to jump to a given line of code, subject to some restrictions. Most
* lines are OK to jump to because they don't make any assumptions about the
* state of the stack (obvious because you could remove the line and the code
* would still work without any stack errors), but there are some constructs
* that limit jumping:
*
* o Any exception handlers.
* o 'for' and 'async for' loops can't be jumped into because the
* iterator needs to be on the stack.
* o Jumps cannot be made from within a trace function invoked with a
* 'return' or 'exception' event since the eval loop has been exited at
* that time.
*/
static int
frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignored))
{
PyCodeObject *code = _PyFrame_GetCode(f->f_frame);
if (p_new_lineno == NULL) {
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
return -1;
}
/* f_lineno must be an integer. */
if (!PyLong_CheckExact(p_new_lineno)) {
PyErr_SetString(PyExc_ValueError,
"lineno must be an integer");
return -1;
}
bool is_suspended = frame_is_suspended(f);
/*
* This code preserves the historical restrictions on
* setting the line number of a frame.
* Jumps are forbidden on a 'return' trace event (except after a yield).
* Jumps from 'call' trace events are also forbidden.
* In addition, jumps are forbidden when not tracing,
* as this is a debugging feature.
*/
int what_event = PyThreadState_GET()->what_event;
if (what_event < 0) {
PyErr_Format(PyExc_ValueError,
"f_lineno can only be set in a trace function");
return -1;
}
switch (what_event) {
case PY_MONITORING_EVENT_PY_RESUME:
case PY_MONITORING_EVENT_JUMP:
case PY_MONITORING_EVENT_BRANCH:
case PY_MONITORING_EVENT_LINE:
case PY_MONITORING_EVENT_PY_YIELD:
/* Setting f_lineno is allowed for the above events */
break;
case PY_MONITORING_EVENT_PY_START:
PyErr_Format(PyExc_ValueError,
"can't jump from the 'call' trace event of a new frame");
return -1;
case PY_MONITORING_EVENT_CALL:
case PY_MONITORING_EVENT_C_RETURN:
PyErr_SetString(PyExc_ValueError,
"can't jump during a call");
return -1;
case PY_MONITORING_EVENT_PY_RETURN:
case PY_MONITORING_EVENT_PY_UNWIND:
case PY_MONITORING_EVENT_PY_THROW:
case PY_MONITORING_EVENT_RAISE:
case PY_MONITORING_EVENT_C_RAISE:
case PY_MONITORING_EVENT_INSTRUCTION:
case PY_MONITORING_EVENT_EXCEPTION_HANDLED:
PyErr_Format(PyExc_ValueError,
"can only jump from a 'line' trace event");
return -1;
default:
PyErr_SetString(PyExc_SystemError,
"unexpected event type");
return -1;
}
int new_lineno;
/* Fail if the line falls outside the code block and
select first line with actual code. */
int overflow;
long l_new_lineno = PyLong_AsLongAndOverflow(p_new_lineno, &overflow);
if (overflow
#if SIZEOF_LONG > SIZEOF_INT
|| l_new_lineno > INT_MAX
|| l_new_lineno < INT_MIN
#endif
) {
PyErr_SetString(PyExc_ValueError,
"lineno out of range");
return -1;
}
new_lineno = (int)l_new_lineno;
if (new_lineno < code->co_firstlineno) {
PyErr_Format(PyExc_ValueError,
"line %d comes before the current code block",
new_lineno);
return -1;
}
/* PyCode_NewWithPosOnlyArgs limits co_code to be under INT_MAX so this
* should never overflow. */
int len = (int)Py_SIZE(code);
int *lines = marklines(code, len);
if (lines == NULL) {
return -1;
}
new_lineno = first_line_not_before(lines, len, new_lineno);
if (new_lineno < 0) {
PyErr_Format(PyExc_ValueError,
"line %d comes after the current code block",
(int)l_new_lineno);
PyMem_Free(lines);
return -1;
}
int64_t *stacks = mark_stacks(code, len);
if (stacks == NULL) {
PyMem_Free(lines);
return -1;
}
int64_t best_stack = OVERFLOWED;
int best_addr = -1;
int64_t start_stack = stacks[_PyInterpreterFrame_LASTI(f->f_frame)];
int err = -1;
const char *msg = "cannot find bytecode for specified line";
for (int i = 0; i < len; i++) {
if (lines[i] == new_lineno) {
int64_t target_stack = stacks[i];
if (compatible_stack(start_stack, target_stack)) {
err = 0;
if (target_stack > best_stack) {
best_stack = target_stack;
best_addr = i;
}
}
else if (err < 0) {
if (start_stack == OVERFLOWED) {
msg = "stack to deep to analyze";
}
else if (start_stack == UNINITIALIZED) {
msg = "can't jump from unreachable code";
}
else {
msg = explain_incompatible_stack(target_stack);
err = 1;
}
}
}
}
PyMem_Free(stacks);
PyMem_Free(lines);
if (err) {
PyErr_SetString(PyExc_ValueError, msg);
return -1;
}
// Populate any NULL locals that the compiler might have "proven" to exist
// in the new location. Rather than crashing or changing co_code, just bind
// None instead:
int unbound = 0;
for (int i = 0; i < code->co_nlocalsplus; i++) {
// Counting every unbound local is overly-cautious, but a full flow
// analysis (like we do in the compiler) is probably too expensive:
unbound += PyStackRef_IsNull(f->f_frame->localsplus[i]);
}
if (unbound) {
const char *e = "assigning None to %d unbound local%s";
const char *s = (unbound == 1) ? "" : "s";
if (PyErr_WarnFormat(PyExc_RuntimeWarning, 0, e, unbound, s)) {
return -1;
}
// Do this in a second pass to avoid writing a bunch of Nones when
// warnings are being treated as errors and the previous bit raises:
for (int i = 0; i < code->co_nlocalsplus; i++) {
if (PyStackRef_IsNull(f->f_frame->localsplus[i])) {
f->f_frame->localsplus[i] = PyStackRef_None;
unbound--;
}
}
assert(unbound == 0);
}
if (is_suspended) {
/* Account for value popped by yield */
start_stack = pop_value(start_stack);
}
while (start_stack > best_stack) {
if (top_of_stack(start_stack) == Except) {
/* Pop exception stack as well as the evaluation stack */
PyObject *exc = PyStackRef_AsPyObjectBorrow(_PyFrame_StackPop(f->f_frame));
assert(PyExceptionInstance_Check(exc) || exc == Py_None);
PyThreadState *tstate = _PyThreadState_GET();
Py_XSETREF(tstate->exc_info->exc_value, exc == Py_None ? NULL : exc);
}
else {
PyStackRef_XCLOSE(_PyFrame_StackPop(f->f_frame));
}
start_stack = pop_value(start_stack);
}
/* Finally set the new lasti and return OK. */
f->f_lineno = 0;
f->f_frame->instr_ptr = _PyFrame_GetBytecode(f->f_frame) + best_addr;
return 0;
}
static PyObject *
frame_gettrace(PyFrameObject *f, void *closure)
{
PyObject* trace = f->f_trace;
if (trace == NULL)
trace = Py_None;
return Py_NewRef(trace);
}
static int
frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
{
if (v == Py_None) {
v = NULL;
}
if (v != f->f_trace) {
Py_XSETREF(f->f_trace, Py_XNewRef(v));
if (v != NULL && f->f_trace_opcodes) {
return _PyEval_SetOpcodeTrace(f, true);
}
}
return 0;
}
static PyGetSetDef frame_getsetlist[] = {
{"f_back", (getter)frame_getback, NULL, NULL},
{"f_locals", (getter)frame_getlocals, NULL, NULL},
{"f_lineno", (getter)frame_getlineno,
(setter)frame_setlineno, NULL},
{"f_trace", (getter)frame_gettrace, (setter)frame_settrace, NULL},
{"f_lasti", (getter)frame_getlasti, NULL, NULL},
{"f_globals", (getter)frame_getglobals, NULL, NULL},
{"f_builtins", (getter)frame_getbuiltins, NULL, NULL},
{"f_code", (getter)frame_getcode, NULL, NULL},
{"f_trace_opcodes", (getter)frame_gettrace_opcodes, (setter)frame_settrace_opcodes, NULL},
{0}
};
static void
frame_dealloc(PyFrameObject *f)
{
/* It is the responsibility of the owning generator/coroutine
* to have cleared the generator pointer */
if (_PyObject_GC_IS_TRACKED(f)) {
_PyObject_GC_UNTRACK(f);
}
Py_TRASHCAN_BEGIN(f, frame_dealloc);
/* GH-106092: If f->f_frame was on the stack and we reached the maximum
* nesting depth for deallocations, the trashcan may have delayed this
* deallocation until after f->f_frame is freed. Avoid dereferencing
* f->f_frame unless we know it still points to valid memory. */
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)f->_f_frame_data;
/* Kill all local variables including specials, if we own them */
if (f->f_frame == frame && frame->owner == FRAME_OWNED_BY_FRAME_OBJECT) {
PyStackRef_CLEAR(frame->f_executable);
PyStackRef_CLEAR(frame->f_funcobj);
Py_CLEAR(frame->f_locals);
_PyStackRef *locals = _PyFrame_GetLocalsArray(frame);
_PyStackRef *sp = frame->stackpointer;
while (sp > locals) {
sp--;
PyStackRef_CLEAR(*sp);
}
}
Py_CLEAR(f->f_back);
Py_CLEAR(f->f_trace);
Py_CLEAR(f->f_extra_locals);
Py_CLEAR(f->f_locals_cache);
PyObject_GC_Del(f);
Py_TRASHCAN_END;
}
static int
frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
{
Py_VISIT(f->f_back);
Py_VISIT(f->f_trace);
Py_VISIT(f->f_extra_locals);
Py_VISIT(f->f_locals_cache);
if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
return 0;
}
assert(f->f_frame->frame_obj == NULL);
return _PyFrame_Traverse(f->f_frame, visit, arg);
}
static int
frame_tp_clear(PyFrameObject *f)
{
Py_CLEAR(f->f_trace);
Py_CLEAR(f->f_extra_locals);
Py_CLEAR(f->f_locals_cache);
/* locals and stack */
_PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame);
_PyStackRef *sp = f->f_frame->stackpointer;
assert(sp >= locals);
while (sp > locals) {
sp--;
PyStackRef_CLEAR(*sp);
}
f->f_frame->stackpointer = locals;
Py_CLEAR(f->f_frame->f_locals);
return 0;
}
static PyObject *
frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
{
if (f->f_frame->owner == FRAME_OWNED_BY_GENERATOR) {
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(f->f_frame);
if (gen->gi_frame_state == FRAME_EXECUTING) {
goto running;
}
if (FRAME_STATE_SUSPENDED(gen->gi_frame_state)) {
goto suspended;
}
_PyGen_Finalize((PyObject *)gen);
}
else if (f->f_frame->owner == FRAME_OWNED_BY_THREAD) {
goto running;
}
else {
assert(f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT);
(void)frame_tp_clear(f);
}
Py_RETURN_NONE;
running:
PyErr_SetString(PyExc_RuntimeError,
"cannot clear an executing frame");
return NULL;
suspended:
PyErr_SetString(PyExc_RuntimeError,
"cannot clear a suspended frame");
return NULL;
}
PyDoc_STRVAR(clear__doc__,
"F.clear(): clear all references held by the frame");
static PyObject *
frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
{
Py_ssize_t res;
res = offsetof(PyFrameObject, _f_frame_data) + offsetof(_PyInterpreterFrame, localsplus);
PyCodeObject *code = _PyFrame_GetCode(f->f_frame);
res += _PyFrame_NumSlotsForCodeObject(code) * sizeof(PyObject *);
return PyLong_FromSsize_t(res);
}
PyDoc_STRVAR(sizeof__doc__,
"F.__sizeof__() -> size of F in memory, in bytes");
static PyObject *
frame_repr(PyFrameObject *f)
{
int lineno = PyFrame_GetLineNumber(f);
PyCodeObject *code = _PyFrame_GetCode(f->f_frame);
return PyUnicode_FromFormat(
"<frame at %p, file %R, line %d, code %S>",
f, code->co_filename, lineno, code->co_name);
}
static PyMethodDef frame_methods[] = {
{"clear", (PyCFunction)frame_clear, METH_NOARGS,
clear__doc__},
{"__sizeof__", (PyCFunction)frame_sizeof, METH_NOARGS,
sizeof__doc__},
{NULL, NULL} /* sentinel */
};
PyTypeObject PyFrame_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"frame",
offsetof(PyFrameObject, _f_frame_data) +
offsetof(_PyInterpreterFrame, localsplus),
sizeof(PyObject *),
(destructor)frame_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)frame_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
PyObject_GenericSetAttr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
0, /* tp_doc */
(traverseproc)frame_traverse, /* tp_traverse */
(inquiry)frame_tp_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
frame_methods, /* tp_methods */
frame_memberlist, /* tp_members */
frame_getsetlist, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
};
static void
init_frame(PyThreadState *tstate, _PyInterpreterFrame *frame,
PyFunctionObject *func, PyObject *locals)
{
PyCodeObject *code = (PyCodeObject *)func->func_code;
_PyFrame_Initialize(tstate, frame, PyStackRef_FromPyObjectNew(func),
Py_XNewRef(locals), code, 0, NULL);
}
PyFrameObject*
_PyFrame_New_NoTrack(PyCodeObject *code)
{
CALL_STAT_INC(frame_objects_created);
int slots = code->co_nlocalsplus + code->co_stacksize;
PyFrameObject *f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, slots);
if (f == NULL) {
return NULL;
}
f->f_back = NULL;
f->f_trace = NULL;
f->f_trace_lines = 1;
f->f_trace_opcodes = 0;
f->f_lineno = 0;
f->f_extra_locals = NULL;
f->f_locals_cache = NULL;
return f;
}
/* Legacy API */
PyFrameObject*
PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
PyObject *globals, PyObject *locals)
{
PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
if (builtins == NULL) {
return NULL;
}
PyFrameConstructor desc = {
.fc_globals = globals,
.fc_builtins = builtins,
.fc_name = code->co_name,
.fc_qualname = code->co_name,
.fc_code = (PyObject *)code,
.fc_defaults = NULL,
.fc_kwdefaults = NULL,
.fc_closure = NULL
};
PyFunctionObject *func = _PyFunction_FromConstructor(&desc);
_Py_DECREF_BUILTINS(builtins);
if (func == NULL) {
return NULL;
}
PyFrameObject *f = _PyFrame_New_NoTrack(code);
if (f == NULL) {
Py_DECREF(func);
return NULL;
}
init_frame(tstate, (_PyInterpreterFrame *)f->_f_frame_data, func, locals);
f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
f->f_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
// This frame needs to be "complete", so pretend that the first RESUME ran:
f->f_frame->instr_ptr = _PyCode_CODE(code) + code->_co_firsttraceable + 1;
assert(!_PyFrame_IsIncomplete(f->f_frame));
Py_DECREF(func);
_PyObject_GC_TRACK(f);
return f;
}
// Initialize frame free variables if needed
static void
frame_init_get_vars(_PyInterpreterFrame *frame)
{
// COPY_FREE_VARS has no quickened forms, so no need to use _PyOpcode_Deopt
// here:
PyCodeObject *co = _PyFrame_GetCode(frame);
int lasti = _PyInterpreterFrame_LASTI(frame);
if (!(lasti < 0
&& _PyFrame_GetBytecode(frame)->op.code == COPY_FREE_VARS
&& PyStackRef_FunctionCheck(frame->f_funcobj)))
{
/* Free vars are initialized */
return;
}
/* Free vars have not been initialized -- Do that */
PyFunctionObject *func = _PyFrame_GetFunction(frame);
PyObject *closure = func->func_closure;
int offset = PyUnstable_Code_GetFirstFree(co);
for (int i = 0; i < co->co_nfreevars; ++i) {
PyObject *o = PyTuple_GET_ITEM(closure, i);
frame->localsplus[offset + i] = PyStackRef_FromPyObjectNew(o);
}
// COPY_FREE_VARS doesn't have inline CACHEs, either:
frame->instr_ptr = _PyFrame_GetBytecode(frame);
}
static int
frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
PyObject **pvalue)
{
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
/* If the namespace is unoptimized, then one of the
following cases applies:
1. It does not contain free variables, because it
uses import * or is a top-level namespace.
2. It is a class namespace.
We don't want to accidentally copy free variables
into the locals dict used by the class.
*/
if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED)) {
return 0;
}
PyObject *value = NULL;
if (frame->stackpointer == NULL || frame->stackpointer > frame->localsplus + i) {
value = PyStackRef_AsPyObjectBorrow(frame->localsplus[i]);
if (kind & CO_FAST_FREE) {
// The cell was set by COPY_FREE_VARS.
assert(value != NULL && PyCell_Check(value));
value = PyCell_GET(value);
}
else if (kind & CO_FAST_CELL) {
if (value != NULL) {
if (PyCell_Check(value)) {
assert(!_PyFrame_IsIncomplete(frame));
value = PyCell_GET(value);
}
// (likely) Otherwise it is an arg (kind & CO_FAST_LOCAL),
// with the initial value set when the frame was created...
// (unlikely) ...or it was set via the f_locals proxy.
}
}
}
*pvalue = value;
return 1;
}
bool
_PyFrame_HasHiddenLocals(_PyInterpreterFrame *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);
for (int i = 0; i < co->co_nlocalsplus; i++) {
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
if (kind & CO_FAST_HIDDEN) {
PyObject* value = framelocalsproxy_getval(frame, co, i);
if (value != NULL) {
return true;
}
}
}
return false;
}
PyObject *
_PyFrame_GetLocals(_PyInterpreterFrame *frame)
{
// 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)) {
if (frame->f_locals == NULL) {
// We found cases when f_locals is NULL for non-optimized code.
// We fill the f_locals with an empty dict to avoid crash until
// we find the root cause.
frame->f_locals = PyDict_New();
if (frame->f_locals == NULL) {
return NULL;
}
}
return Py_NewRef(frame->f_locals);
}
PyFrameObject* f = _PyFrame_GetFrameObject(frame);
return _PyFrameLocalsProxy_New(f);
}
PyObject *
PyFrame_GetVar(PyFrameObject *frame_obj, PyObject *name)
{
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError, "name must be str, not %s",
Py_TYPE(name)->tp_name);
return NULL;
}
_PyInterpreterFrame *frame = frame_obj->f_frame;
frame_init_get_vars(frame);
PyCodeObject *co = _PyFrame_GetCode(frame);
for (int i = 0; i < co->co_nlocalsplus; i++) {
PyObject *var_name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
if (!_PyUnicode_Equal(var_name, name)) {
continue;
}
PyObject *value; // borrowed reference
if (!frame_get_var(frame, co, i, &value)) {
break;
}
if (value == NULL) {
break;
}
return Py_NewRef(value);
}
PyErr_Format(PyExc_NameError, "variable %R does not exist", name);
return NULL;
}
PyObject *
PyFrame_GetVarString(PyFrameObject *frame, const char *name)
{
PyObject *name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
return NULL;
}
PyObject *value = PyFrame_GetVar(frame, name_obj);
Py_DECREF(name_obj);
return value;
}
int
PyFrame_FastToLocalsWithError(PyFrameObject *f)
{
// Nothing to do here, as f_locals is now a write-through proxy in
// optimized frames. Soft-deprecated, since there's no maintenance hassle.
return 0;
}
void
PyFrame_FastToLocals(PyFrameObject *f)
{
// Nothing to do here, as f_locals is now a write-through proxy in
// optimized frames. Soft-deprecated, since there's no maintenance hassle.
return;
}
void
PyFrame_LocalsToFast(PyFrameObject *f, int clear)
{
// Nothing to do here, as f_locals is now a write-through proxy in
// optimized frames. Soft-deprecated, since there's no maintenance hassle.
return;
}
int
_PyFrame_IsEntryFrame(PyFrameObject *frame)
{
assert(frame != NULL);
_PyInterpreterFrame *f = frame->f_frame;
assert(!_PyFrame_IsIncomplete(f));
return f->previous && f->previous->owner == FRAME_OWNED_BY_CSTACK;
}
PyCodeObject *
PyFrame_GetCode(PyFrameObject *frame)
{
assert(frame != NULL);
assert(!_PyFrame_IsIncomplete(frame->f_frame));
PyCodeObject *code = _PyFrame_GetCode(frame->f_frame);
assert(code != NULL);
return (PyCodeObject*)Py_NewRef(code);
}
PyFrameObject*
PyFrame_GetBack(PyFrameObject *frame)
{
assert(frame != NULL);
assert(!_PyFrame_IsIncomplete(frame->f_frame));
PyFrameObject *back = frame->f_back;
if (back == NULL) {
_PyInterpreterFrame *prev = frame->f_frame->previous;
prev = _PyFrame_GetFirstComplete(prev);
if (prev) {
back = _PyFrame_GetFrameObject(prev);
}
}
return (PyFrameObject*)Py_XNewRef(back);
}
PyObject*
PyFrame_GetLocals(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
return frame_getlocals(frame, NULL);
}
PyObject*
PyFrame_GetGlobals(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
return frame_getglobals(frame, NULL);
}
PyObject*
PyFrame_GetBuiltins(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
return frame_getbuiltins(frame, NULL);
}
int
PyFrame_GetLasti(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
int lasti = _PyInterpreterFrame_LASTI(frame->f_frame);
if (lasti < 0) {
return -1;
}
return lasti * sizeof(_Py_CODEUNIT);
}
PyObject *
PyFrame_GetGenerator(PyFrameObject *frame)
{
assert(!_PyFrame_IsIncomplete(frame->f_frame));
if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR) {
return NULL;
}
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame->f_frame);
return Py_NewRef(gen);
}