免责声明:我是另一个答案中代码中提到的 Alexer。老实说,我只是半开玩笑地建议了字节码解析方法,因为我碰巧有 99% 的代码都在一个不相关的项目中,所以可以在几分钟内快速组合出一个 POC。也就是说,它本身不应该有任何问题;只是这项任务需要更复杂的机器。事实上,你should只需反汇编代码[根据白名单检查操作码],检查常量和名称是否有效,然后用简单的、邪恶的 eval 执行它就可以逃脱惩罚。您应该失去在整个执行过程中插入偏执的额外检查的能力。 (另一个免责声明:我仍然觉得用 eval 来做这件事还不够舒服)
不管怎样,我有一个无聊的时刻,所以我写了一些代码来以聪明的方式做到这一点;使用 AST 而不是字节码。这只是一个额外的标志compile()
。 (要不就ast.parse()
,因为无论如何你都需要模块中的类型)
import ast
import operator
_operations = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.div,
ast.Pow: operator.pow,
}
def _safe_eval(node, variables, functions):
if isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.Name):
return variables[node.id] # KeyError -> Unsafe variable
elif isinstance(node, ast.BinOp):
op = _operations[node.op.__class__] # KeyError -> Unsafe operation
left = _safe_eval(node.left, variables, functions)
right = _safe_eval(node.right, variables, functions)
if isinstance(node.op, ast.Pow):
assert right < 100
return op(left, right)
elif isinstance(node, ast.Call):
assert not node.keywords and not node.starargs and not node.kwargs
assert isinstance(node.func, ast.Name), 'Unsafe function derivation'
func = functions[node.func.id] # KeyError -> Unsafe function
args = [_safe_eval(arg, variables, functions) for arg in node.args]
return func(*args)
assert False, 'Unsafe operation'
def safe_eval(expr, variables={}, functions={}):
node = ast.parse(expr, '<string>', 'eval').body
return _safe_eval(node, variables, functions)
if __name__ == '__main__':
import math
print safe_eval('sin(a*pi/b)', dict(a=1, b=2, pi=math.pi), dict(sin=math.sin))
这与字节码版本同样适用;如果您根据白名单检查操作并检查名称和值是否有效,那么您应该能够在 AST 上调用 eval 。 (但是,我还是不会这样做。因为偏执。当涉及评估时,偏执是好的)