Skip to content

Commit 3a37d33

Browse files
Add __builtins__ to globals argument of py::exec and py::eval if not present (#2616)
* Add __builtins__ to globals argument of `py::exec` and `py::eval` if not present * Refactor into inline ensure_builtins_in_globals function
1 parent ace4deb commit 3a37d33

File tree

3 files changed

+36
-0
lines changed

3 files changed

+36
-0
lines changed

include/pybind11/eval.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@
1414
#include "pybind11.h"
1515

1616
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
17+
PYBIND11_NAMESPACE_BEGIN(detail)
18+
19+
inline void ensure_builtins_in_globals(object &global) {
20+
#if PY_VERSION_HEX < 0x03080000
21+
// Running exec and eval on Python 2 and 3 adds `builtins` module under
22+
// `__builtins__` key to globals if not yet present.
23+
// Python 3.8 made PyRun_String behave similarly. Let's also do that for
24+
// older versions, for consistency.
25+
if (!global.contains("__builtins__"))
26+
global["__builtins__"] = module_::import(PYBIND11_BUILTINS_MODULE);
27+
#else
28+
(void) global;
29+
#endif
30+
}
31+
32+
PYBIND11_NAMESPACE_END(detail)
1733

1834
enum eval_mode {
1935
/// Evaluate a string containing an isolated expression
@@ -31,6 +47,8 @@ object eval(str expr, object global = globals(), object local = object()) {
3147
if (!local)
3248
local = global;
3349

50+
detail::ensure_builtins_in_globals(global);
51+
3452
/* PyRun_String does not accept a PyObject / encoding specifier,
3553
this seems to be the only alternative */
3654
std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr;
@@ -85,6 +103,8 @@ object eval_file(str fname, object global = globals(), object local = object())
85103
if (!local)
86104
local = global;
87105

106+
detail::ensure_builtins_in_globals(global);
107+
88108
int start;
89109
switch (mode) {
90110
case eval_expr: start = Py_eval_input; break;

tests/test_eval.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,12 @@ TEST_SUBMODULE(eval_, m) {
8888
}
8989
return false;
9090
});
91+
92+
// test_eval_empty_globals
93+
m.def("eval_empty_globals", [](py::object global) {
94+
if (global.is_none())
95+
global = py::dict();
96+
auto int_class = py::eval("isinstance(42, int)", global);
97+
return global;
98+
});
9199
}

tests/test_eval.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ def test_eval_file():
2525
assert m.test_eval_file(filename)
2626

2727
assert m.test_eval_file_failure()
28+
29+
30+
def test_eval_empty_globals():
31+
assert "__builtins__" in m.eval_empty_globals(None)
32+
33+
g = {}
34+
assert "__builtins__" in m.eval_empty_globals(g)
35+
assert "__builtins__" in g

0 commit comments

Comments
 (0)