Skip to content
Merged
7 changes: 7 additions & 0 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,13 @@ static inline void _Py_LeaveRecursiveCall(void) {

extern _PyInterpreterFrame* _PyEval_GetFrame(void);

extern PyObject * _PyEval_GetGlobalsFromRunningMain(PyThreadState *);
extern int _PyEval_EnsureBuiltins(
PyThreadState *,
PyObject *,
int usemod,
PyObject **p_builtins);

PyAPI_FUNC(PyObject *)_Py_MakeCoro(PyFunctionObject *func);

/* Handle signals, pending calls, GIL drop request
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,41 @@ def test_call_invalid(self):
with self.assertRaises(interpreters.NotShareableError):
interp.call(func, op, 'eggs!')

def test_callable_requires_frame(self):
# There are various functions tha require a current frame.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a typo in the comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

interp = interpreters.create()
for call, expected in [
((eval, '[1, 2, 3]'),
[1, 2, 3]),
((eval, 'sum([1, 2, 3])'),
6),
((exec, '...'),
None),
]:
with self.subTest(str(call)):
res = interp.call(*call)
self.assertEqual(res, expected)

notshareable = [
globals,
locals,
vars,
]
Copy link
Contributor

@neonene neonene Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If "notshareable" here means that the result of the functions cannot be pickled, could NotShareableError differentiate the round-trip in the future? Also, interpreters.is_shareable() returns False for both globals and dir, which might be confusing at first.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

for func, expectedtype in {
globals: dict,
locals: dict,
vars: dict,
dir: list,
}.items():
with self.subTest(str(func)):
if func in notshareable:
with self.assertRaises(interpreters.NotShareableError):
interp.call(func)
else:
res = interp.call(func)
self.assertIsInstance(res, expectedtype)
self.assertIn('__builtins__', res)

def test_call_in_thread(self):
interp = interpreters.create()

Expand Down
18 changes: 16 additions & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2083,9 +2083,23 @@ _dir_locals(void)
PyObject *names;
PyObject *locals;

locals = _PyEval_GetFrameLocals();
if (locals == NULL)
if (_PyEval_GetFrame() != NULL) {
locals = _PyEval_GetFrameLocals();
}
PyThreadState *tstate = _PyThreadState_GET();
locals = _PyEval_GetGlobalsFromRunningMain(tstate);
Copy link
Contributor

@neonene neonene Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_PyEval_GetFrameLocals() returns a new reference (leak), which is overwritten by _PyEval_GetGlobalsFromRunningMain(). Thus the fallback mechanism looks to be missing.

The same goes for builtin_vars().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if (locals == NULL) {
if (!_PyErr_Occurred(tstate)) {
locals = _PyEval_GetFrameLocals();
assert(_PyErr_Occurred(tstate));
}
}
else {
Py_INCREF(locals);
}
if (locals == NULL) {
return NULL;
}

names = PyMapping_Keys(locals);
Py_DECREF(locals);
Expand Down
161 changes: 113 additions & 48 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals)
/*[clinic end generated code: output=0a0824aa70093116 input=7c7bce5299a89062]*/
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *result = NULL, *source_copy;
const char *str;

Expand All @@ -970,35 +971,46 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
: "globals must be a dict");
return NULL;
}
if (globals == Py_None) {

int fromframe = 0;
if (globals != Py_None) {
Py_INCREF(globals);
}
else if (_PyEval_GetFrame() != NULL) {
fromframe = 1;
globals = PyEval_GetGlobals();
if (locals == Py_None) {
locals = _PyEval_GetFrameLocals();
if (locals == NULL)
return NULL;
}
else {
Py_INCREF(locals);
}
assert(globals != NULL);
Py_INCREF(globals);
}
else if (locals == Py_None)
locals = Py_NewRef(globals);
else {
Py_INCREF(locals);
globals = _PyEval_GetGlobalsFromRunningMain(tstate);
if (globals == NULL) {
if (!_PyErr_Occurred(tstate)) {
PyErr_SetString(PyExc_TypeError,
"eval must be given globals and locals "
"when called without a frame");
}
return NULL;
}
Py_INCREF(globals);
}

if (globals == NULL || locals == NULL) {
PyErr_SetString(PyExc_TypeError,
"eval must be given globals and locals "
"when called without a frame");
goto error;
if (locals != Py_None) {
Py_INCREF(locals);
}

int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
if (r == 0) {
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
else if (fromframe) {
locals = _PyEval_GetFrameLocals();
if (locals == NULL) {
assert(PyErr_Occurred());
Py_DECREF(globals);
return NULL;
}
}
else {
locals = Py_NewRef(globals);
}
if (r < 0) {

if (_PyEval_EnsureBuiltins(tstate, globals, 0, NULL) < 0) {
goto error;
}

Expand Down Expand Up @@ -1039,6 +1051,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
}

error:
Py_XDECREF(globals);
Py_XDECREF(locals);
return result;
}
Expand Down Expand Up @@ -1069,29 +1082,44 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals, PyObject *closure)
/*[clinic end generated code: output=7579eb4e7646743d input=25e989b6d87a3a21]*/
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *v;

if (globals == Py_None) {
int fromframe = 0;
if (globals != Py_None) {
Py_INCREF(globals);
}
else if (_PyEval_GetFrame() != NULL) {
fromframe = 1;
globals = PyEval_GetGlobals();
if (locals == Py_None) {
locals = _PyEval_GetFrameLocals();
if (locals == NULL)
return NULL;
}
else {
Py_INCREF(locals);
assert(globals != NULL);
Py_INCREF(globals);
}
else {
globals = _PyEval_GetGlobalsFromRunningMain(tstate);
if (globals == NULL) {
if (!_PyErr_Occurred(tstate)) {
PyErr_SetString(PyExc_SystemError,
"globals and locals cannot be NULL");
}
goto error;
}
if (!globals || !locals) {
PyErr_SetString(PyExc_SystemError,
"globals and locals cannot be NULL");
Py_INCREF(globals);
}

if (locals != Py_None) {
Py_INCREF(locals);
}
else if (fromframe) {
locals = _PyEval_GetFrameLocals();
if (locals == NULL) {
assert(PyErr_Occurred());
Py_DECREF(globals);
return NULL;
}
}
else if (locals == Py_None) {
locals = Py_NewRef(globals);
}
else {
Py_INCREF(locals);
locals = Py_NewRef(globals);
}

if (!PyDict_Check(globals)) {
Expand All @@ -1105,11 +1133,8 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
Py_TYPE(locals)->tp_name);
goto error;
}
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
if (r == 0) {
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
}
if (r < 0) {

if (_PyEval_EnsureBuiltins(tstate, globals, 0, NULL) < 0) {
goto error;
}

Expand Down Expand Up @@ -1186,11 +1211,13 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
}
if (v == NULL)
goto error;
Py_DECREF(globals);
Py_DECREF(locals);
Py_DECREF(v);
Py_RETURN_NONE;

error:
Py_XDECREF(globals);
Py_XDECREF(locals);
return NULL;
}
Expand Down Expand Up @@ -1240,10 +1267,21 @@ static PyObject *
builtin_globals_impl(PyObject *module)
/*[clinic end generated code: output=e5dd1527067b94d2 input=9327576f92bb48ba]*/
{
PyObject *d;

d = PyEval_GetGlobals();
return Py_XNewRef(d);
PyObject *globals;
if (_PyEval_GetFrame() != NULL) {
globals = PyEval_GetGlobals();
assert(globals != NULL);
return Py_NewRef(globals);
}
PyThreadState *tstate = _PyThreadState_GET();
globals = _PyEval_GetGlobalsFromRunningMain(tstate);
if (globals == NULL) {
if (_PyErr_Occurred(tstate)) {
return NULL;
}
Py_RETURN_NONE;
}
return Py_NewRef(globals);
}


Expand Down Expand Up @@ -1887,7 +1925,21 @@ static PyObject *
builtin_locals_impl(PyObject *module)
/*[clinic end generated code: output=b46c94015ce11448 input=7874018d478d5c4b]*/
{
return _PyEval_GetFrameLocals();
PyObject *locals;
if (_PyEval_GetFrame() != NULL) {
locals = _PyEval_GetFrameLocals();
assert(locals != NULL || PyErr_Occurred());
return locals;
}
PyThreadState *tstate = _PyThreadState_GET();
locals = _PyEval_GetGlobalsFromRunningMain(tstate);
if (locals == NULL) {
if (_PyErr_Occurred(tstate)) {
return NULL;
}
Py_RETURN_NONE;
}
return Py_NewRef(locals);
}


Expand Down Expand Up @@ -2623,7 +2675,20 @@ builtin_vars(PyObject *self, PyObject *args)
if (!PyArg_UnpackTuple(args, "vars", 0, 1, &v))
return NULL;
if (v == NULL) {
d = _PyEval_GetFrameLocals();
if (_PyEval_GetFrame() != NULL) {
d = _PyEval_GetFrameLocals();
}
PyThreadState *tstate = _PyThreadState_GET();
d = _PyEval_GetGlobalsFromRunningMain(tstate);
if (d == NULL) {
if (!_PyErr_Occurred(tstate)) {
d = _PyEval_GetFrameLocals();
assert(_PyErr_Occurred(tstate));
}
}
else {
Py_INCREF(d);
}
}
else {
if (PyObject_GetOptionalAttr(v, &_Py_ID(__dict__), &d) == 0) {
Expand Down
Loading
Loading