summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_builtin.py25
-rw-r--r--Lib/test/test_compile.py57
-rw-r--r--Misc/NEWS3
-rw-r--r--Python/bltinmodule.c8
-rw-r--r--Python/ceval.c6
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)