Skip to content

Commit a271e36

Browse files
Add tests for -1, co_nlocalsplus indice (test_get_local_index_out_of_range) and unset variable (get_get_local_unset)
1 parent cd8b975 commit a271e36

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

‎Lib/test/test_capi/test_misc.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2836,6 +2836,32 @@ def inner():
28362836
d = inner()
28372837
self.assertEqual(d['z'], 7)
28382838

2839+
def test_get_local_index_out_of_range(self):
2840+
def f(index):
2841+
return _testinternalcapi.get_frame_local(index)
2842+
2843+
# f has no cell or free variables, so co_nlocalsplus == co_nlocals.
2844+
code = f.__code__
2845+
self.assertFalse(code.co_cellvars or code.co_freevars)
2846+
nlocalsplus = code.co_nlocals
2847+
for index in (-1, nlocalsplus, nlocalsplus + 1):
2848+
with self.subTest(index=index):
2849+
with self.assertRaises(IndexError):
2850+
f(index)
2851+
2852+
def test_get_local_unset(self):
2853+
def f():
2854+
if False:
2855+
unset = 1
2856+
names = f.__code__.co_varnames
2857+
return _testinternalcapi.get_frame_local(names.index('unset'))
2858+
2859+
self.assertEqual(f(), (0, None))
2860+
2861+
def test_get_local_set(self):
2862+
x = 5
2863+
index = self.test_get_local_set.__code__.co_varnames.index('x')
2864+
self.assertEqual(_testinternalcapi.get_frame_local(index), (1, 5))
28392865

28402866
def test_code_get_localsplus_names(self):
28412867
def outer(a, b):

‎Modules/_testinternalcapi.c‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,44 @@ get_frame_locals(PyObject *self, PyObject *Py_UNUSED(ignored))
15781578
return dict;
15791579
}
15801580

1581+
// Calls PyUnstable_InterpreterFrame_GetLocal on the caller's frame for a single
1582+
// index and returns (rc, value), or raises if rc is -1.
1583+
static PyObject *
1584+
get_frame_local(PyObject *self, PyObject *arg)
1585+
{
1586+
Py_ssize_t index = PyLong_AsSsize_t(arg);
1587+
if (index == -1 && PyErr_Occurred()) {
1588+
return NULL;
1589+
}
1590+
PyThreadState *tstate = _PyThreadState_GET();
1591+
_PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate);
1592+
if (frame == NULL) {
1593+
PyErr_SetString(PyExc_RuntimeError, "no caller frame");
1594+
return NULL;
1595+
}
1596+
PyObject *value;
1597+
int rc = PyUnstable_InterpreterFrame_GetLocal(frame, index, &value);
1598+
if (rc < 0) {
1599+
assert(value == NULL);
1600+
return NULL;
1601+
}
1602+
if (rc == 0) {
1603+
assert(value == NULL);
1604+
return Py_BuildValue("iO", rc, Py_None);
1605+
}
1606+
return Py_BuildValue("iN", rc, value);
1607+
}
1608+
1609+
static PyObject *
1610+
code_get_localsplus_names(PyObject *self, PyObject *arg)
1611+
{
1612+
if (!PyCode_Check(arg)) {
1613+
PyErr_SetString(PyExc_TypeError, "argument must be a code object");
1614+
return NULL;
1615+
}
1616+
return PyUnstable_Code_GetLocalPlusNames((PyCodeObject *)arg);
1617+
}
1618+
15811619
static PyObject *
15821620
code_returns_only_none(PyObject *self, PyObject *arg)
15831621
{
@@ -3393,6 +3431,8 @@ static PyMethodDef module_functions[] = {
33933431
{"iframe_getline", iframe_getline, METH_O, NULL},
33943432
{"iframe_getlasti", iframe_getlasti, METH_O, NULL},
33953433
{"get_frame_locals", get_frame_locals, METH_NOARGS, NULL},
3434+
{"get_frame_local", get_frame_local, METH_O, NULL},
3435+
{"code_get_localsplus_names", code_get_localsplus_names, METH_O, NULL},
33963436
{"code_returns_only_none", code_returns_only_none, METH_O, NULL},
33973437
{"get_co_framesize", get_co_framesize, METH_O, NULL},
33983438
{"get_co_localskinds", get_co_localskinds, METH_O, NULL},

0 commit comments

Comments
 (0)