Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 214b1c3

Browse files
committed
SF Bug #215126: Over restricted type checking on eval() function
The builtin eval() function now accepts any mapping for the locals argument. Time sensitive steps guarded by PyDict_CheckExact() to keep from slowing down the normal case. My timings so no measurable impact.
1 parent 78bace7 commit 214b1c3

9 files changed

Lines changed: 103 additions & 31 deletions

File tree

Doc/lib/libfuncs.tex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,12 @@ \section{Built-in Functions \label{built-in-funcs}}
291291
\end{funcdesc}
292292

293293
\begin{funcdesc}{eval}{expression\optional{, globals\optional{, locals}}}
294-
The arguments are a string and two optional dictionaries. The
295-
\var{expression} argument is parsed and evaluated as a Python
294+
The arguments are a string and optional globals and locals. If provided,
295+
\var{globals} must be a dictionary. If provided, \var{locals} can be
296+
any mapping object. \versionchanged[formerly \var{locals} was required
297+
to be a dictionary]{2.4}
298+
299+
The \var{expression} argument is parsed and evaluated as a Python
296300
expression (technically speaking, a condition list) using the
297301
\var{globals} and \var{locals} dictionaries as global and local name
298302
space. If the \var{globals} dictionary is present and lacks

Include/frameobject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ typedef struct _frame {
1919
PyCodeObject *f_code; /* code segment */
2020
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
2121
PyObject *f_globals; /* global symbol table (PyDictObject) */
22-
PyObject *f_locals; /* local symbol table (PyDictObject) */
22+
PyObject *f_locals; /* local symbol table (any mapping) */
2323
PyObject **f_valuestack; /* points after the last local */
2424
/* Next free slot in f_valuestack. Frame creation sets to f_valuestack.
2525
Frame evaluation usually NULLs it, but a frame that yields sets it

Lib/test/test_builtin.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import test.test_support, unittest
44
from test.test_support import fcmp, have_unicode, TESTFN, unlink
55

6-
import sys, warnings, cStringIO, random
6+
import sys, warnings, cStringIO, random, UserDict
77
warnings.filterwarnings("ignore", "hex../oct.. of negative int",
88
FutureWarning, __name__)
99
warnings.filterwarnings("ignore", "integer argument expected",
@@ -262,6 +262,60 @@ def test_eval(self):
262262
self.assertRaises(TypeError, eval)
263263
self.assertRaises(TypeError, eval, ())
264264

265+
def test_general_eval(self):
266+
# Tests that general mappings can be used for the locals argument
267+
268+
class M:
269+
"Test mapping interface versus possible calls from eval()."
270+
def __getitem__(self, key):
271+
if key == 'a':
272+
return 12
273+
raise KeyError
274+
def keys(self):
275+
return list('xyz')
276+
277+
m = M()
278+
g = globals()
279+
self.assertEqual(eval('a', g, m), 12)
280+
self.assertRaises(NameError, eval, 'b', g, m)
281+
self.assertEqual(eval('dir()', g, m), list('xyz'))
282+
self.assertEqual(eval('globals()', g, m), g)
283+
self.assertEqual(eval('locals()', g, m), m)
284+
285+
# Verify that dict subclasses work as well
286+
class D(dict):
287+
def __getitem__(self, key):
288+
if key == 'a':
289+
return 12
290+
return dict.__getitem__(self, key)
291+
def keys(self):
292+
return list('xyz')
293+
294+
d = D()
295+
self.assertEqual(eval('a', g, d), 12)
296+
self.assertRaises(NameError, eval, 'b', g, d)
297+
self.assertEqual(eval('dir()', g, d), list('xyz'))
298+
self.assertEqual(eval('globals()', g, d), g)
299+
self.assertEqual(eval('locals()', g, d), d)
300+
301+
# Verify locals stores (used by list comps)
302+
eval('[locals() for i in (2,3)]', g, d)
303+
eval('[locals() for i in (2,3)]', g, UserDict.UserDict())
304+
305+
class SpreadSheet:
306+
"Sample application showing nested, calculated lookups."
307+
_cells = {}
308+
def __setitem__(self, key, formula):
309+
self._cells[key] = formula
310+
def __getitem__(self, key ):
311+
return eval(self._cells[key], globals(), self)
312+
313+
ss = SpreadSheet()
314+
ss['a1'] = '5'
315+
ss['a2'] = 'a1*6'
316+
ss['a3'] = 'a2*7'
317+
self.assertEqual(ss['a3'], 210)
318+
265319
# Done outside of the method test_z to get the correct scope
266320
z = 0
267321
f = open(TESTFN, 'w')

Lib/test/test_descrtut.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,6 @@ def merge(self, other):
7878
3
7979
>>>
8080
81-
However, our __getitem__() method is not used for variable access by the
82-
interpreter:
83-
84-
>>> exec "print foo" in a
85-
Traceback (most recent call last):
86-
File "<stdin>", line 1, in ?
87-
File "<string>", line 1, in ?
88-
NameError: name 'foo' is not defined
89-
>>>
90-
9181
Now I'll show that defaultdict instances have dynamic instance variables,
9282
just like classic classes:
9383

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ What's New in Python 2.4 alpha 1?
1212
Core and builtins
1313
-----------------
1414

15+
- Bug #215126. The locals argument to eval() now accepts any mapping type.
16+
1517
- marshal now shares interned strings. This change introduces
1618
a new .pyc magic.
1719

Objects/frameobject.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
544544

545545
#ifdef Py_DEBUG
546546
if (code == NULL || globals == NULL || !PyDict_Check(globals) ||
547-
(locals != NULL && !PyDict_Check(locals))) {
547+
(locals != NULL && !PyMapping_Check(locals))) {
548548
PyErr_BadInternalCall();
549549
return NULL;
550550
}
@@ -688,11 +688,11 @@ map_to_dict(PyObject *map, int nmap, PyObject *dict, PyObject **values,
688688
if (deref)
689689
value = PyCell_GET(value);
690690
if (value == NULL) {
691-
if (PyDict_DelItem(dict, key) != 0)
691+
if (PyObject_DelItem(dict, key) != 0)
692692
PyErr_Clear();
693693
}
694694
else {
695-
if (PyDict_SetItem(dict, key, value) != 0)
695+
if (PyObject_SetItem(dict, key, value) != 0)
696696
PyErr_Clear();
697697
}
698698
}
@@ -705,7 +705,9 @@ dict_to_map(PyObject *map, int nmap, PyObject *dict, PyObject **values,
705705
int j;
706706
for (j = nmap; --j >= 0; ) {
707707
PyObject *key = PyTuple_GET_ITEM(map, j);
708-
PyObject *value = PyDict_GetItem(dict, key);
708+
PyObject *value = PyObject_GetItem(dict, key);
709+
if (value == NULL)
710+
PyErr_Clear();
709711
if (deref) {
710712
if (value || clear) {
711713
if (PyCell_GET(values[j]) != value) {
@@ -720,6 +722,7 @@ dict_to_map(PyObject *map, int nmap, PyObject *dict, PyObject **values,
720722
values[j] = value;
721723
}
722724
}
725+
Py_XDECREF(value);
723726
}
724727
}
725728

@@ -742,7 +745,7 @@ PyFrame_FastToLocals(PyFrameObject *f)
742745
}
743746
}
744747
map = f->f_code->co_varnames;
745-
if (!PyDict_Check(locals) || !PyTuple_Check(map))
748+
if (!PyTuple_Check(map))
746749
return;
747750
PyErr_Fetch(&error_type, &error_value, &error_traceback);
748751
fast = f->f_localsplus;
@@ -780,7 +783,7 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
780783
map = f->f_code->co_varnames;
781784
if (locals == NULL)
782785
return;
783-
if (!PyDict_Check(locals) || !PyTuple_Check(map))
786+
if (!PyTuple_Check(map))
784787
return;
785788
PyErr_Fetch(&error_type, &error_value, &error_traceback);
786789
fast = f->f_localsplus;

Objects/object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1621,7 +1621,7 @@ PyObject_Dir(PyObject *arg)
16211621
PyObject *locals = PyEval_GetLocals();
16221622
if (locals == NULL)
16231623
goto error;
1624-
result = PyDict_Keys(locals);
1624+
result = PyMapping_Keys(locals);
16251625
if (result == NULL)
16261626
goto error;
16271627
}

Python/bltinmodule.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,17 @@ builtin_eval(PyObject *self, PyObject *args)
455455
char *str;
456456
PyCompilerFlags cf;
457457

458-
if (!PyArg_ParseTuple(args, "O|O!O!:eval",
459-
&cmd,
460-
&PyDict_Type, &globals,
461-
&PyDict_Type, &locals))
458+
if (!PyArg_UnpackTuple(args, "eval", 1, 3, &cmd, &globals, &locals))
462459
return NULL;
460+
if (locals != Py_None && !PyMapping_Check(locals)) {
461+
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
462+
return NULL;
463+
}
464+
if (globals != Py_None && !PyDict_Check(globals)) {
465+
PyErr_SetString(PyExc_TypeError, PyMapping_Check(globals) ?
466+
"globals must be a real dict; try eval(expr, {}, mapping)"
467+
: "globals must be a dict");
468+
}
463469
if (globals == Py_None) {
464470
globals = PyEval_GetGlobals();
465471
if (locals == Py_None)
@@ -517,8 +523,9 @@ PyDoc_STRVAR(eval_doc,
517523
Evaluate the source in the context of globals and locals.\n\
518524
The source may be a string representing a Python expression\n\
519525
or a code object as returned by compile().\n\
520-
The globals and locals are dictionaries, defaulting to the current\n\
521-
globals and locals. If only globals is given, locals defaults to it.");
526+
The globals must be a dictionary and locals can be any mappping,\n\
527+
defaulting to the current globals and locals.\n\
528+
If only globals is given, locals defaults to it.\n");
522529

523530

524531
static PyObject *

Python/ceval.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,10 @@ PyEval_EvalFrame(PyFrameObject *f)
16431643
w = GETITEM(names, oparg);
16441644
v = POP();
16451645
if ((x = f->f_locals) != NULL) {
1646-
err = PyDict_SetItem(x, w, v);
1646+
if (PyDict_CheckExact(v))
1647+
err = PyDict_SetItem(x, w, v);
1648+
else
1649+
err = PyObject_SetItem(x, w, v);
16471650
Py_DECREF(v);
16481651
if (err == 0) continue;
16491652
break;
@@ -1656,7 +1659,7 @@ PyEval_EvalFrame(PyFrameObject *f)
16561659
case DELETE_NAME:
16571660
w = GETITEM(names, oparg);
16581661
if ((x = f->f_locals) != NULL) {
1659-
if ((err = PyDict_DelItem(x, w)) != 0)
1662+
if ((err = PyObject_DelItem(x, w)) != 0)
16601663
format_exc_check_arg(PyExc_NameError,
16611664
NAME_ERROR_MSG ,w);
16621665
break;
@@ -1733,13 +1736,22 @@ PyEval_EvalFrame(PyFrameObject *f)
17331736

17341737
case LOAD_NAME:
17351738
w = GETITEM(names, oparg);
1736-
if ((x = f->f_locals) == NULL) {
1739+
if ((v = f->f_locals) == NULL) {
17371740
PyErr_Format(PyExc_SystemError,
17381741
"no locals when loading %s",
17391742
PyObject_REPR(w));
17401743
break;
17411744
}
1742-
x = PyDict_GetItem(x, w);
1745+
if (PyDict_CheckExact(v))
1746+
x = PyDict_GetItem(v, w);
1747+
else {
1748+
x = PyObject_GetItem(v, w);
1749+
if (x == NULL && PyErr_Occurred()) {
1750+
if (!PyErr_ExceptionMatches(PyExc_KeyError))
1751+
break;
1752+
PyErr_Clear();
1753+
}
1754+
}
17431755
if (x == NULL) {
17441756
x = PyDict_GetItem(f->f_globals, w);
17451757
if (x == NULL) {

0 commit comments

Comments
 (0)