From 66bd23322567a9ef0ad7bbe2436fef73b18bc9db Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 2 Aug 2004 08:30:07 +0000 Subject: Completed the patch for Bug #215126. * Fixes an incorrect variable in a PyDict_CheckExact. * Allow general mapping locals arguments for the execfile() function and exec statement. * Add tests. --- Lib/test/test_builtin.py | 25 +++++++++++++++++++++ Lib/test/test_compile.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 3 ++- Python/bltinmodule.c | 8 +++++-- Python/ceval.c | 6 ++--- 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index fdbbdfc..6654f56 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -282,6 +282,11 @@ class BuiltinTest(unittest.TestCase): self.assertEqual(eval('globals()', g, m), g) self.assertEqual(eval('locals()', g, m), m) self.assertRaises(TypeError, eval, 'a', m) + class A: + "Non-mapping" + pass + m = A() + self.assertRaises(TypeError, eval, 'a', g, m) # Verify that dict subclasses work as well class D(dict): @@ -336,6 +341,26 @@ class BuiltinTest(unittest.TestCase): locals['z'] = 0 execfile(TESTFN, globals, locals) self.assertEqual(locals['z'], 2) + + class M: + "Test mapping interface versus possible calls from execfile()." + def __init__(self): + self.z = 10 + def __getitem__(self, key): + if key == 'z': + return self.z + raise KeyError + def __setitem__(self, key, value): + if key == 'z': + self.z = value + return + raise KeyError + + locals = M() + locals['z'] = 0 + execfile(TESTFN, globals, locals) + self.assertEqual(locals['z'], 2) + unlink(TESTFN) self.assertRaises(TypeError, execfile) import os diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 5b7b717..b1644cb 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -44,6 +44,63 @@ class TestSpecifics(unittest.TestCase): except SyntaxError: pass + def test_exec_with_general_mapping_for_locals(self): + + class M: + "Test mapping interface versus possible calls from eval()." + def __getitem__(self, key): + if key == 'a': + return 12 + raise KeyError + def __setitem__(self, key, value): + self.results = (key, value) + def keys(self): + return list('xyz') + + m = M() + g = globals() + exec 'z = a' in g, m + self.assertEqual(m.results, ('z', 12)) + try: + exec 'z = b' in g, m + except NameError: + pass + else: + self.fail('Did not detect a KeyError') + exec 'z = dir()' in g, m + self.assertEqual(m.results, ('z', list('xyz'))) + exec 'z = globals()' in g, m + self.assertEqual(m.results, ('z', g)) + exec 'z = locals()' in g, m + self.assertEqual(m.results, ('z', m)) + try: + exec 'z = b' in m + except TypeError: + pass + else: + self.fail('Did not validate globals as a real dict') + + class A: + "Non-mapping" + pass + m = A() + try: + exec 'z = a' in g, m + except TypeError: + pass + else: + self.fail('Did not validate locals as a mapping') + + # Verify that dict subclasses work as well + class D(dict): + def __getitem__(self, key): + if key == 'a': + return 12 + return dict.__getitem__(self, key) + d = D() + exec 'z = a' in g, d + self.assertEqual(d['z'], 12) + def test_complex_args(self): def comp_args((a, b)): diff --git a/Misc/NEWS b/Misc/NEWS index 15713e9..edd5989 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -269,7 +269,8 @@ Core and builtins - Bug #951851: Python crashed when reading import table of certain Windows DLLs. -- Bug #215126. The locals argument to eval() now accepts any mapping type. +- Bug #215126. The locals argument to eval(), execfile(), and exec now + accept any mapping type. - marshal now shares interned strings. This change introduces a new .pyc magic. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 4143681..b76f373 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -539,11 +539,15 @@ builtin_execfile(PyObject *self, PyObject *args) PyCompilerFlags cf; int exists; - if (!PyArg_ParseTuple(args, "s|O!O!:execfile", + if (!PyArg_ParseTuple(args, "s|O!O:execfile", &filename, &PyDict_Type, &globals, - &PyDict_Type, &locals)) + &locals)) return NULL; + if (locals != Py_None && !PyMapping_Check(locals)) { + PyErr_SetString(PyExc_TypeError, "locals must be a mapping"); + return NULL; + } if (globals == Py_None) { globals = PyEval_GetGlobals(); if (locals == Py_None) diff --git a/Python/ceval.c b/Python/ceval.c index 152b942..3a462af 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1643,7 +1643,7 @@ PyEval_EvalFrame(PyFrameObject *f) w = GETITEM(names, oparg); v = POP(); if ((x = f->f_locals) != NULL) { - if (PyDict_CheckExact(v)) + if (PyDict_CheckExact(x)) err = PyDict_SetItem(x, w, v); else err = PyObject_SetItem(x, w, v); @@ -4116,9 +4116,9 @@ exec_statement(PyFrameObject *f, PyObject *prog, PyObject *globals, "exec: arg 2 must be a dictionary or None"); return -1; } - if (!PyDict_Check(locals)) { + if (!PyMapping_Check(locals)) { PyErr_SetString(PyExc_TypeError, - "exec: arg 3 must be a dictionary or None"); + "exec: arg 3 must be a mapping or None"); return -1; } if (PyDict_GetItemString(globals, "__builtins__") == NULL) -- cgit v0.12