From b0b224233e481d979430a54d257d871424ff19fb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Apr 2012 00:57:45 +0200 Subject: [PATCH] Issue #14385: Support other types than dict for __builtins__ It is now possible to use a custom type for the __builtins__ namespace, instead of a dict. It can be used for sandboxing for example. Raise also a NameError instead of ImportError if __build_class__ name if not found in __builtins__. --- Lib/test/test_builtin.py | 33 +++++++++ Misc/NEWS | 5 ++ Objects/frameobject.c | 6 +- Python/ceval.c | 150 +++++++++++++++++++++++++-------------- 4 files changed, 138 insertions(+), 56 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 88bccd9f332..dfe64bf09c3 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -554,6 +554,39 @@ class BuiltinTest(unittest.TestCase): del l['__builtins__'] self.assertEqual((g, l), ({'a': 1}, {'b': 2})) + def test_exec_globals(self): + code = compile("print('Hello World!')", "", "exec") + # no builtin function + self.assertRaisesRegex(NameError, "name 'print' is not defined", + exec, code, {'__builtins__': {}}) + # __builtins__ must be a mapping type + self.assertRaises(TypeError, + exec, code, {'__builtins__': 123}) + + # no __build_class__ function + code = compile("class A: pass", "", "exec") + self.assertRaisesRegex(NameError, "__build_class__ not found", + exec, code, {'__builtins__': {}}) + + class frozendict_error(Exception): + pass + + class frozendict(dict): + def __setitem__(self, key, value): + raise frozendict_error("frozendict is readonly") + + # read-only builtins + frozen_builtins = frozendict(__builtins__) + code = compile("__builtins__['superglobal']=2; print(superglobal)", "test", "exec") + self.assertRaises(frozendict_error, + exec, code, {'__builtins__': frozen_builtins}) + + # read-only globals + namespace = frozendict({}) + code = compile("x=1", "test", "exec") + self.assertRaises(frozendict_error, + exec, code, namespace) + def test_exec_redirected(self): savestdout = sys.stdout sys.stdout = None # Whatever that cannot flush() diff --git a/Misc/NEWS b/Misc/NEWS index 595136d4357..598b9a83869 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,11 @@ What's New in Python 3.3.0 Alpha 3? Core and Builtins ----------------- +- Issue #14385: It is now possible to use a custom type for the __builtins__ + namespace, instead of a dict. It can be used for sandboxing for example. + Raise also a NameError instead of ImportError if __build_class__ name if not + found in __builtins__. + - Issue #12599: Be more strict in accepting None compared to a false-like object for importlib.util.module_for_loader and importlib.machinery.PathFinder. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index c8b8b1d2b31..62085562e3d 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -614,10 +614,8 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, if (builtins) { if (PyModule_Check(builtins)) { builtins = PyModule_GetDict(builtins); - assert(!builtins || PyDict_Check(builtins)); + assert(builtins != NULL); } - else if (!PyDict_Check(builtins)) - builtins = NULL; } if (builtins == NULL) { /* No builtins! Make up a minimal one @@ -636,7 +634,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, /* If we share the globals, we share the builtins. Save a lookup and a call. */ builtins = back->f_builtins; - assert(builtins != NULL && PyDict_Check(builtins)); + assert(builtins != NULL); Py_INCREF(builtins); } if (code->co_zombieframe != NULL) { diff --git a/Python/ceval.c b/Python/ceval.c index 7908d444e18..a32d685f08c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1932,11 +1932,26 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) TARGET(LOAD_BUILD_CLASS) { _Py_IDENTIFIER(__build_class__); - x = _PyDict_GetItemId(f->f_builtins, &PyId___build_class__); - if (x == NULL) { - PyErr_SetString(PyExc_ImportError, - "__build_class__ not found"); - break; + + if (PyDict_CheckExact(f->f_builtins)) { + x = _PyDict_GetItemId(f->f_builtins, &PyId___build_class__); + if (x == NULL) { + PyErr_SetString(PyExc_NameError, + "__build_class__ not found"); + break; + } + } + else { + PyObject *build_class_str = _PyUnicode_FromId(&PyId___build_class__); + if (build_class_str == NULL) + break; + x = PyObject_GetItem(f->f_builtins, build_class_str); + if (x == NULL) { + if (PyErr_ExceptionMatches(PyExc_KeyError)) + PyErr_SetString(PyExc_NameError, + "__build_class__ not found"); + break; + } } Py_INCREF(x); PUSH(x); @@ -2078,12 +2093,24 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) if (x == NULL) { x = PyDict_GetItem(f->f_globals, w); if (x == NULL) { - x = PyDict_GetItem(f->f_builtins, w); - if (x == NULL) { - format_exc_check_arg( - PyExc_NameError, - NAME_ERROR_MSG, w); - break; + if (PyDict_CheckExact(f->f_builtins)) { + x = PyDict_GetItem(f->f_builtins, w); + if (x == NULL) { + format_exc_check_arg( + PyExc_NameError, + NAME_ERROR_MSG, w); + break; + } + } + else { + x = PyObject_GetItem(f->f_builtins, w); + if (x == NULL) { + if (PyErr_ExceptionMatches(PyExc_KeyError)) + format_exc_check_arg( + PyExc_NameError, + NAME_ERROR_MSG, w); + break; + } } } Py_INCREF(x); @@ -2093,50 +2120,69 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) TARGET(LOAD_GLOBAL) w = GETITEM(names, oparg); - if (PyUnicode_CheckExact(w)) { - /* Inline the PyDict_GetItem() calls. - WARNING: this is an extreme speed hack. - Do not try this at home. */ - Py_hash_t hash = ((PyASCIIObject *)w)->hash; - if (hash != -1) { - PyDictObject *d; - PyDictEntry *e; - d = (PyDictObject *)(f->f_globals); - e = d->ma_lookup(d, w, hash); - if (e == NULL) { - x = NULL; - break; + if (PyDict_CheckExact(f->f_globals) + && PyDict_CheckExact(f->f_builtins)) { + if (PyUnicode_CheckExact(w)) { + /* Inline the PyDict_GetItem() calls. + WARNING: this is an extreme speed hack. + Do not try this at home. */ + Py_hash_t hash = ((PyASCIIObject *)w)->hash; + if (hash != -1) { + PyDictObject *d; + PyDictEntry *e; + d = (PyDictObject *)(f->f_globals); + e = d->ma_lookup(d, w, hash); + if (e == NULL) { + x = NULL; + break; + } + x = e->me_value; + if (x != NULL) { + Py_INCREF(x); + PUSH(x); + DISPATCH(); + } + d = (PyDictObject *)(f->f_builtins); + e = d->ma_lookup(d, w, hash); + if (e == NULL) { + x = NULL; + break; + } + x = e->me_value; + if (x != NULL) { + Py_INCREF(x); + PUSH(x); + DISPATCH(); + } + goto load_global_error; } - x = e->me_value; - if (x != NULL) { - Py_INCREF(x); - PUSH(x); - DISPATCH(); - } - d = (PyDictObject *)(f->f_builtins); - e = d->ma_lookup(d, w, hash); - if (e == NULL) { - x = NULL; - break; - } - x = e->me_value; - if (x != NULL) { - Py_INCREF(x); - PUSH(x); - DISPATCH(); - } - goto load_global_error; } - } - /* This is the un-inlined version of the code above */ - x = PyDict_GetItem(f->f_globals, w); - if (x == NULL) { - x = PyDict_GetItem(f->f_builtins, w); + /* This is the un-inlined version of the code above */ + x = PyDict_GetItem(f->f_globals, w); if (x == NULL) { - load_global_error: - format_exc_check_arg( - PyExc_NameError, - GLOBAL_NAME_ERROR_MSG, w); + x = PyDict_GetItem(f->f_builtins, w); + if (x == NULL) { + load_global_error: + format_exc_check_arg( + PyExc_NameError, + GLOBAL_NAME_ERROR_MSG, w); + break; + } + } + Py_INCREF(x); + PUSH(x); + DISPATCH(); + } + + /* Slow-path if globals or builtins is not a dict */ + x = PyObject_GetItem(f->f_globals, w); + if (x == NULL) { + x = PyObject_GetItem(f->f_builtins, w); + if (x == NULL) { + if (PyErr_ExceptionMatches(PyExc_KeyError)) + format_exc_check_arg( + PyExc_NameError, + GLOBAL_NAME_ERROR_MSG, w); break; } }