/usr/lib/python3/dist-packages/bpython/simpleeval.py is in bpython3 0.16-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | # encoding: utf-8
# The MIT License
#
# Copyright (c) 2015 the bpython authors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
"""simple evaluation of side-effect free code
In order to provide fancy completion, some code can be executed safely.
"""
import ast
import inspect
from six import string_types
from six.moves import builtins
import sys
import types
from bpython import line as line_properties
from bpython._py3compat import py3
from bpython.inspection import is_new_style, AttrCleaner
_string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,)
_numeric_types = (int, float, complex) + (() if py3 else (long,))
# added in Python 3.4
if hasattr(ast, 'NameConstant'):
_name_type_nodes = (ast.Name, ast.NameConstant)
else:
_name_type_nodes = (ast.Name,)
class EvaluationError(Exception):
"""Raised if an exception occurred in safe_eval."""
def safe_eval(expr, namespace):
"""Not all that safe, just catches some errors"""
try:
return eval(expr, namespace)
except (NameError, AttributeError, SyntaxError):
# If debugging safe_eval, raise this!
# raise
raise EvaluationError
# This function is under the Python License, Version 2
# This license requires modifications to the code be reported.
# Based on ast.literal_eval in Python 2 and Python 3
# Modifications:
# * Python 2 and Python 3 versions of the function are combined
# * checks that objects used as operands of + and - are numbers
# instead of checking they are constructed with number literals
# * new docstring describing different functionality
# * looks up names from namespace
# * indexing syntax is allowed
def simple_eval(node_or_string, namespace=None):
"""
Safely evaluate an expression node or a string containing a Python
expression without triggering any user code.
The string or node provided may only consist of:
* the following Python literal structures: strings, numbers, tuples,
lists, and dicts
* variable names causing lookups in the passed in namespace or builtins
* getitem calls using the [] syntax on objects of the types above
Like the Python 3 (and unlike the Python 2) literal_eval, unary and binary
+ and - operations are allowed on all builtin numeric types.
The optional namespace dict-like ought not to cause side effects on lookup
"""
if namespace is None:
namespace = {}
if isinstance(node_or_string, string_types):
node_or_string = ast.parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
def _convert(node):
if isinstance(node, _string_type_nodes):
return node.s
elif isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.Tuple):
return tuple(map(_convert, node.elts))
elif isinstance(node, ast.List):
return list(map(_convert, node.elts))
elif isinstance(node, ast.Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
# this is a deviation from literal_eval: we allow non-literals
elif isinstance(node, _name_type_nodes):
try:
return namespace[node.id]
except KeyError:
try:
return getattr(builtins, node.id)
except AttributeError:
raise EvaluationError("can't lookup %s" % node.id)
# unary + and - are allowed on any type
elif (isinstance(node, ast.UnaryOp) and
isinstance(node.op, (ast.UAdd, ast.USub))):
# ast.literal_eval does ast typechecks here, we use type checks
operand = _convert(node.operand)
if not type(operand) in _numeric_types:
raise ValueError("unary + and - only allowed on builtin nums")
if isinstance(node.op, ast.UAdd):
return + operand
else:
return - operand
elif (isinstance(node, ast.BinOp) and
isinstance(node.op, (ast.Add, ast.Sub))):
# ast.literal_eval does ast typechecks here, we use type checks
left = _convert(node.left)
right = _convert(node.right)
if not (type(left) in _numeric_types and
type(right) in _numeric_types):
raise ValueError("binary + and - only allowed on builtin nums")
if isinstance(node.op, ast.Add):
return left + right
else:
return left - right
# this is a deviation from literal_eval: we allow indexing
elif (isinstance(node, ast.Subscript) and
isinstance(node.slice, ast.Index)):
obj = _convert(node.value)
index = _convert(node.slice.value)
return safe_getitem(obj, index)
# this is a deviation from literal_eval: we allow attribute access
if isinstance(node, ast.Attribute):
obj = _convert(node.value)
attr = node.attr
return safe_get_attribute(obj, attr)
raise ValueError('malformed string')
return _convert(node_or_string)
def safe_getitem(obj, index):
if type(obj) in (list, tuple, dict, bytes) + string_types:
try:
return obj[index]
except (KeyError, IndexError):
raise EvaluationError("can't lookup key %r on %r" % (index, obj))
raise ValueError('unsafe to lookup on object of type %s' % (type(obj), ))
def find_attribute_with_name(node, name):
if isinstance(node, ast.Attribute) and node.attr == name:
return node
for item in ast.iter_child_nodes(node):
r = find_attribute_with_name(item, name)
if r:
return r
def evaluate_current_expression(cursor_offset, line, namespace=None):
"""
Return evaluated expression to the right of the dot of current attribute.
Only evaluates builtin objects, and do any attribute lookup.
"""
# Builds asts from with increasing numbers of characters back from cursor.
# Find the biggest valid ast.
# Once our attribute access is found, return its .value subtree
if namespace is None:
namespace = {}
# in case attribute is blank, e.g. foo.| -> foo.xxx|
temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:]
temp_cursor = cursor_offset + 3
temp_attribute = line_properties.current_expression_attribute(
temp_cursor, temp_line)
if temp_attribute is None:
raise EvaluationError("No current attribute")
attr_before_cursor = temp_line[temp_attribute.start:temp_cursor]
def parse_trees(cursor_offset, line):
for i in range(cursor_offset - 1, -1, -1):
try:
tree = ast.parse(line[i:cursor_offset])
yield tree
except SyntaxError:
continue
largest_ast = None
for tree in parse_trees(temp_cursor, temp_line):
attribute_access = find_attribute_with_name(tree, attr_before_cursor)
if attribute_access:
largest_ast = attribute_access.value
if largest_ast is None:
raise EvaluationError(
"Corresponding ASTs to right of cursor are invalid")
try:
return simple_eval(largest_ast, namespace)
except ValueError:
raise EvaluationError("Could not safely evaluate")
def evaluate_current_attribute(cursor_offset, line, namespace=None):
"""Safely evaluates the expression having an attributed accessed"""
# this function runs user code in case of custom descriptors,
# so could fail in any way
obj = evaluate_current_expression(cursor_offset, line, namespace)
attr = line_properties.current_expression_attribute(cursor_offset, line)
if attr is None:
raise EvaluationError("No attribute found to look up")
try:
return getattr(obj, attr.word)
except AttributeError:
raise EvaluationError(
"can't lookup attribute %s on %r" % (attr.word, obj))
def safe_get_attribute(obj, attr):
"""Gets attributes without triggering descriptors on new-style classes"""
if is_new_style(obj):
with AttrCleaner(obj):
result = safe_get_attribute_new_style(obj, attr)
if isinstance(result, member_descriptor):
# will either be the same slot descriptor or the value
return getattr(obj, attr)
return result
return getattr(obj, attr)
class _ClassWithSlots(object):
__slots__ = ['a']
member_descriptor = type(_ClassWithSlots.a)
def safe_get_attribute_new_style(obj, attr):
"""Returns approximately the attribute returned by getattr(obj, attr)
The object returned ought to be callable if getattr(obj, attr) was.
Fake callable objects may be returned instead, in order to avoid executing
arbitrary code in descriptors.
If the object is an instance of a class that uses __slots__, will return
the member_descriptor object instead of the value.
"""
if not is_new_style(obj):
raise ValueError("%r is not a new-style class or object" % obj)
to_look_through = (obj.__mro__
if inspect.isclass(obj)
else (obj,) + type(obj).__mro__)
for cls in to_look_through:
if hasattr(cls, '__dict__') and attr in cls.__dict__:
return cls.__dict__[attr]
raise AttributeError()
|