diff --git a/src/RestrictedPython/RCompile.py b/src/RestrictedPython/RCompile.py index 80e439c..d046076 100644 --- a/src/RestrictedPython/RCompile.py +++ b/src/RestrictedPython/RCompile.py @@ -83,6 +83,9 @@ def compile(self): def _compileAndTuplize(gen): try: gen.compile() + except TypeError as v: + return CompileResult( + None, (str(v),), gen.rm.warnings, gen.rm.used_names) except SyntaxError as v: return CompileResult( None, (str(v),), gen.rm.warnings, gen.rm.used_names) diff --git a/src/RestrictedPython/compile.py b/src/RestrictedPython/compile.py index c611db6..7f0d674 100644 --- a/src/RestrictedPython/compile.py +++ b/src/RestrictedPython/compile.py @@ -27,28 +27,22 @@ def _compile_restricted_mode( c_ast = None try: c_ast = ast.parse(source, filename, mode) + except (TypeError, ValueError) as e: + errors.append(str(e)) except SyntaxError as v: - c_ast = None errors.append('Line {lineno}: {type}: {msg} in on statement: {statement}'.format( lineno=v.lineno, type=v.__class__.__name__, msg=v.msg, statement=v.text.strip() )) - try: - if c_ast: - policy(errors, warnings, used_names).visit(c_ast) - if not errors: - byte_code = compile(c_ast, filename, mode=mode # , - #flags=flags, - #dont_inherit=dont_inherit - ) - except SyntaxError as v: - byte_code = None - errors.append(v) - except TypeError as v: - byte_code = None - errors.append(v) + if c_ast: + policy(errors, warnings, used_names).visit(c_ast) + if not errors: + byte_code = compile(c_ast, filename, mode=mode # , + #flags=flags, + #dont_inherit=dont_inherit + ) return CompileResult(byte_code, tuple(errors), warnings, used_names) diff --git a/tests/test_compile.py b/tests/test_compile.py index 7cf3879..c7a8201 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -3,6 +3,7 @@ from RestrictedPython._compat import IS_PY2 import pytest +import RestrictedPython.compile @pytest.mark.parametrize(*compile) @@ -18,6 +19,70 @@ def test_compile__compile_restricted_exec__1(compile): assert glob['a'] == 42 +@pytest.mark.parametrize(*compile) +def test_compile__compile_restricted_exec__2(compile): + """It compiles without restrictions if there is no policy.""" + if compile is RestrictedPython.compile.compile_restricted_exec: + # The old version does not support a custom policy + result = compile('_a = 42', policy=None) + assert result.errors == () + assert result.warnings == [] + assert result.used_names == {} + glob = {} + exec(result.code, glob) + assert glob['_a'] == 42 + + +@pytest.mark.parametrize(*compile) +def test_compile__compile_restricted_exec__3(compile): + """It returns a tuple of errors if the code is not allowed. + + There is no code in this case. + """ + result = compile('_a = 42\n_b = 43') + errors = ( + 'Line 1: "_a" is an invalid variable name because it starts with "_"', + 'Line 2: "_b" is an invalid variable name because it starts with "_"') + if compile is RestrictedPython.compile.compile_restricted_exec: + assert result.errors == errors + else: + # The old version did only return the first error message. + assert result.errors == (errors[0],) + assert result.warnings == [] + assert result.used_names == {} + assert result.code is None + + +@pytest.mark.parametrize(*compile) +def test_compile__compile_restricted_exec__4(compile): + """It does not return code on a SyntaxError.""" + result = compile('asdf|') + assert result.code is None + assert result.warnings == [] + assert result.used_names == {} + if compile is RestrictedPython.compile.compile_restricted_exec: + assert result.errors == ( + 'Line 1: SyntaxError: invalid syntax in on statement: asdf|',) + else: + # The old version had a less nice error message: + assert result.errors == ('invalid syntax (, line 1)',) + + +@pytest.mark.parametrize(*compile) +def test_compile__compile_restricted_exec__5(compile): + """It does not return code if the code contains a NULL byte.""" + result = compile('a = 5\x00') + assert result.code is None + assert result.warnings == [] + assert result.used_names == {} + if IS_PY2: + assert result.errors == ( + 'compile() expected string without null bytes',) + else: + assert result.errors == ( + 'source code string cannot contain null bytes',) + + EXEC_STATEMENT = """\ def no_exec(): exec 'q = 1'