From bf823bc851008491d3b5c06016953a11766c2106 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Fri, 23 Oct 2020 23:40:15 +0200 Subject: [PATCH 1/2] Add __builtins__ to globals argument of `py::exec` and `py::eval` if not present --- include/pybind11/eval.h | 15 +++++++++++++++ tests/test_eval.cpp | 8 ++++++++ tests/test_eval.py | 8 ++++++++ 3 files changed, 31 insertions(+) diff --git a/include/pybind11/eval.h b/include/pybind11/eval.h index 05d7bb6140..f9fcce2106 100644 --- a/include/pybind11/eval.h +++ b/include/pybind11/eval.h @@ -31,6 +31,15 @@ object eval(str expr, object global = globals(), object local = object()) { if (!local) local = global; +#if PY_VERSION_HEX < 0x03080000 + // Running exec and eval on Python 2 and 3 adds `builtins` module under + // `__builtins__` key to globals if not yet present. + // Python 3.8 made PyRun_String behave similarly. Let's also do that for + // older versions, for consistency. + if (!global.contains("__builtins__")) + global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); +#endif + /* PyRun_String does not accept a PyObject / encoding specifier, this seems to be the only alternative */ std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr; @@ -85,6 +94,12 @@ object eval_file(str fname, object global = globals(), object local = object()) if (!local) local = global; +#if PY_VERSION_HEX < 0x03080000 + // See `eval` above. + if (!global.contains("__builtins__")) + global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); +#endif + int start; switch (mode) { case eval_expr: start = Py_eval_input; break; diff --git a/tests/test_eval.cpp b/tests/test_eval.cpp index 03d0fb3666..5416c2ec4d 100644 --- a/tests/test_eval.cpp +++ b/tests/test_eval.cpp @@ -88,4 +88,12 @@ TEST_SUBMODULE(eval_, m) { } return false; }); + + // test_eval_empty_globals + m.def("eval_empty_globals", [](py::object global) { + if (global.is_none()) + global = py::dict(); + auto int_class = py::eval("isinstance(42, int)", global); + return global; + }); } diff --git a/tests/test_eval.py b/tests/test_eval.py index b6f9d1881d..1bb05af05e 100644 --- a/tests/test_eval.py +++ b/tests/test_eval.py @@ -25,3 +25,11 @@ def test_eval_file(): assert m.test_eval_file(filename) assert m.test_eval_file_failure() + + +def test_eval_empty_globals(): + assert "__builtins__" in m.eval_empty_globals(None) + + g = {} + assert "__builtins__" in m.eval_empty_globals(g) + assert "__builtins__" in g From 120510bbd58c7c6e6fb7041dba69ba510ac970c6 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 27 Oct 2020 19:26:04 +0100 Subject: [PATCH 2/2] Refactor into inline ensure_builtins_in_globals function --- include/pybind11/eval.h | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/include/pybind11/eval.h b/include/pybind11/eval.h index f9fcce2106..fa6b8af479 100644 --- a/include/pybind11/eval.h +++ b/include/pybind11/eval.h @@ -14,6 +14,22 @@ #include "pybind11.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +inline void ensure_builtins_in_globals(object &global) { + #if PY_VERSION_HEX < 0x03080000 + // Running exec and eval on Python 2 and 3 adds `builtins` module under + // `__builtins__` key to globals if not yet present. + // Python 3.8 made PyRun_String behave similarly. Let's also do that for + // older versions, for consistency. + if (!global.contains("__builtins__")) + global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); + #else + (void) global; + #endif +} + +PYBIND11_NAMESPACE_END(detail) enum eval_mode { /// Evaluate a string containing an isolated expression @@ -31,14 +47,7 @@ object eval(str expr, object global = globals(), object local = object()) { if (!local) local = global; -#if PY_VERSION_HEX < 0x03080000 - // Running exec and eval on Python 2 and 3 adds `builtins` module under - // `__builtins__` key to globals if not yet present. - // Python 3.8 made PyRun_String behave similarly. Let's also do that for - // older versions, for consistency. - if (!global.contains("__builtins__")) - global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); -#endif + detail::ensure_builtins_in_globals(global); /* PyRun_String does not accept a PyObject / encoding specifier, this seems to be the only alternative */ @@ -94,11 +103,7 @@ object eval_file(str fname, object global = globals(), object local = object()) if (!local) local = global; -#if PY_VERSION_HEX < 0x03080000 - // See `eval` above. - if (!global.contains("__builtins__")) - global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE); -#endif + detail::ensure_builtins_in_globals(global); int start; switch (mode) {