[IMP] safe_eval: speed up evaluation
* creating the huge builtins dict inline represents ~40% of safe_eval's total cost according to line_profiler * for the specific situation of opcodes, using a set difference is apparently much faster than for+if. all(gencomp) is also faster than for+if but not as much as letting Python do (almost) all of it might be that returning a set from _get_opcodes would be even faster, but didn't test it
This commit is contained in:
committed by
Olivier Dony
parent
60ad1fec4d
commit
20b6003dce
+52
-52
@@ -144,10 +144,12 @@ def assert_valid_codeobj(allowed_codes, code_obj, expr):
|
||||
is found in ``code_obj``
|
||||
"""
|
||||
assert_no_dunder_name(code_obj, expr)
|
||||
for opcode in _get_opcodes(code_obj):
|
||||
if opcode not in allowed_codes:
|
||||
raise ValueError(
|
||||
"opcode %s not allowed (%r)" % (opname[opcode], expr))
|
||||
|
||||
# almost twice as fast as a manual iteration + condition when loading
|
||||
# /web according to line_profiler
|
||||
if set(_get_opcodes(code_obj)) - allowed_codes:
|
||||
raise ValueError("forbidden opcode(s) in %r" % expr)
|
||||
|
||||
for const in code_obj.co_consts:
|
||||
if isinstance(const, CodeType):
|
||||
assert_valid_codeobj(allowed_codes, const, 'lambda')
|
||||
@@ -227,7 +229,44 @@ def _import(name, globals=None, locals=None, fromlist=None, level=-1):
|
||||
if name in _ALLOWED_MODULES:
|
||||
return __import__(name, globals, locals, level)
|
||||
raise ImportError(name)
|
||||
|
||||
_BUILTINS = {
|
||||
'__import__': _import,
|
||||
'True': True,
|
||||
'False': False,
|
||||
'None': None,
|
||||
'str': str,
|
||||
'unicode': unicode,
|
||||
'bool': bool,
|
||||
'int': int,
|
||||
'float': float,
|
||||
'long': long,
|
||||
'enumerate': enumerate,
|
||||
'dict': dict,
|
||||
'list': list,
|
||||
'tuple': tuple,
|
||||
'map': map,
|
||||
'abs': abs,
|
||||
'min': min,
|
||||
'max': max,
|
||||
'sum': sum,
|
||||
'reduce': reduce,
|
||||
'filter': filter,
|
||||
'round': round,
|
||||
'len': len,
|
||||
'repr': repr,
|
||||
'set': set,
|
||||
'all': all,
|
||||
'any': any,
|
||||
'ord': ord,
|
||||
'chr': chr,
|
||||
'cmp': cmp,
|
||||
'divmod': divmod,
|
||||
'isinstance': isinstance,
|
||||
'range': range,
|
||||
'xrange': xrange,
|
||||
'zip': zip,
|
||||
'Exception': Exception,
|
||||
}
|
||||
def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False, locals_builtins=False):
|
||||
"""safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
|
||||
|
||||
@@ -245,12 +284,9 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
||||
:throws NameError: If the expression provided accesses forbidden names
|
||||
:throws ValueError: If the expression provided uses forbidden bytecode
|
||||
"""
|
||||
if isinstance(expr, CodeType):
|
||||
if type(expr) is CodeType:
|
||||
raise TypeError("safe_eval does not allow direct evaluation of code objects.")
|
||||
|
||||
if globals_dict is None:
|
||||
globals_dict = {}
|
||||
|
||||
# prevent altering the globals/locals from within the sandbox
|
||||
# by taking a copy.
|
||||
if not nocopy:
|
||||
@@ -260,55 +296,19 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
||||
_logger.warning(
|
||||
"Looks like you are trying to pass a dynamic environment, "
|
||||
"you should probably pass nocopy=True to safe_eval().")
|
||||
|
||||
globals_dict = dict(globals_dict)
|
||||
if globals_dict is not None:
|
||||
globals_dict = dict(globals_dict)
|
||||
if locals_dict is not None:
|
||||
locals_dict = dict(locals_dict)
|
||||
|
||||
globals_dict.update(
|
||||
__builtins__={
|
||||
'__import__': _import,
|
||||
'True': True,
|
||||
'False': False,
|
||||
'None': None,
|
||||
'str': str,
|
||||
'unicode': unicode,
|
||||
'bool': bool,
|
||||
'int': int,
|
||||
'float': float,
|
||||
'long': long,
|
||||
'enumerate': enumerate,
|
||||
'dict': dict,
|
||||
'list': list,
|
||||
'tuple': tuple,
|
||||
'map': map,
|
||||
'abs': abs,
|
||||
'min': min,
|
||||
'max': max,
|
||||
'sum': sum,
|
||||
'reduce': reduce,
|
||||
'filter': filter,
|
||||
'round': round,
|
||||
'len': len,
|
||||
'repr': repr,
|
||||
'set': set,
|
||||
'all': all,
|
||||
'any': any,
|
||||
'ord': ord,
|
||||
'chr': chr,
|
||||
'cmp': cmp,
|
||||
'divmod': divmod,
|
||||
'isinstance': isinstance,
|
||||
'range': range,
|
||||
'xrange': xrange,
|
||||
'zip': zip,
|
||||
'Exception': Exception,
|
||||
}
|
||||
)
|
||||
if globals_dict is None:
|
||||
globals_dict = {}
|
||||
|
||||
globals_dict['__builtins__'] = _BUILTINS
|
||||
if locals_builtins:
|
||||
if locals_dict is None:
|
||||
locals_dict = {}
|
||||
locals_dict.update(globals_dict.get('__builtins__'))
|
||||
locals_dict.update(_BUILTINS)
|
||||
c = test_expr(expr, _SAFE_OPCODES, mode=mode)
|
||||
try:
|
||||
return eval(c, globals_dict, locals_dict)
|
||||
|
||||
Reference in New Issue
Block a user