| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d36c30d commit b093ced
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,8 +8,10 @@ | |||
| 8 | 8 | import ast | |
| 9 | 9 | from six import string_types | |
| 10 | 10 | from six.moves import builtins | |
| 11 | + from numbers import Number | ||
| 11 | 12 | ||
| 12 | 13 | from bpython import line as line_properties | |
| 14 | + from bpython._py3compat import py3 | ||
| 13 | 15 | ||
| 14 | 16 | class EvaluationError(Exception): | |
| 15 | 17 | """Raised if an exception occurred in safe_eval.""" | |
@@ -24,7 +26,7 @@ def safe_eval(expr, namespace): | |||
| 24 | 26 | # raise | |
| 25 | 27 | raise EvaluationError | |
| 26 | 28 | ||
| 27 | - # taken from Python 2 stdlib ast.literal_eval | ||
| 29 | + | ||
| 28 | 30 | def simple_eval(node_or_string, namespace=None): | |
| 29 | 31 | """ | |
| 30 | 32 | Safely evaluate an expression node or a string containing a Python | |
@@ -34,6 +36,9 @@ def simple_eval(node_or_string, namespace=None): | |||
| 34 | 36 | * variable names causing lookups in the passed in namespace or builtins | |
| 35 | 37 | * getitem calls using the [] syntax on objects of the types above | |
| 36 | 38 | ||
| 39 | + Like the Python 3 (and unlike the Python 2) literal_eval, unary and binary | ||
| 40 | + + and - operations are allowed on all builtin numeric types. | ||
| 41 | + | ||
| 37 | 42 | The optional namespace dict-like ought not to cause side effects on lookup | |
| 38 | 43 | """ | |
| 39 | 44 | # Based heavily on stdlib ast.literal_eval | |
@@ -43,8 +48,13 @@ def simple_eval(node_or_string, namespace=None): | |||
| 43 | 48 | node_or_string = ast.parse(node_or_string, mode='eval') | |
| 44 | 49 | if isinstance(node_or_string, ast.Expression): | |
| 45 | 50 | node_or_string = node_or_string.body | |
| 51 | + | ||
| 52 | + string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) | ||
| 53 | + name_type_nodes = (ast.Name, ast.NameConstant) if py3 else (ast.Name,) | ||
| 54 | + numeric_types = (int, float, complex) + (() if py3 else (long,)) | ||
| 55 | + | ||
| 46 | 56 | def _convert(node): | |
| 47 | - if isinstance(node, ast.Str): | ||
| 57 | + if isinstance(node, string_type_nodes): | ||
| 48 | 58 | return node.s | |
| 49 | 59 | elif isinstance(node, ast.Num): | |
| 50 | 60 | return node.n | |
@@ -55,23 +65,41 @@ def _convert(node): | |||
| 55 | 65 | elif isinstance(node, ast.Dict): | |
| 56 | 66 | return dict((_convert(k), _convert(v)) for k, v | |
| 57 | 67 | in zip(node.keys, node.values)) | |
| 58 | - elif isinstance(node, ast.Name): | ||
| 68 | + | ||
| 69 | + # this is a deviation from literal_eval: we allow non-literals | ||
| 70 | + elif isinstance(node, name_type_nodes): | ||
| 59 | 71 | try: | |
| 60 | 72 | return namespace[node.id] | |
| 61 | 73 | except KeyError: | |
| 62 | - return getattr(builtins, node.id) | ||
| 74 | + try: | ||
| 75 | + return getattr(builtins, node.id) | ||
| 76 | + except AttributeError: | ||
| 77 | + raise EvaluationError("can't lookup %s" % node.id) | ||
| 78 | + | ||
| 79 | + # unary + and - are allowed on any type | ||
| 80 | + elif isinstance(node, ast.UnaryOp) and \ | ||
| 81 | + isinstance(node.op, (ast.UAdd, ast.USub)): | ||
| 82 | + # ast.literal_eval does ast typechecks here, we use type checks | ||
| 83 | + operand = _convert(node.operand) | ||
| 84 | + if not type(operand) in numeric_types: | ||
| 85 | + raise ValueError("unary + and - only allowed on builtin nums") | ||
| 86 | + if isinstance(node.op, ast.UAdd): | ||
| 87 | + return + operand | ||
| 88 | + else: | ||
| 89 | + return - operand | ||
| 63 | 90 | elif isinstance(node, ast.BinOp) and \ | |
| 64 | - isinstance(node.op, (ast.Add, ast.Sub)) and \ | ||
| 65 | - isinstance(node.right, ast.Num) and \ | ||
| 66 | - isinstance(node.right.n, complex) and \ | ||
| 67 | - isinstance(node.left, ast.Num) and \ | ||
| 68 | - isinstance(node.left.n, (int, long, float)): | ||
| 69 | - left = node.left.n | ||
| 70 | - right = node.right.n | ||
| 91 | + isinstance(node.op, (ast.Add, ast.Sub)): | ||
| 92 | + # ast.literal_eval does ast typechecks here, we use type checks | ||
| 93 | + left = _convert(node.left) | ||
| 94 | + right = _convert(node.right) | ||
| 95 | + if not (type(left) in numeric_types and type(right) in numeric_types): | ||
| 96 | + raise ValueError("binary + and - only allowed on builtin nums") | ||
| 71 | 97 | if isinstance(node.op, ast.Add): | |
| 72 | 98 | return left + right | |
| 73 | 99 | else: | |
| 74 | 100 | return left - right | |
| 101 | + | ||
| 102 | + # this is a deviation from literal_eval: we allow indexing | ||
| 75 | 103 | elif isinstance(node, ast.Subscript) and \ | |
| 76 | 104 | isinstance(node.slice, ast.Index): | |
| 77 | 105 | obj = _convert(node.value) | |
@@ -84,7 +112,10 @@ def _convert(node): | |||
| 84 | 112 | ||
| 85 | 113 | def safe_getitem(obj, index): | |
| 86 | 114 | if type(obj) in (list, tuple, dict, bytes) + string_types: | |
| 87 | - return obj[index] | ||
| 115 | + try: | ||
| 116 | + return obj[index] | ||
| 117 | + except (KeyError, IndexError): | ||
| 118 | + raise EvaluationError("can't lookup key %r on %r" % (index, obj)) | ||
| 88 | 119 | raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) | |
| 89 | 120 | ||
| 90 | 121 | ||
@@ -141,5 +172,5 @@ def parse_trees(cursor_offset, line): | |||
| 141 | 172 | raise EvaluationError("Corresponding ASTs to right of cursor are invalid") | |
| 142 | 173 | try: | |
| 143 | 174 | return simple_eval(largest_ast, namespace) | |
| 144 | - except (ValueError, KeyError, IndexError, AttributeError): | ||
| 175 | + except ValueError: | ||
| 145 | 176 | raise EvaluationError("Could not safely evaluate") | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -65,7 +65,7 @@ def test_function_calls_raise(self): | |||
| 65 | 65 | simple_eval('1()') | |
| 66 | 66 | ||
| 67 | 67 | def test_nonexistant_names_raise(self): | |
| 68 | - with self.assertRaises((KeyError, AttributeError)): | ||
| 68 | + with self.assertRaises(EvaluationError): | ||
| 69 | 69 | simple_eval('a') | |
| 70 | 70 | ||
| 71 | 71 | class TestEvaluateCurrentExpression(unittest.TestCase): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments