1
2 """
3 Safe math context for evaluating expressions.
4
5 The following are defined::
6
7 context - dictionary of math functions suitable for use as global in eval
8 seval(expr) - expression evaluator
9 sexec(expr) - code evaluator
10 show() - display the symbols available in contexgt
11
12 The math symbols (functions, pi, e, inf, nan) are pulled from numpy and scipy
13 so they will work equally well with scalars and vectors, real and complex.
14
15 The restricted environment does not allow any use of the underscore
16 character. This suppresses most python introspection exploits. Of
17 course, it also means that user code cannot define new classes or many
18 of the other things a plugin environment may want to support.
19
20 Note: DoS attacks are pretty much impossible to stop if you give access
21 to the interpreter. E.g., 'a'*2**64 will exceed memory, as will
22 array([1,2]*2**64). Running the service in a separate process with
23 resource limits can mitigate the problem, as well as protect against
24 accidental infinite loops. If this process is running in a chroot jail
25 or on a virtual machine, then eval/exec can be used directly.
26 """
27
28
29 __all__ = ['context','eval','show']
30
31 import __builtin__
32 import numpy
33
34
35 try:
36 import scipy.special
37 special = scipy.special.__dict__
38 except ImportError:
39 special = {}
40
42 """
43 Return a context suitable for evaluating math expressions.
44
45 The context is a subset of the following:
46
47 from numpy import *
48 from numpy.linalg import *
49 from scipy.special import *
50
51 Most of the mathematical functions are included.
52 """
53 context = {'__builtins__':None,
54 '__name__':'__safemath__',
55 }
56
57
58 for fn in [
59 'True', 'False', 'None',
60 'enumerate', 'zip', 'range', 'reversed',
61 ]:
62 context[fn] = getattr(__builtin__,fn)
63
64
65 for v in ['e','pi','inf','nan']:
66 context[v] = numpy.__dict__[v]
67 context['Inf'] = numpy.inf
68 context['INF'] = numpy.inf
69 context['NaN'] = numpy.nan
70 context['NAN'] = numpy.nan
71
72
73 for base in ['cos','sin','tan']:
74 for fn in [base, base+'h']:
75 context[fn] = numpy.__dict__[fn]
76 context['a'+fn] = numpy.__dict__['arc'+fn]
77 context['arc'+fn] = numpy.__dict__['arc'+fn]
78 context['atan2'] = numpy.__dict__['arctan2']
79 context['arctan2'] = numpy.__dict__['arctan2']
80
81
82 for fn in [
83 'abs','angle', 'around','clip', 'degrees', 'e',
84 'exp', 'expm1', 'fabs', 'hypot', 'imag',
85 'iscomplex', 'isfinite', 'isinf', 'isnan', 'isreal', 'isscalar',
86 'log', 'log10', 'log1p', 'log2', 'polyval', 'radians',
87 'round', 'sign', 'sinc', 'sqrt', 'square', 'unwrap',
88 ]:
89
90 if fn in numpy.__dict__:
91 context[fn] = numpy.__dict__[fn]
92
93
94 for fn in [
95 'airy','airye',
96 'jn', 'jv', 'yn', 'yv', 'kn', 'kv', 'iv',
97 'j0', 'j1', 'y0', 'y1', 'k0', 'k1', 'i0', 'i1',
98 'gamma', 'gammaln', 'gammainc',
99 'beta', 'betaln', 'betainc',
100 'erf', 'erfc', 'erfinv', 'erfcinv',
101 'expn', 'exp1', 'expi',
102 'wofz', 'dawsn', 'shichi', 'sici', 'spence', 'zeta', 'zetac',
103 ]:
104 if fn in special:
105 context[fn] = special[fn]
106
107
108
109 for fn in [
110 'all', 'allclose', 'amax', 'amin', 'any', 'convolve',
111 'corrcoef', 'correlate', 'cov', 'cross', 'cumprod', 'cumsum',
112 'diff', 'dot', 'histogram', 'interp', 'kron',
113 'mean', 'median', 'outer', 'poly', 'prod', 'roots', 'std',
114 'sort', 'sum', 'trace', 'trapz', 'var', 'vdot',
115 ]:
116 context[fn] = numpy.__dict__[fn]
117
118
119 for fn in [
120 'array', 'arange', 'choose', 'dstack', 'diag',
121 'extract', 'eye', 'hstack', 'linspace', 'logspace',
122 'meshgrid', 'ones', 'reshape', 'searchsorted', 'select',
123 'squeeze', 'take', 'tri', 'tril', 'triu', 'vstack',
124 'where', 'zeros',
125 ]:
126
127 if fn in numpy.__dict__:
128 context[fn] = numpy.__dict__[fn]
129
130
131 for fn in [
132 'cholesky', 'cond', 'det',
133 'eig', 'eigh', 'eigvals', 'eigvalsh', 'inv', 'lstsq',
134 'norm', 'pinv', 'qr', 'solve', 'svd',
135 'tensorinv','tensorsolve'
136 ]:
137
138 if fn in numpy.linalg.__dict__:
139 context[fn] = numpy.linalg.__dict__[fn]
140
141
142
143
144
145 if 'chr' in context:
146 raise RuntimeError('chr() snuck into context')
147 return context
148
149 context = _math_context()
150 """Table of available mathematical functions"""
151
153 """
154 Evaluate an expression containing math functions.
155 """
156 if '_' in expr: raise ValueError('expression contains underscores')
157 return eval(expr,context,locals)
158
160 """
161 Evaluate code in a restricted context, returning a dictionary of
162 local variables. Context should include all symbols that the code
163 might need; no import statements will be allowed.
164 """
165 if '_' in code: raise ValueError('code contains underscores')
166 exec code in context,locals
167
169 """
170 Show the functions available in the math context.
171 """
172 keys = context.keys()
173 keys.sort()
174 print ", ".join(keys)
175
177
178
179
180
181
182
183
184
185 import configobj
186 s = """
187 all_types = ().__class__.__bases__[0].__subclasses__()
188 zipimport = [x for x in all_types if x.__name__ == "zipimporter"][0]
189 egg = "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site-packages/configobj-4.5.3-py2.5.egg"
190 x = zipimport(egg)
191 mod = x.load_module("configobj")
192
193 print "system", mod.os.system("ls")
194 """
195 sexec(s)
196
198
199 assert seval("3*pi") == 3*numpy.pi
200
201
202 class Table:
203 Si = 2.07
204 values = {'Au': 4}
205 table = Table()
206 def f(x): return x+3
207 vars = dict(f=f, table=table, x=3.5, vec=numpy.arange(10))
208 assert seval("f(3)",vars) == 6
209 assert seval("table.Si*4*pi", vars) == 2.07*4*numpy.pi
210 assert seval("table.values['Au']+3", vars) == 7
211 assert seval("sum(vec)", vars) == 45
212 assert seval("x**2",vars) == 3.5**2
213 assert seval("True") == True
214 assert seval("False") == False
215 assert seval("None") is None
216 assert seval('zip([1,2,3],[4,5,6])') == [(1,4),(2,5),(3,6)]
217
218
219
220
221
222
223
224 for expr in ['G4.cage', 'M0.cage', 'M1.G1 + *2',
225 'piddle',
226 'open("hello","w")',
227 '().__class__',
228 'import sys; print sys.platform',
229 '__import__("sys").platform',
230 '(lambda: __import__("sys").argv)()',
231 "poly.func_globals['__builtins__']['__import__']('sys').platform",
232 ]:
233 try: seval(expr)
234 except Exception,msg: pass
235 else: raise "Failed to raise error for %s"%expr
236 try: sexec(expr)
237 except Exception,msg: pass
238 else: raise "Failed to raise error for %s"%expr
239
240 if __name__ == "__main__": test()
241