[IMP] safe_eval: do not log exceptions, when re-raising a new exception, make the...
[odoo/odoo.git] / openerp / tools / safe_eval.py
index 02fcbc1..c56c94e 100644 (file)
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
-#    Copyright (C) 2004-2010 OpenERP s.a. (<http://www.openerp.com>).
+#    Copyright (C) 2004-2012 OpenERP s.a. (<http://www.openerp.com>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -30,14 +30,12 @@ condition/math builtins.
 #  - http://code.activestate.com/recipes/286134/
 #  - safe_eval in lp:~xrg/openobject-server/optimize-5.0
 #  - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
-#  - python 2.6's ast.literal_eval
 
 from opcode import HAVE_ARGUMENT, opmap, opname
 from types import CodeType
 import logging
-import os
 
-__all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ]
+__all__ = ['test_expr', 'safe_eval', 'const_eval']
 
 # The time module is usually already provided in the safe_eval environment
 # but some code, e.g. datetime.datetime.now() (Windows/Python 2.5.2, bug
@@ -45,9 +43,9 @@ __all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ]
 _ALLOWED_MODULES = ['_strptime', 'time']
 
 _CONST_OPCODES = set(opmap[x] for x in [
-    'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP','POP_BLOCK','SETUP_LOOP',
-    'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
-    'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap)
+    'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'DUP_TOPX',
+    'POP_BLOCK','SETUP_LOOP', 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
+    'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR', 'STORE_MAP'] if x in opmap)
 
 _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
     'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
@@ -55,10 +53,14 @@ _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
     'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
     'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR',
     'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
-    'BINARY_OR'] if x in opmap))
+    'BINARY_OR', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
+    'INPLACE_DIVIDE', 'INPLACE_REMAINDER', 'INPLACE_POWER',
+    'INPLACE_LEFTSHIFT', 'INPLACE_RIGHTSHIFT', 'INPLACE_AND',
+    'INPLACE_XOR','INPLACE_OR'
+    ] if x in opmap))
 
 _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
-    'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
+    'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
     'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME',
     'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
     'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3',
@@ -67,7 +69,7 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
     'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY'
     ] if x in opmap))
 
-_logger = logging.getLogger('safe_eval')
+_logger = logging.getLogger(__name__)
 
 def _get_opcodes(codeobj):
     """_get_opcodes(codeobj) -> [opcodes]
@@ -104,11 +106,11 @@ def test_expr(expr, allowed_codes, mode="eval"):
             expr = expr.strip()
         code_obj = compile(expr, "", mode)
     except (SyntaxError, TypeError):
-        _logger.debug('Invalid eval expression', exc_info=True)
         raise
-    except Exception:
-        _logger.debug('Disallowed or invalid eval expression', exc_info=True)
-        raise ValueError("%s is not a valid expression" % expr)
+    except Exception, e:
+        import sys
+        exc_info = sys.exc_info()
+        raise ValueError, '"%s" while compiling\n%s' % (str(e), expr), exc_info[2]
     for code in _get_opcodes(code_obj):
         if code not in allowed_codes:
             raise ValueError("opcode %s not allowed (%r)" % (opname[code], expr))
@@ -157,62 +159,13 @@ def expr_eval(expr):
     c = test_expr(expr, _EXPR_OPCODES)
     return eval(c)
 
-
-# Port of Python 2.6's ast.literal_eval for use under Python 2.5
-SAFE_CONSTANTS = {'None': None, 'True': True, 'False': False}
-
-try:
-    # first, try importing directly
-    from ast import literal_eval
-except ImportError:
-    import _ast as ast
-
-    def _convert(node):
-        if isinstance(node, ast.Str):
-            return node.s
-        elif isinstance(node, ast.Num):
-            return node.n
-        elif isinstance(node, ast.Tuple):
-            return tuple(map(_convert, node.elts))
-        elif isinstance(node, ast.List):
-            return list(map(_convert, node.elts))
-        elif isinstance(node, ast.Dict):
-            return dict((_convert(k), _convert(v)) for k, v
-                        in zip(node.keys, node.values))
-        elif isinstance(node, ast.Name):
-            if node.id in SAFE_CONSTANTS:
-                return SAFE_CONSTANTS[node.id]
-        raise ValueError('malformed or disallowed expression')
-
-    def parse(expr, filename='<unknown>', mode='eval'):
-        """parse(source[, filename], mode]] -> code object
-        Parse an expression into an AST node.
-        Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
-        """
-        return compile(expr, filename, mode, ast.PyCF_ONLY_AST)
-
-    def literal_eval(node_or_string):
-        """literal_eval(expression) -> value
-        Safely evaluate an expression node or a string containing a Python
-        expression.  The string or node provided may only consist of the
-        following Python literal structures: strings, numbers, tuples,
-        lists, dicts, booleans, and None.
-
-        >>> literal_eval('[1,True,"spam"]')
-        [1, True, 'spam']
-
-        >>> literal_eval('1+3')
-        Traceback (most recent call last):
-        ...
-        ValueError: malformed or disallowed expression
-        """
-        if isinstance(node_or_string, basestring):
-            node_or_string = parse(node_or_string)
-        if isinstance(node_or_string, ast.Expression):
-            node_or_string = node_or_string.body
-        return _convert(node_or_string)
-
-def _import(name, globals={}, locals={}, fromlist=[], level=-1):
+def _import(name, globals=None, locals=None, fromlist=None, level=-1):
+    if globals is None:
+        globals = {}
+    if locals is None:
+        locals = {}
+    if fromlist is None:
+        fromlist = []
     if name in _ALLOWED_MODULES:
         return __import__(name, globals, locals, level)
     raise ImportError(name)
@@ -252,8 +205,9 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
         # isinstance() does not work below, we want *exactly* the dict class
         if (globals_dict is not None and type(globals_dict) is not dict) \
             or (locals_dict is not None and type(locals_dict) is not dict):
-            logging.getLogger('safe_eval').warning('Looks like you are trying to pass a dynamic environment,"\
-                              "you should probably pass nocopy=True to safe_eval()')
+            _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 locals_dict is not None:
@@ -283,6 +237,7 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
                 'set' : set
             }
     )
-    return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
+    c = test_expr(expr, _SAFE_OPCODES, mode=mode)
+    return eval(c, globals_dict, locals_dict)
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: