Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Changes

- switch to pytest

- The ``compile_restricted*`` functions now return a
``namedtuple CompileResult`` instead of a simple ``tuple``.


3.6.0 (2010-07-09)
------------------

Expand Down
9 changes: 7 additions & 2 deletions src/RestrictedPython/RCompile.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from compiler.pycodegen import Interactive
from compiler.pycodegen import Module
from compiler.pycodegen import ModuleCodeGenerator
from RestrictedPython import CompileResult
from RestrictedPython import MutatingWalker
from RestrictedPython.RestrictionMutator import RestrictionMutator

Expand Down Expand Up @@ -82,9 +83,13 @@ 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 None, (str(v),), gen.rm.warnings, gen.rm.used_names
return gen.getCode(), (), gen.rm.warnings, gen.rm.used_names
return CompileResult(
None, (str(v),), gen.rm.warnings, gen.rm.used_names)
return CompileResult(gen.getCode(), (), gen.rm.warnings, gen.rm.used_names)


def compile_restricted_function(p, body, name, filename, globalize=None):
Expand Down
1 change: 1 addition & 0 deletions src/RestrictedPython/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from RestrictedPython.compile import compile_restricted_exec
from RestrictedPython.compile import compile_restricted_function
from RestrictedPython.compile import compile_restricted_single
from RestrictedPython.compile import CompileResult
from RestrictedPython.Guards import safe_builtins
from RestrictedPython.Limits import limited_builtins
from RestrictedPython.PrintCollector import PrintCollector
Expand Down
31 changes: 15 additions & 16 deletions src/RestrictedPython/compile.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from collections import namedtuple
from RestrictedPython.transformer import RestrictingNodeTransformer

import ast


CompileResult = namedtuple(
'CompileResult', 'code, errors, warnings, used_names')


def _compile_restricted_mode(
source,
filename='<string>',
Expand All @@ -22,29 +27,23 @@ 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)
return byte_code, tuple(errors), warnings, used_names
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)


def compile_restricted_exec(
Expand Down
83 changes: 81 additions & 2 deletions tests/test_compile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,86 @@
from . import compile
from RestrictedPython import CompileResult
from RestrictedPython._compat import IS_PY2

import pytest
import RestrictedPython.compile


@pytest.mark.parametrize(*compile)
def test_compile__compile_restricted_exec__1(compile):
"""It returns a CompileResult on success."""
result = compile('a = 42')
assert result.__class__ == CompileResult
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__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 (<string>, 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 = """\
Expand All @@ -16,7 +95,7 @@ def no_exec():
@pytest.mark.parametrize(*compile)
def test_compile__compile_restricted_exec__10(compile):
"""It is a SyntaxError to use the `exec` statement. (Python 3 only)"""
code, errors, warnings, used_names = compile(EXEC_STATEMENT)
result = compile(EXEC_STATEMENT)
assert (
"Line 2: SyntaxError: Missing parentheses in call to 'exec' in on "
"statement: exec 'q = 1'",) == errors
"statement: exec 'q = 1'",) == result.errors
Loading