I have a website where the user enters math equations (expressions) and then those equations are evaluated against data (constants) provided by the website. The math operati
Disclaimer: I'm the Alexer mentioned in the code in the other answer. To be honest, I kind of suggested the bytecode parsing approach only half-jokingly, since I happened to have 99% of the code lying around for an unrelated project and so could whip together a POC in like a couple of minutes. That said, there shouldn't be anything wrong with it, per se; it's just that it's a more complex machinery that is needed for this task. In fact, you should be able to get away with just disassembling the code [checking the opcodes against a whitelist], checking that the constants and names are valid, and executing it with plain, evil eval after that. You should just lose the ability to insert paranoid extra checks all over the execution. (Another disclaimer: I still wouldn't feel comfortable enough to do it with eval)
Anyway, I had a boring moment, so I wrote some code to do this the smart way; using the AST instead of the bytecode. It's just an extra flag to compile(). (Or just ast.parse(), since you'll want the types from the module anyway)
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, '', '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))
The same thing applies to this as to the bytecode version; if you check the operations against a whitelist and check that the names and values are valid, you should be able to get away with calling eval on the AST. (But again, I still wouldn't do it. Because paranoid. And paranoia is good when eval is concerned)