It looks like the float() cast was the solution after all.
First of all, the inverse trigonometric functions don't take values outside of their domain, so they're completely safe and the exception can be caught.
>>> acos(5e100)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: math domain error
The same thing happens with the fmod() function.
The "normal" trigonometric functions don't seem to have any problem with big values unless they're really big, which makes the function return ValueError again.
The rounding functions (ceil(), floor() and round()) work fine and return inf if the value is too big. The same goes for the degrees(), log(), log10(), pow(), sqrt(), fabs(), hypot() and radians() functions.
The hyperbolic trigonometric functions and the exp() function throw OverflowErrors or return inf.
The atan2() function works perfectly fine with big values.
For simple arithmetic operations, the float cast makes the function throw an OverflowError (or an inf) instead of doing the calculation.
>>> float(10) ** float(100) ** float(100) ** float(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: (34, 'Numerical result out of range')
>>> float(5e500) * float(4e1000)
inf
Lastly, the problematic factorial() function. All I had to do was redefining the function in an iterative way and adding it to safe_dict.
import sys
def factorial(n):
fact = 1
while (n > 0):
fact = float(fact) * float(n)
n -= float(1)
if float(fact) > sys.float_info.max:
return "Too big"
return str(fact)
print factorial(50e500)
While this is a very ugly and grossly inefficient way of calculating a factorial, it's enough for my needs. In fact, I think I added a lot of unnecessary float()s.
Now I need to figure out how to put float()s around all the terms in an expression so this happens automatically.
eval, but I've tried to execute dangerous commands for a long time and I couldn't do anything. I know this doesn't guarantee that it's 100% safe but the users that will be using are "semi-trusted", meaning that they're not going to do weird stuff withosbut I'm sure they will just kill the bot for fun.ast.literal_evalinstead. Also, run your user input in a separate thread with a timeout. this will avoid problems from doing something likefactorial(1000000).[]_;"'. I'm hoping that makes a lot harder to abuseeval. Oh, and theevalin my function is explicitly using{"__builtins__": None}instead of{'__builtins__':{}}, but I'm not sure if it makes a difference. Either way, I'll useast.literal_evaland see what happens.