diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index b4750f7..a3fb2c0 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -494,7 +494,7 @@ def generic_visit(self, node): # ) self.warn( node, - '{o.__class__.__name__}' + '{0.__class__.__name__}' ' statement is not known to RestrictedPython'.format(node) ) self.not_allowed(node) @@ -515,43 +515,36 @@ def visit_Num(self, node): return self.node_contents_visit(node) def visit_Str(self, node): - """Allow strings without restrictions.""" + """Allow string literals without restrictions.""" return self.node_contents_visit(node) def visit_Bytes(self, node): - """Allow bytes without restrictions. + """Allow bytes literals without restrictions. Bytes is Python 3 only. """ - self.not_allowed(node) + return self.node_contents_visit(node) def visit_List(self, node): - """ - - """ + """Allow list literals without restrictions.""" return self.node_contents_visit(node) def visit_Tuple(self, node): - """ - - """ + """Allow tuple literals without restrictions.""" return self.node_contents_visit(node) def visit_Set(self, node): - """ - - """ - self.not_allowed(node) + """Allow set literals without restrictions.""" + return self.node_contents_visit(node) def visit_Dict(self, node): - """ - - """ + """Allow dict literals without restrictions.""" return self.node_contents_visit(node) def visit_Ellipsis(self, node): - """ + """Deny using `...`. + Ellipsis is exists only in Python 3. """ self.not_allowed(node) @@ -620,15 +613,16 @@ def visit_Starred(self, node): return self.node_contents_visit(node) # Expressions + def visit_Expression(self, node): """Allow Expression statements without restrictions. - Python 2 only AST Element. + + They are in the AST when using the `eval` compile mode. """ return self.node_contents_visit(node) def visit_Expr(self, node): - """Allow Expr statements without restrictions. - Python 3+ AST Element.""" + """Allow Expr statements (any expression) without restrictions.""" return self.node_contents_visit(node) def visit_UnaryOp(self, node): diff --git a/tests/__init__.py b/tests/__init__.py index 9e787b4..1374915 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,25 +3,48 @@ import RestrictedPython +def _compile(compile_func, source): + """Compile some source with a compile func.""" + result = compile_func(source) + assert result.errors == (), result.errors + assert result.code is not None + return result.code + + def _execute(compile_func): """Factory to create an execute function.""" def _execute(source, glb=None): - result = compile_func(source) - assert result.errors == (), result.errors - assert result.code is not None + code = _compile(compile_func, source) if glb is None: glb = {} - exec(result.code, glb) + exec(code, glb) return glb return _execute +def _eval(compile_func): + """Factory to create an eval function.""" + def _eval(source, glb=None): + code = _compile(compile_func, source) + if glb is None: + glb = {} + return eval(code, glb) + return _eval + + # Define the arguments for @pytest.mark.parametrize to be able to test both the # old and the new implementation to be equal: compile = ('compile', [RestrictedPython.compile.compile_restricted_exec]) +compile_eval = ('compile_eval', + [RestrictedPython.compile.compile_restricted_eval]) execute = ('execute', [_execute(RestrictedPython.compile.compile_restricted_exec)]) +r_eval = ('r_eval', + [_eval(RestrictedPython.compile.compile_restricted_eval)]) + if IS_PY2: from RestrictedPython import RCompile compile[1].append(RCompile.compile_restricted_exec) + compile_eval[1].append(RCompile.compile_restricted_eval) execute[1].append(_execute(RCompile.compile_restricted_exec)) + r_eval[1].append(_eval(RCompile.compile_restricted_eval)) diff --git a/tests/test_compile.py b/tests/test_compile.py index c5de7e9..c4aaf56 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -1,4 +1,6 @@ from . import compile +from . import compile_eval +from . import r_eval from RestrictedPython import compile_restricted from RestrictedPython import CompileResult from RestrictedPython._compat import IS_PY2 @@ -114,3 +116,29 @@ def test_compile__compile_restricted_exec__10(compile): assert ( "Line 2: SyntaxError: Missing parentheses in call to 'exec' in on " "statement: exec 'q = 1'",) == result.errors + + +FUNCTION_DEF = """\ +def a(): + pass +""" + + +@pytest.mark.parametrize(*compile_eval) +def test_compile__compile_restricted_eval__1(compile_eval): + """It compiles code as an Expression. + + Function definitions are not allowed in Expressions. + """ + result = compile_eval(FUNCTION_DEF) + if compile_eval is RestrictedPython.compile.compile_restricted_eval: + assert result.errors == ( + 'Line 1: SyntaxError: invalid syntax in on statement: def a():',) + else: + assert result.errors == ('invalid syntax (, line 1)',) + + +@pytest.mark.parametrize(*r_eval) +def test_compile__compile_restricted_eval__2(r_eval): + """It compiles code as an Expression.""" + assert r_eval('4 * 6') == 24 diff --git a/tests/test_transformer.py b/tests/test_transformer.py index ae71239..dc10a41 100644 --- a/tests/test_transformer.py +++ b/tests/test_transformer.py @@ -1,22 +1,59 @@ from . import compile from . import execute +from RestrictedPython import RestrictingNodeTransformer from RestrictedPython._compat import IS_PY2 from RestrictedPython._compat import IS_PY3 from RestrictedPython.Guards import guarded_iter_unpack_sequence from RestrictedPython.Guards import guarded_unpack_sequence +import ast import contextlib import pytest import RestrictedPython import types +def test_transformer__RestrictingNodeTransformer__generic_visit__1(): + """It log an error if there is an unknown ast node visited.""" + class MyFancyNode(ast.AST): + pass + + transformer = RestrictingNodeTransformer() + transformer.visit(MyFancyNode()) + assert transformer.errors == [ + 'Line None: MyFancyNode statements are not allowed.'] + assert transformer.warnings == [ + 'Line None: MyFancyNode statement is not known to RestrictedPython'] + + +@pytest.mark.parametrize(*execute) +def test_transformer__RestrictingNodeTransformer__visit_Num__1(execute): + """It allows to use number literals.""" + glb = execute('a = 42') + assert glb['a'] == 42 + + +@pytest.mark.parametrize(*execute) +def test_transformer__RestrictingNodeTransformer__visit_Bytes__1(execute): + """It allows to use bytes literals.""" + glb = execute('a = b"code"') + assert glb['a'] == b"code" + + +@pytest.mark.parametrize(*execute) +def test_transformer__RestrictingNodeTransformer__visit_Set__1(execute): + """It allows to use bytes literals.""" + glb = execute('a = {1, 2, 3}') + assert glb['a'] == set([1, 2, 3]) + + +@pytest.mark.skipif(IS_PY2, + reason="... is new in Python 3") @pytest.mark.parametrize(*compile) -def test_transformer__RestrictingNodeTransformer__visit_Num__1(compile): - """It compiles a number successfully.""" - result = compile('42') - assert result.errors == () - assert str(result.code.__class__.__name__) == 'code' +def test_transformer__RestrictingNodeTransformer__visit_Ellipsis__1(compile): + """It prevents using the `ellipsis` statement.""" + result = compile('...') + assert result.errors == ('Line 1: Ellipsis statements are not allowed.',) @pytest.mark.parametrize(*compile)