|
1 |
| -## {{{ http://code.activestate.com/recipes/364469/ (r2) |
2 |
| -import compiler |
| 1 | +## {{{ http://code.activestate.com/recipes/286134/ (r3) (modified) |
| 2 | +import dis |
3 | 3 |
|
4 |
| -class Unsafe_Source_Error(Exception): |
5 |
| - def __init__(self,error,descr = None,node = None): |
6 |
| - self.error = error |
7 |
| - self.descr = descr |
8 |
| - self.node = node |
9 |
| - self.lineno = getattr(node,"lineno",None) |
10 |
| - |
11 |
| - def __repr__(self): |
12 |
| - return "Line %d. %s: %s" % (self.lineno, self.error, self.descr) |
13 |
| - __str__ = __repr__ |
14 |
| - |
15 |
| -class SafeEval(object): |
16 |
| - |
17 |
| - def visit(self, node,**kw): |
18 |
| - cls = node.__class__ |
19 |
| - meth = getattr(self,'visit'+cls.__name__,self.default) |
20 |
| - return meth(node, **kw) |
21 |
| - |
22 |
| - def default(self, node, **kw): |
23 |
| - for child in node.getChildNodes(): |
24 |
| - return self.visit(child, **kw) |
25 |
| - |
26 |
| - visitExpression = default |
27 |
| - |
28 |
| - def visitConst(self, node, **kw): |
29 |
| - return node.value |
| 4 | +_const_codes = map(dis.opmap.__getitem__, [ |
| 5 | + 'POP_TOP','ROT_TWO','ROT_THREE','ROT_FOUR','DUP_TOP', |
| 6 | + 'BUILD_LIST','BUILD_MAP','BUILD_TUPLE', |
| 7 | + 'LOAD_CONST','RETURN_VALUE','STORE_SUBSCR' |
| 8 | + ]) |
30 | 9 |
|
31 |
| - def visitDict(self,node,**kw): |
32 |
| - return dict([(self.visit(k),self.visit(v)) for k,v in node.items]) |
33 |
| - |
34 |
| - def visitTuple(self,node, **kw): |
35 |
| - return tuple(self.visit(i) for i in node.nodes) |
36 |
| - |
37 |
| - def visitList(self,node, **kw): |
38 |
| - return [self.visit(i) for i in node.nodes] |
39 | 10 |
|
40 |
| -class SafeEvalWithErrors(SafeEval): |
| 11 | +_load_names = ['False', 'True', 'null', 'true', 'false'] |
41 | 12 |
|
42 |
| - def default(self, node, **kw): |
43 |
| - raise Unsafe_Source_Error("Unsupported source construct", |
44 |
| - node.__class__,node) |
45 |
| - |
46 |
| - def visitName(self,node, **kw): |
47 |
| - if node.name == "None": |
48 |
| - return None |
49 |
| - elif node.name == "True": |
50 |
| - return True |
51 |
| - elif node.name == "False": |
52 |
| - return False |
| 13 | +_locals = {'null': None, 'true': True, 'false': False} |
| 14 | + |
| 15 | +def _get_opcodes(codeobj): |
| 16 | + i = 0 |
| 17 | + opcodes = [] |
| 18 | + s = codeobj.co_code |
| 19 | + names = codeobj.co_names |
| 20 | + while i < len(s): |
| 21 | + code = ord(s[i]) |
| 22 | + opcodes.append(code) |
| 23 | + if code >= dis.HAVE_ARGUMENT: |
| 24 | + i += 3 |
53 | 25 | else:
|
54 |
| - raise Unsafe_Source_Error("Strings must be quoted", |
55 |
| - node.name, node) |
56 |
| - |
57 |
| - # Add more specific errors if desired |
58 |
| - |
| 26 | + i += 1 |
| 27 | + return opcodes, names |
59 | 28 |
|
60 |
| -def safe_eval(source, fail_on_error = True): |
61 |
| - walker = fail_on_error and SafeEvalWithErrors() or SafeEval() |
62 |
| - try: |
63 |
| - ast = compiler.parse(source,"eval") |
64 |
| - except SyntaxError, err: |
65 |
| - raise |
| 29 | +def test_expr(expr, allowed_codes): |
66 | 30 | try:
|
67 |
| - return walker.visit(ast) |
68 |
| - except Unsafe_Source_Error, err: |
69 |
| - raise |
70 |
| -## end of http://code.activestate.com/recipes/364469/ }}} |
| 31 | + c = compile(expr, "", "eval") |
| 32 | + except: |
| 33 | + raise ValueError, "%s is not a valid expression" % expr |
| 34 | + codes, names = _get_opcodes(c) |
| 35 | + for code in codes: |
| 36 | + if code not in allowed_codes: |
| 37 | + for n in names: |
| 38 | + if n not in _load_names: |
| 39 | + raise ValueError, "opcode %s not allowed" % dis.opname[code] |
| 40 | + return c |
| 41 | + |
| 42 | + |
| 43 | +def const_eval(expr): |
| 44 | + c = test_expr(expr, _const_codes) |
| 45 | + return eval(c, None, _locals) |
| 46 | + |
| 47 | +## end of http://code.activestate.com/recipes/286134/ }}} |
0 commit comments