From 54ea52bdcfa2e65b5be92b3107571283744af0c1 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 6 Jun 2025 11:41:12 -0600 Subject: [PATCH 01/11] Add tests. --- Lib/test/test_interpreters/test_api.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 1403cd145b6787..4a29d82ece8afe 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -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. + 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, + ] + 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() From 39afdace98842b68b84a60068178b825aefffb9f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 13 Jun 2025 13:50:53 -0600 Subject: [PATCH 02/11] Refactor before adding _PyEval_GetGlobalsFromRunningMain(). --- Include/internal/pycore_ceval.h | 6 ++ Python/bltinmodule.c | 115 +++++++++++++++++++------------- Python/ceval.c | 50 +++++++++++++- Python/import.c | 15 +++-- 4 files changed, 131 insertions(+), 55 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 239177deb4a948..97b95218e7026c 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -239,6 +239,12 @@ static inline void _Py_LeaveRecursiveCall(void) { extern _PyInterpreterFrame* _PyEval_GetFrame(void); +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 diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index e08c63924ca16d..dcfb49a4a18b8f 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -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; @@ -970,35 +971,40 @@ 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); - } - - if (globals == NULL || locals == NULL) { PyErr_SetString(PyExc_TypeError, "eval must be given globals and locals " "when called without a frame"); - goto error; + return NULL; } - int r = PyDict_Contains(globals, &_Py_ID(__builtins__)); - if (r == 0) { - r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins()); + if (locals != Py_None) { + Py_INCREF(locals); + } + else if (fromframe) { + locals = _PyEval_GetFrameLocals(); + if (locals == NULL) { + assert(PyErr_Occurred()); + Py_DECREF(globals); + return NULL; + } } - if (r < 0) { + else { + locals = Py_NewRef(globals); + } + + if (_PyEval_EnsureBuiltins(tstate, globals, 0, NULL) < 0) { goto error; } @@ -1039,6 +1045,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, } error: + Py_XDECREF(globals); Py_XDECREF(locals); return result; } @@ -1069,29 +1076,38 @@ 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); - } - if (!globals || !locals) { - PyErr_SetString(PyExc_SystemError, - "globals and locals cannot be NULL"); + assert(globals != NULL); + Py_INCREF(globals); + } + else { + PyErr_SetString(PyExc_SystemError, + "globals and locals cannot be NULL"); + goto error; + } + + 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)) { @@ -1105,11 +1121,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; } @@ -1186,11 +1199,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; } @@ -1240,10 +1255,12 @@ static PyObject * builtin_globals_impl(PyObject *module) /*[clinic end generated code: output=e5dd1527067b94d2 input=9327576f92bb48ba]*/ { - PyObject *d; - - d = PyEval_GetGlobals(); - return Py_XNewRef(d); + if (_PyEval_GetFrame() != NULL) { + PyObject *globals = PyEval_GetGlobals(); + assert(globals != NULL); + return Py_NewRef(globals); + } + Py_RETURN_NONE; } @@ -1887,7 +1904,13 @@ 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; + } + Py_RETURN_NONE; } diff --git a/Python/ceval.c b/Python/ceval.c index 4cfe4bb88f4e48..178c6414bd3fc4 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2746,10 +2746,9 @@ _PyEval_GetFrameLocals(void) return locals; } -PyObject * -PyEval_GetGlobals(void) +static PyObject * +_PyEval_GetGlobals(PyThreadState *tstate) { - PyThreadState *tstate = _PyThreadState_GET(); _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate); if (current_frame == NULL) { return NULL; @@ -2757,6 +2756,51 @@ PyEval_GetGlobals(void) return current_frame->f_globals; } +PyObject * +PyEval_GetGlobals(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + return _PyEval_GetGlobals(tstate); +} + +int +_PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals, int usemod, + PyObject **p_builtins) +{ + PyObject *builtins = NULL; + if (PyMapping_GetOptionalItem(globals, &_Py_ID(__builtins__), &builtins) < 0) { + return -1; + } + if (builtins == NULL) { + if (usemod) { + builtins = + PyImport_ImportModuleLevel("builtins", NULL, NULL, NULL, 0); + if (builtins == NULL) { + return -1; + } + } + else { + builtins = PyEval_GetBuiltins(); + if (builtins == NULL) { + assert(_PyErr_Occurred(tstate)); + return -1; + } + Py_INCREF(builtins); + } + if (PyDict_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { + Py_DECREF(builtins); + return -1; + } + } + if (p_builtins != NULL) { + *p_builtins = builtins; + } + else { + Py_DECREF(builtins); + } + return 0; +} + PyObject* PyEval_GetFrameLocals(void) { diff --git a/Python/import.c b/Python/import.c index 184dede335dfd6..5732cd9adaa410 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3960,25 +3960,28 @@ PyImport_Import(PyObject *module_name) } /* Get the builtins from current globals */ - globals = PyEval_GetGlobals(); + globals = PyEval_GetGlobals(); // borrowed if (globals != NULL) { Py_INCREF(globals); + // XXX Use _PyEval_EnsureBuiltins()? builtins = PyObject_GetItem(globals, &_Py_ID(__builtins__)); if (builtins == NULL) { // XXX Fall back to interp->builtins or sys.modules['builtins']? goto err; } } + else if (_PyErr_Occurred(tstate)) { + goto err; + } else { /* No globals -- use standard builtins, and fake globals */ - builtins = PyImport_ImportModuleLevel("builtins", - NULL, NULL, NULL, 0); - if (builtins == NULL) { + globals = PyDict_New(); + if (globals == NULL) { goto err; } - globals = Py_BuildValue("{OO}", &_Py_ID(__builtins__), builtins); - if (globals == NULL) + if (_PyEval_EnsureBuiltins(tstate, globals, 1, &builtins) < 0) { goto err; + } } /* Get the __import__ function from the builtins */ From 3cfddbc5197f07dc028574749c66a474900b7b13 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 13 Jun 2025 15:27:54 -0600 Subject: [PATCH 03/11] Fall back to __main__.__dict__ if running main. --- Include/internal/pycore_ceval.h | 1 + Objects/object.c | 18 ++++++++-- Python/bltinmodule.c | 64 +++++++++++++++++++++++++++------ Python/ceval.c | 16 +++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 97b95218e7026c..acac8d47456264 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -239,6 +239,7 @@ static inline void _Py_LeaveRecursiveCall(void) { extern _PyInterpreterFrame* _PyEval_GetFrame(void); +extern PyObject * _PyEval_GetGlobalsFromRunningMain(PyThreadState *); extern int _PyEval_EnsureBuiltins( PyThreadState *, PyObject *, diff --git a/Objects/object.c b/Objects/object.c index 9fe61ba7f1593a..c208894f3f9837 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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); + 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); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index dcfb49a4a18b8f..dc220e997ad22d 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -983,10 +983,16 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, Py_INCREF(globals); } else { - PyErr_SetString(PyExc_TypeError, - "eval must be given globals and locals " - "when called without a frame"); - return NULL; + 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 (locals != Py_None) { @@ -1090,9 +1096,15 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, Py_INCREF(globals); } else { - PyErr_SetString(PyExc_SystemError, - "globals and locals cannot be NULL"); - goto error; + globals = _PyEval_GetGlobalsFromRunningMain(tstate); + if (globals == NULL) { + if (!_PyErr_Occurred(tstate)) { + PyErr_SetString(PyExc_SystemError, + "globals and locals cannot be NULL"); + } + goto error; + } + Py_INCREF(globals); } if (locals != Py_None) { @@ -1255,12 +1267,21 @@ static PyObject * builtin_globals_impl(PyObject *module) /*[clinic end generated code: output=e5dd1527067b94d2 input=9327576f92bb48ba]*/ { + PyObject *globals; if (_PyEval_GetFrame() != NULL) { - PyObject *globals = PyEval_GetGlobals(); + globals = PyEval_GetGlobals(); assert(globals != NULL); return Py_NewRef(globals); } - Py_RETURN_NONE; + PyThreadState *tstate = _PyThreadState_GET(); + globals = _PyEval_GetGlobalsFromRunningMain(tstate); + if (globals == NULL) { + if (_PyErr_Occurred(tstate)) { + return NULL; + } + Py_RETURN_NONE; + } + return Py_NewRef(globals); } @@ -1910,7 +1931,15 @@ builtin_locals_impl(PyObject *module) assert(locals != NULL || PyErr_Occurred()); return locals; } - Py_RETURN_NONE; + PyThreadState *tstate = _PyThreadState_GET(); + locals = _PyEval_GetGlobalsFromRunningMain(tstate); + if (locals == NULL) { + if (_PyErr_Occurred(tstate)) { + return NULL; + } + Py_RETURN_NONE; + } + return Py_NewRef(locals); } @@ -2646,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) { diff --git a/Python/ceval.c b/Python/ceval.c index 178c6414bd3fc4..8635796fde3e78 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2763,6 +2763,22 @@ PyEval_GetGlobals(void) return _PyEval_GetGlobals(tstate); } +PyObject * +_PyEval_GetGlobalsFromRunningMain(PyThreadState *tstate) +{ + if (!_PyInterpreterState_IsRunningMain(tstate->interp)) { + return NULL; + } + PyObject *mod = _Py_GetMainModule(tstate); + if (_Py_CheckMainModule(mod) < 0) { + Py_XDECREF(mod); + return NULL; + } + PyObject *globals = PyModule_GetDict(mod); // borrowed + Py_DECREF(mod); + return globals; +} + int _PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals, int usemod, PyObject **p_builtins) From 1d5b7bd241c1caa919266103b490fd9e45b7e6c1 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 10:21:22 -0600 Subject: [PATCH 04/11] Fix the fallback. --- Objects/object.c | 20 +++++++++++--------- Python/bltinmodule.c | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index c208894f3f9837..341bf06c5404f4 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2086,16 +2086,18 @@ _dir_locals(void) if (_PyEval_GetFrame() != NULL) { locals = _PyEval_GetFrameLocals(); } - PyThreadState *tstate = _PyThreadState_GET(); - locals = _PyEval_GetGlobalsFromRunningMain(tstate); - if (locals == NULL) { - if (!_PyErr_Occurred(tstate)) { - locals = _PyEval_GetFrameLocals(); - assert(_PyErr_Occurred(tstate)); - } - } else { - Py_INCREF(locals); + PyThreadState *tstate = _PyThreadState_GET(); + locals = _PyEval_GetGlobalsFromRunningMain(tstate); + if (locals == NULL) { + if (!_PyErr_Occurred(tstate)) { + locals = _PyEval_GetFrameLocals(); + assert(_PyErr_Occurred(tstate)); + } + } + else { + Py_INCREF(locals); + } } if (locals == NULL) { return NULL; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index dc220e997ad22d..3687e96e08dea2 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2678,16 +2678,18 @@ builtin_vars(PyObject *self, PyObject *args) 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); + 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 { From 2f8caf0858ce1c6ec9d6fcb5104c45256697c7e4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 10:24:00 -0600 Subject: [PATCH 05/11] Fix a typo. --- Lib/test/test_interpreters/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 4a29d82ece8afe..ee40c25409354c 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1415,7 +1415,7 @@ def test_call_invalid(self): interp.call(func, op, 'eggs!') def test_callable_requires_frame(self): - # There are various functions tha require a current frame. + # There are various functions that require a current frame. interp = interpreters.create() for call, expected in [ ((eval, '[1, 2, 3]'), From 77fbd1ff37ddd5291a45623d3de5c7c199ee50c4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 10:24:27 -0600 Subject: [PATCH 06/11] Use a clearer variable name in a test. --- Lib/test/test_interpreters/test_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index ee40c25409354c..53f7fd99cf2cd3 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1429,7 +1429,7 @@ def test_callable_requires_frame(self): res = interp.call(*call) self.assertEqual(res, expected) - notshareable = [ + result_not_pickleable = [ globals, locals, vars, @@ -1441,7 +1441,7 @@ def test_callable_requires_frame(self): dir: list, }.items(): with self.subTest(str(func)): - if func in notshareable: + if func in result_not_pickleable: with self.assertRaises(interpreters.NotShareableError): interp.call(func) else: From 2bf3a4b493736fe114ef80f8e883cc85a429808b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 13:29:25 -0600 Subject: [PATCH 07/11] Fix type handling in _PyEval_EnsureBuiltins(). --- Python/ceval.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index 8635796fde3e78..44322dadb2a246 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2784,8 +2784,17 @@ _PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals, int usemod, PyObject **p_builtins) { PyObject *builtins = NULL; - if (PyMapping_GetOptionalItem(globals, &_Py_ID(__builtins__), &builtins) < 0) { - return -1; + if (PyDict_Check(globals)) { + if (PyDict_GetItemRef(globals, &_Py_ID(__builtins__), &builtins) < 0) { + return -1; + } + } + else { + if (PyMapping_GetOptionalItem( + globals, &_Py_ID(__builtins__), &builtins) < 0) + { + return -1; + } } if (builtins == NULL) { if (usemod) { @@ -2803,9 +2812,17 @@ _PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals, int usemod, } Py_INCREF(builtins); } - if (PyDict_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { - Py_DECREF(builtins); - return -1; + if (PyDict_Check(globals)) { + if (PyDict_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { + Py_DECREF(builtins); + return -1; + } + } + else { + if (PyObject_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { + Py_DECREF(builtins); + return -1; + } } } if (p_builtins != NULL) { From 69109c62fb589f6c46ac3e6330c9a683243b3d10 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 15:04:33 -0600 Subject: [PATCH 08/11] Add _PyEval_EnsureBuiltinsWithModule(). --- Include/internal/pycore_ceval.h | 5 +- Python/bltinmodule.c | 4 +- Python/ceval.c | 90 +++++++++++++++++++++++---------- Python/import.c | 2 +- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index acac8d47456264..18623cc8f1c945 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -243,7 +243,10 @@ extern PyObject * _PyEval_GetGlobalsFromRunningMain(PyThreadState *); extern int _PyEval_EnsureBuiltins( PyThreadState *, PyObject *, - int usemod, + PyObject **p_builtins); +extern int _PyEval_EnsureBuiltinsWithModule( + PyThreadState *, + PyObject *, PyObject **p_builtins); PyAPI_FUNC(PyObject *)_Py_MakeCoro(PyFunctionObject *func); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 3687e96e08dea2..51d7297ec243cc 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1010,7 +1010,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, locals = Py_NewRef(globals); } - if (_PyEval_EnsureBuiltins(tstate, globals, 0, NULL) < 0) { + if (_PyEval_EnsureBuiltins(tstate, globals, NULL) < 0) { goto error; } @@ -1134,7 +1134,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, goto error; } - if (_PyEval_EnsureBuiltins(tstate, globals, 0, NULL) < 0) { + if (_PyEval_EnsureBuiltins(tstate, globals, NULL) < 0) { goto error; } diff --git a/Python/ceval.c b/Python/ceval.c index 44322dadb2a246..3da6f61f5acde5 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2779,50 +2779,86 @@ _PyEval_GetGlobalsFromRunningMain(PyThreadState *tstate) return globals; } -int -_PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals, int usemod, - PyObject **p_builtins) +static PyObject * +get_globals_builtins(PyObject *globals) { PyObject *builtins = NULL; if (PyDict_Check(globals)) { if (PyDict_GetItemRef(globals, &_Py_ID(__builtins__), &builtins) < 0) { - return -1; + return NULL; } } else { if (PyMapping_GetOptionalItem( globals, &_Py_ID(__builtins__), &builtins) < 0) { + return NULL; + } + } + return builtins; +} + +static int +set_globals_builtins(PyObject *globals, PyObject *builtins) +{ + if (PyDict_Check(globals)) { + if (PyDict_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { + return -1; + } + } + else { + if (PyObject_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { return -1; } } + return 0; +} + +int +_PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals, + PyObject **p_builtins) +{ + PyObject *builtins = get_globals_builtins(globals); if (builtins == NULL) { - if (usemod) { - builtins = - PyImport_ImportModuleLevel("builtins", NULL, NULL, NULL, 0); - if (builtins == NULL) { - return -1; - } + if (_PyErr_Occurred(tstate)) { + return -1; } - else { - builtins = PyEval_GetBuiltins(); - if (builtins == NULL) { - assert(_PyErr_Occurred(tstate)); - return -1; - } - Py_INCREF(builtins); + builtins = PyEval_GetBuiltins(); // borrowed + if (builtins == NULL) { + assert(_PyErr_Occurred(tstate)); + return -1; } - if (PyDict_Check(globals)) { - if (PyDict_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { - Py_DECREF(builtins); - return -1; - } + Py_INCREF(builtins); + if (set_globals_builtins(globals, builtins) < 0) { + Py_DECREF(builtins); + return -1; } - else { - if (PyObject_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) { - Py_DECREF(builtins); - return -1; - } + } + if (p_builtins != NULL) { + *p_builtins = builtins; + } + else { + Py_DECREF(builtins); + } + return 0; +} + +int +_PyEval_EnsureBuiltinsWithModule(PyThreadState *tstate, PyObject *globals, + PyObject **p_builtins) +{ + PyObject *builtins = get_globals_builtins(globals); + if (builtins == NULL) { + if (_PyErr_Occurred(tstate)) { + return -1; + } + builtins = PyImport_ImportModuleLevel("builtins", NULL, NULL, NULL, 0); + if (builtins == NULL) { + return -1; + } + if (set_globals_builtins(globals, builtins) < 0) { + Py_DECREF(builtins); + return -1; } } if (p_builtins != NULL) { diff --git a/Python/import.c b/Python/import.c index 5732cd9adaa410..73b94d0dd2a1b1 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3979,7 +3979,7 @@ PyImport_Import(PyObject *module_name) if (globals == NULL) { goto err; } - if (_PyEval_EnsureBuiltins(tstate, globals, 1, &builtins) < 0) { + if (_PyEval_EnsureBuiltinsWithModule(tstate, globals, &builtins) < 0) { goto err; } } From 44a39a5a54c3ac226a36882ad8dc16f80914fcbf Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 13:07:54 -0600 Subject: [PATCH 09/11] more tests --- Lib/test/test_interpreters/test_api.py | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 53f7fd99cf2cd3..238db23aa7d350 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1356,6 +1356,78 @@ def {funcname}(): with self.assertRaises(interpreters.NotShareableError): interp.call(defs.spam_returns_arg, arg) + def test_globals_from_builtins(self): + # The builtins exec(), eval(), globals(), locals(), vars(), + # and dir() each runs relative to the target interpreter's + # __main__ module, when called directly. However, + # globals(), locals(), and vars() don't work when called + # directly so we don't check them. + from _frozen_importlib import BuiltinImporter + interp = interpreters.create() + + names = interp.call(dir) + self.assertEqual(names, [ + '__builtins__', + '__doc__', + '__loader__', + '__name__', + '__package__', + '__spec__', + ]) + + values = {name: interp.call(eval, name) + for name in names if name != '__builtins__'} + self.assertEqual(values, { + '__name__': '__main__', + '__doc__': None, + '__spec__': None, # It wasn't imported, so no module spec? + '__package__': None, + '__loader__': BuiltinImporter, + }) + with self.assertRaises(ExecutionFailed): + interp.call(eval, 'spam'), + + interp.call(exec, f'assert dir() == {names}') + + # Update the interpreter's __main__. + interp.prepare_main(spam=42) + expected = names + ['spam'] + + names = interp.call(dir) + self.assertEqual(names, expected) + + value = interp.call(eval, 'spam') + self.assertEqual(value, 42) + + interp.call(exec, f'assert dir() == {expected}, dir()') + + def test_globals_from_stateless_func(self): + # A stateless func, which doesn't depend on any globals, + # doesn't go through pickle, so it runs in __main__. + def set_global(name, value): + globals()[name] = value + + def get_global(name): + return globals().get(name) + + interp = interpreters.create() + + modname = interp.call(get_global, '__name__') + self.assertEqual(modname, '__main__') + + res = interp.call(get_global, 'spam') + self.assertIsNone(res) + + interp.exec('spam = True') + res = interp.call(get_global, 'spam') + self.assertTrue(res) + + interp.call(set_global, 'spam', 42) + res = interp.call(get_global, 'spam') + self.assertEqual(res, 42) + + interp.exec('assert spam == 42, repr(spam)') + def test_raises(self): interp = interpreters.create() with self.assertRaises(ExecutionFailed): From cbc4d3f93df266166fd72e595e94e7dfa70cb6b9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 15:43:43 -0600 Subject: [PATCH 10/11] Set __globals__ to __main__.__dict__ for stateless funcs. --- Python/crossinterp_data_lookup.h | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h index b16f38b847fc66..e5056f5f038255 100644 --- a/Python/crossinterp_data_lookup.h +++ b/Python/crossinterp_data_lookup.h @@ -695,16 +695,26 @@ _PyFunction_FromXIData(_PyXIData_t *xidata) return NULL; } // Create a new function. + // For stateless functions (no globals) we use __main__ as __globals__, + // just like we do for builtins like exec(). assert(PyCode_Check(code)); - PyObject *globals = PyDict_New(); + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *globals = _PyEval_GetGlobalsFromRunningMain(tstate); // borrowed if (globals == NULL) { - Py_DECREF(code); - return NULL; + if (_PyErr_Occurred(tstate)) { + Py_DECREF(code); + return NULL; + } + globals = PyDict_New(); + if (globals == NULL) { + Py_DECREF(code); + return NULL; + } } - PyThreadState *tstate = _PyThreadState_GET(); - if (PyDict_SetItem(globals, &_Py_ID(__builtins__), - tstate->interp->builtins) < 0) - { + else { + Py_INCREF(globals); + } + if (_PyEval_EnsureBuiltins(tstate, globals, NULL) < 0) { Py_DECREF(code); Py_DECREF(globals); return NULL; From c93f890f8161b27286272adb31b88fa5a3f2cecb Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 16 Jun 2025 15:46:26 -0600 Subject: [PATCH 11/11] Move tests down. --- Lib/test/test_interpreters/test_api.py | 144 ++++++++++++------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 238db23aa7d350..a409c1a691818e 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1356,78 +1356,6 @@ def {funcname}(): with self.assertRaises(interpreters.NotShareableError): interp.call(defs.spam_returns_arg, arg) - def test_globals_from_builtins(self): - # The builtins exec(), eval(), globals(), locals(), vars(), - # and dir() each runs relative to the target interpreter's - # __main__ module, when called directly. However, - # globals(), locals(), and vars() don't work when called - # directly so we don't check them. - from _frozen_importlib import BuiltinImporter - interp = interpreters.create() - - names = interp.call(dir) - self.assertEqual(names, [ - '__builtins__', - '__doc__', - '__loader__', - '__name__', - '__package__', - '__spec__', - ]) - - values = {name: interp.call(eval, name) - for name in names if name != '__builtins__'} - self.assertEqual(values, { - '__name__': '__main__', - '__doc__': None, - '__spec__': None, # It wasn't imported, so no module spec? - '__package__': None, - '__loader__': BuiltinImporter, - }) - with self.assertRaises(ExecutionFailed): - interp.call(eval, 'spam'), - - interp.call(exec, f'assert dir() == {names}') - - # Update the interpreter's __main__. - interp.prepare_main(spam=42) - expected = names + ['spam'] - - names = interp.call(dir) - self.assertEqual(names, expected) - - value = interp.call(eval, 'spam') - self.assertEqual(value, 42) - - interp.call(exec, f'assert dir() == {expected}, dir()') - - def test_globals_from_stateless_func(self): - # A stateless func, which doesn't depend on any globals, - # doesn't go through pickle, so it runs in __main__. - def set_global(name, value): - globals()[name] = value - - def get_global(name): - return globals().get(name) - - interp = interpreters.create() - - modname = interp.call(get_global, '__name__') - self.assertEqual(modname, '__main__') - - res = interp.call(get_global, 'spam') - self.assertIsNone(res) - - interp.exec('spam = True') - res = interp.call(get_global, 'spam') - self.assertTrue(res) - - interp.call(set_global, 'spam', 42) - res = interp.call(get_global, 'spam') - self.assertEqual(res, 42) - - interp.exec('assert spam == 42, repr(spam)') - def test_raises(self): interp = interpreters.create() with self.assertRaises(ExecutionFailed): @@ -1521,6 +1449,78 @@ def test_callable_requires_frame(self): self.assertIsInstance(res, expectedtype) self.assertIn('__builtins__', res) + def test_globals_from_builtins(self): + # The builtins exec(), eval(), globals(), locals(), vars(), + # and dir() each runs relative to the target interpreter's + # __main__ module, when called directly. However, + # globals(), locals(), and vars() don't work when called + # directly so we don't check them. + from _frozen_importlib import BuiltinImporter + interp = interpreters.create() + + names = interp.call(dir) + self.assertEqual(names, [ + '__builtins__', + '__doc__', + '__loader__', + '__name__', + '__package__', + '__spec__', + ]) + + values = {name: interp.call(eval, name) + for name in names if name != '__builtins__'} + self.assertEqual(values, { + '__name__': '__main__', + '__doc__': None, + '__spec__': None, # It wasn't imported, so no module spec? + '__package__': None, + '__loader__': BuiltinImporter, + }) + with self.assertRaises(ExecutionFailed): + interp.call(eval, 'spam'), + + interp.call(exec, f'assert dir() == {names}') + + # Update the interpreter's __main__. + interp.prepare_main(spam=42) + expected = names + ['spam'] + + names = interp.call(dir) + self.assertEqual(names, expected) + + value = interp.call(eval, 'spam') + self.assertEqual(value, 42) + + interp.call(exec, f'assert dir() == {expected}, dir()') + + def test_globals_from_stateless_func(self): + # A stateless func, which doesn't depend on any globals, + # doesn't go through pickle, so it runs in __main__. + def set_global(name, value): + globals()[name] = value + + def get_global(name): + return globals().get(name) + + interp = interpreters.create() + + modname = interp.call(get_global, '__name__') + self.assertEqual(modname, '__main__') + + res = interp.call(get_global, 'spam') + self.assertIsNone(res) + + interp.exec('spam = True') + res = interp.call(get_global, 'spam') + self.assertTrue(res) + + interp.call(set_global, 'spam', 42) + res = interp.call(get_global, 'spam') + self.assertEqual(res, 42) + + interp.exec('assert spam == 42, repr(spam)') + def test_call_in_thread(self): interp = interpreters.create()