From 4e89925fb5365a14d4748caed2ca4122bc1be40c Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 15 Sep 2017 10:58:38 +0200 Subject: [PATCH 1/2] Drop the old implementation of version 3.x. --- .travis.yml | 6 +- docs/CHANGES.rst | 4 + setup.cfg | 3 - src/RestrictedPython/MutatingWalker.py | 85 --- src/RestrictedPython/RCompile.py | 314 ------------ src/RestrictedPython/README.rst | 3 +- src/RestrictedPython/RestrictionMutator.py | 421 --------------- src/RestrictedPython/SelectCompiler.py | 39 -- src/RestrictedPython/__init__.py | 10 +- src/RestrictedPython/tests/__init__.py | 1 - src/RestrictedPython/tests/class.py | 13 - .../tests/restricted_module.py | 210 -------- src/RestrictedPython/tests/testCompile.py | 36 -- src/RestrictedPython/tests/testREADME.py | 26 - .../tests/testRestrictions.py | 484 ------------------ src/RestrictedPython/tests/unpack.py | 91 ---- src/RestrictedPython/tests/verify.py | 212 -------- tests/__init__.py | 15 - tests/test_print_stmt.py | 51 +- tox.ini | 10 - 20 files changed, 24 insertions(+), 2010 deletions(-) delete mode 100644 src/RestrictedPython/MutatingWalker.py delete mode 100644 src/RestrictedPython/RCompile.py delete mode 100644 src/RestrictedPython/RestrictionMutator.py delete mode 100644 src/RestrictedPython/SelectCompiler.py delete mode 100644 src/RestrictedPython/tests/__init__.py delete mode 100644 src/RestrictedPython/tests/class.py delete mode 100644 src/RestrictedPython/tests/restricted_module.py delete mode 100644 src/RestrictedPython/tests/testCompile.py delete mode 100644 src/RestrictedPython/tests/testREADME.py delete mode 100644 src/RestrictedPython/tests/testRestrictions.py delete mode 100644 src/RestrictedPython/tests/unpack.py delete mode 100644 src/RestrictedPython/tests/verify.py diff --git a/.travis.yml b/.travis.yml index 1d2e4cd..77ba625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,17 +7,17 @@ python: - 3.6 env: - ENVIRON=py - - ENVIRON=py27-rp3,py27-datetime,py36-datetime + - ENVIRON=py27-datetime,py36-datetime - ENVIRON=isort,flake8,docs matrix: exclude: - env: ENVIRON=isort,flake8,docs - - env: ENVIRON=py27-rp3,py27-datetime,py36-datetime + - env: ENVIRON=py27-datetime,py36-datetime include: - python: "3.6" env: ENVIRON=py36-datetime,isort,flake8,docs - python: "2.7" - env: ENVIRON=py27-rp3,py27-datetime + env: ENVIRON=py27-datetime install: - pip install tox coveralls coverage script: diff --git a/docs/CHANGES.rst b/docs/CHANGES.rst index f0ff344..b851f2d 100644 --- a/docs/CHANGES.rst +++ b/docs/CHANGES.rst @@ -14,6 +14,10 @@ Changes ``RestrictedPython.Guards.safer_getattr()`` in your implementation to benefit from this fix. +- Drop the old implementation of version 3.x: `RCompile.py`, + `SelectCompiler.py`, `MutatingWorker.py`, `RestrictionMutator.py` and + `tests/verify.py`. + - Remove ``__len__`` method in ``.Guards._write_wrapper`` because it is no longer reachable by code using the wrapper. diff --git a/setup.cfg b/setup.cfg index 3be0e3e..9d28525 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,6 @@ addopts = testpaths = . tests - src/RestrictedPython/tests norecursedirs = fixures @@ -67,8 +66,6 @@ omit = # code branches and tests that that did not get executed. # Therefore we include tests into coverage analysis for the moment. #tests/*.py - #src/RestrictedPython/tests - #src/RestrictedPython/tests/*.py [coverage:report] precision = 2 diff --git a/src/RestrictedPython/MutatingWalker.py b/src/RestrictedPython/MutatingWalker.py deleted file mode 100644 index 4aaa1d1..0000000 --- a/src/RestrictedPython/MutatingWalker.py +++ /dev/null @@ -1,85 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## - -from compiler import ast - -import warnings - - -warnings.warn( - "This Module (RestrictedPython.MutatingWalker) is deprecated" - "and will be gone soon.", - category=PendingDeprecationWarning, - stacklevel=1 -) - - -ListType = type([]) -TupleType = type(()) -SequenceTypes = (ListType, TupleType) - - -class MutatingWalker: - - def __init__(self, visitor): - self.visitor = visitor - self._cache = {} - - def defaultVisitNode(self, node, walker=None, exclude=None): - for name, child in node.__dict__.items(): - if exclude is not None and name in exclude: - continue - v = self.dispatchObject(child) - if v is not child: - # Replace the node. - node.__dict__[name] = v - return node - - def visitSequence(self, seq): - res = seq - for idx in range(len(seq)): - child = seq[idx] - v = self.dispatchObject(child) - if v is not child: - # Change the sequence. - if type(res) is ListType: - res[idx: idx + 1] = [v] - else: - res = res[:idx] + (v,) + res[idx + 1:] - return res - - def dispatchObject(self, ob): - ''' - Expected to return either ob or something that will take - its place. - ''' - if isinstance(ob, ast.Node): - return self.dispatchNode(ob) - elif type(ob) in SequenceTypes: - return self.visitSequence(ob) - else: - return ob - - def dispatchNode(self, node): - klass = node.__class__ - meth = self._cache.get(klass, None) - if meth is None: - className = klass.__name__ - meth = getattr(self.visitor, 'visit' + className, - self.defaultVisitNode) - self._cache[klass] = meth - return meth(node, self) - - -def walk(tree, visitor): - return MutatingWalker(visitor).dispatchNode(tree) diff --git a/src/RestrictedPython/RCompile.py b/src/RestrictedPython/RCompile.py deleted file mode 100644 index 12c7be3..0000000 --- a/src/RestrictedPython/RCompile.py +++ /dev/null @@ -1,314 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## -"""Compiles restricted code using the compiler module from the -Python standard library. -""" - -from compile import CompileResult -from compiler import ast -from compiler import misc -from compiler import parse -from compiler import pycodegen -from compiler import syntax -from compiler.pycodegen import AbstractCompileMode -from compiler.pycodegen import Expression -from compiler.pycodegen import findOp -from compiler.pycodegen import FunctionCodeGenerator # noqa -from compiler.pycodegen import Interactive -from compiler.pycodegen import Module -from compiler.pycodegen import ModuleCodeGenerator -from RestrictionMutator import RestrictionMutator - -import MutatingWalker -import warnings - - -warnings.warn( - "This Module (RestrictedPython.RCompile) is deprecated" - "and will be gone soon.", - category=PendingDeprecationWarning, - stacklevel=1 -) - - -def niceParse(source, filename, mode): - if isinstance(source, unicode): - # Use the utf-8-sig BOM so the compiler - # detects this as a UTF-8 encoded string. - source = '\xef\xbb\xbf' + source.encode('utf-8') - try: - return parse(source, mode) - except: - # Try to make a clean error message using - # the builtin Python compiler. - try: - compile(source, filename, mode) - except SyntaxError: - raise - # Some other error occurred. - raise - - -class RestrictedCompileMode(AbstractCompileMode): - """Abstract base class for hooking up custom CodeGenerator.""" - # See concrete subclasses below. - - def __init__(self, source, filename): - if source: - source = '\n'.join(source.splitlines()) + '\n' - self.rm = RestrictionMutator() - AbstractCompileMode.__init__(self, source, filename) - - def parse(self): - return niceParse(self.source, self.filename, self.mode) - - def _get_tree(self): - tree = self.parse() - MutatingWalker.walk(tree, self.rm) - if self.rm.errors: - raise SyntaxError(self.rm.errors[0]) - misc.set_filename(self.filename, tree) - syntax.check(tree) - return tree - - def compile(self): - tree = self._get_tree() - gen = self.CodeGeneratorClass(tree) - self.code = gen.getCode() - - -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) - return CompileResult(gen.getCode(), (), gen.rm.warnings, gen.rm.used_names) - - -def compile_restricted_function(p, body, name, filename, globalize=None): - """Compiles a restricted code object for a function. - - The function can be reconstituted using the 'new' module: - - new.function(, ) - - The globalize argument, if specified, is a list of variable names to be - treated as globals (code is generated as if each name in the list - appeared in a global statement at the top of the function). - """ - warnings.warn( - "RestrictedPython.RCompile.compile_restricted_function is deprecated" - "use RestrictedPython.compile_restricted_function instead.", - category=PendingDeprecationWarning, - stacklevel=1 - ) - gen = RFunction(p, body, name, filename, globalize) - return compileAndTuplize(gen) - - -def compile_restricted_exec(source, filename=''): - """Compiles a restricted code suite.""" - warnings.warn( - "RestrictedPython.RCompile.compile_restricted_exec is deprecated" - "use RestrictedPython.compile_restricted_exec instead.", - category=PendingDeprecationWarning, - stacklevel=1 - ) - gen = RModule(source, filename) - return compileAndTuplize(gen) - - -def compile_restricted_eval(source, filename=''): - """Compiles a restricted expression.""" - warnings.warn( - "RestrictedPython.RCompile.compile_restricted_eval is deprecated" - "use RestrictedPython.compile_restricted_eval instead.", - category=PendingDeprecationWarning, - stacklevel=1 - ) - gen = RExpression(source, filename) - return compileAndTuplize(gen) - - -def compile_restricted_single(source, filename=''): - """Compiles a restricted expression.""" - warnings.warn( - "RestrictedPython.RCompile.compile_restricted_single is deprecated" - "use RestrictedPython.compile_restricted_single instead.", - category=PendingDeprecationWarning, - stacklevel=1 - ) - gen = RInteractive(source, filename) - return compileAndTuplize(gen) - - -def compile_restricted(source, filename, mode): - """Replacement for the builtin compile() function.""" - warnings.warn( - "RestrictedPython.RCompile.compile_restricted is deprecated" - "use RestrictedPython.compile_restricted instead.", - category=PendingDeprecationWarning, - stacklevel=1 - ) - if mode == "single": - gen = RInteractive(source, filename) - elif mode == "exec": - gen = RModule(source, filename) - elif mode == "eval": - gen = RExpression(source, filename) - else: - raise ValueError("compile_restricted() 3rd arg must be 'exec' or " - "'eval' or 'single'") - gen.compile() - return gen.getCode() - - -class RestrictedCodeGenerator: - """Mixin for CodeGenerator to replace UNPACK_SEQUENCE bytecodes. - - The UNPACK_SEQUENCE opcode is not safe because it extracts - elements from a sequence without using a safe iterator or - making __getitem__ checks. - - This code generator replaces use of UNPACK_SEQUENCE with calls to - a function that unpacks the sequence, performes the appropriate - security checks, and returns a simple list. - """ - - # Replace the standard code generator for assignments to tuples - # and lists. - - def _gen_safe_unpack_sequence(self, num): - # We're at a place where UNPACK_SEQUENCE should be generated, to - # unpack num items. That's a security hole, since it exposes - # individual items from an arbitrary iterable. We don't remove - # the UNPACK_SEQUENCE, but instead insert a call to our _getiter_() - # wrapper first. That applies security checks to each item as - # it's delivered. codegen is (just) a bit messy because the - # iterable is already on the stack, so we have to do a stack swap - # to get things in the right order. - self.emit('LOAD_GLOBAL', '_getiter_') - self.emit('ROT_TWO') - self.emit('CALL_FUNCTION', 1) - self.emit('UNPACK_SEQUENCE', num) - - def _visitAssSequence(self, node): - if findOp(node) != 'OP_DELETE': - self._gen_safe_unpack_sequence(len(node.nodes)) - for child in node.nodes: - self.visit(child) - - visitAssTuple = _visitAssSequence - visitAssList = _visitAssSequence - - # Call to generate code for unpacking nested tuple arguments - # in function calls. - - def unpackSequence(self, tup): - self._gen_safe_unpack_sequence(len(tup)) - for elt in tup: - if isinstance(elt, tuple): - self.unpackSequence(elt) - else: - self._nameOp('STORE', elt) - -# A collection of code generators that adds the restricted mixin to -# handle unpacking for all the different compilation modes. They -# are defined here (at the end) so that can refer to RestrictedCodeGenerator. - - -class RestrictedFunctionCodeGenerator(RestrictedCodeGenerator, - pycodegen.FunctionCodeGenerator): - pass - - -class RestrictedExpressionCodeGenerator(RestrictedCodeGenerator, - pycodegen.ExpressionCodeGenerator): - pass - - -class RestrictedInteractiveCodeGenerator(RestrictedCodeGenerator, - pycodegen.InteractiveCodeGenerator): - pass - - -class RestrictedModuleCodeGenerator(RestrictedCodeGenerator, - pycodegen.ModuleCodeGenerator): - - def initClass(self): - ModuleCodeGenerator.initClass(self) - self.__class__.FunctionGen = RestrictedFunctionCodeGenerator - - -# These subclasses work around the definition of stub compile and mode -# attributes in the common base class AbstractCompileMode. If it -# didn't define new attributes, then the stub code inherited via -# RestrictedCompileMode would override the real definitions in -# Expression. - -class RExpression(RestrictedCompileMode, Expression): - mode = "eval" - CodeGeneratorClass = RestrictedExpressionCodeGenerator - - -class RInteractive(RestrictedCompileMode, Interactive): - mode = "single" - CodeGeneratorClass = RestrictedInteractiveCodeGenerator - - -class RModule(RestrictedCompileMode, Module): - mode = "exec" - CodeGeneratorClass = RestrictedModuleCodeGenerator - - -class RFunction(RModule): - """A restricted Python function built from parts.""" - - CodeGeneratorClass = RestrictedModuleCodeGenerator - - def __init__(self, p, body, name, filename, globals): - self.params = p - if body: - body = '\n'.join(body.splitlines()) + '\n' - self.body = body - self.name = name - self.globals = globals or [] - RModule.__init__(self, None, filename) - - def parse(self): - # Parse the parameters and body, then combine them. - firstline = 'def f(%s): pass' % self.params - tree = niceParse(firstline, '', 'exec') - f = tree.node.nodes[0] - body_code = niceParse(self.body, self.filename, 'exec') - # Stitch the body code into the function. - f.code.nodes = body_code.node.nodes - f.name = self.name - # Look for a docstring, if there are any nodes at all - if len(f.code.nodes) > 0: - stmt1 = f.code.nodes[0] - if (isinstance(stmt1, ast.Discard) and - isinstance(stmt1.expr, ast.Const) and - isinstance(stmt1.expr.value, str)): - f.doc = stmt1.expr.value - # The caller may specify that certain variables are globals - # so that they can be referenced before a local assignment. - # The only known example is the variables context, container, - # script, traverse_subpath in PythonScripts. - if self.globals: - f.code.nodes.insert(0, ast.Global(self.globals)) - return tree diff --git a/src/RestrictedPython/README.rst b/src/RestrictedPython/README.rst index 0afc4d0..4ccfc8e 100644 --- a/src/RestrictedPython/README.rst +++ b/src/RestrictedPython/README.rst @@ -27,8 +27,7 @@ global namespace: Compatibility ============= -This release of RestrictedPython is compatible with Python 2.3, 2.4, 2.5, 2.6, -and 2.7. +This release of RestrictedPython is compatible with Python 2.7, 3.4, 3.5, 3.6. Implementing a policy ===================== diff --git a/src/RestrictedPython/RestrictionMutator.py b/src/RestrictedPython/RestrictionMutator.py deleted file mode 100644 index 743a0cb..0000000 --- a/src/RestrictedPython/RestrictionMutator.py +++ /dev/null @@ -1,421 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## -"""Modify AST to include security checks. - -RestrictionMutator modifies a tree produced by -compiler.transformer.Transformer, restricting and enhancing the -code in various ways before sending it to pycodegen. -""" - -from compiler import ast -from compiler.consts import OP_APPLY -from compiler.consts import OP_ASSIGN -from compiler.consts import OP_DELETE -from compiler.transformer import parse - -import warnings - - -warnings.warn( - "This Module (RestrictedPython.RestrictionMutator) is deprecated" - "and will be gone soon.", - category=PendingDeprecationWarning, - stacklevel=1 -) - - -# These utility functions allow us to generate AST subtrees without -# line number attributes. These trees can then be inserted into other -# trees without affecting line numbers shown in tracebacks, etc. -def rmLineno(node): - """Strip lineno attributes from a code tree.""" - if 'lineno' in node.__dict__: - del node.lineno - for child in node.getChildren(): - if isinstance(child, ast.Node): - rmLineno(child) - - -def stmtNode(txt): - """Make a "clean" statement node.""" - node = parse(txt).node.nodes[0] - rmLineno(node) - return node - - -# The security checks are performed by a set of six functions that -# must be provided by the restricted environment. - -_apply_name = ast.Name("_apply_") -_getattr_name = ast.Name("_getattr_") -_getitem_name = ast.Name("_getitem_") -_getiter_name = ast.Name("_getiter_") -_print_target_name = ast.Name("_print") -_write_name = ast.Name("_write_") -_inplacevar_name = ast.Name("_inplacevar_") - -# Constants. -_None_const = ast.Const(None) -_write_const = ast.Const("write") - -_printed_expr = stmtNode("_print()").expr -_print_target_node = stmtNode("_print = _print_()") - - -class FuncInfo(object): - print_used = False - printed_used = False - - -class RestrictionMutator: - - def __init__(self): - self.warnings = [] - self.errors = [] - self.used_names = {} - self.funcinfo = FuncInfo() - - def error(self, node, info): - """Records a security error discovered during compilation.""" - lineno = getattr(node, 'lineno', None) - if lineno is not None and lineno > 0: - self.errors.append('Line %d: %s' % (lineno, info)) - else: - self.errors.append(info) - - def checkName(self, node, name): - """Verifies that a name being assigned is safe. - - This is to prevent people from doing things like: - - __metatype__ = mytype (opens up metaclasses, a big unknown - in terms of security) - __path__ = foo (could this confuse the import machinery?) - _getattr = somefunc (not very useful, but could open a hole) - - Note that assigning a variable is not the only way to assign - a name. def _badname, class _badname, import foo as _badname, - and perhaps other statements assign names. Special case: - '_' is allowed. - """ - if name.startswith("_") and name != "_": - # Note: "_" *is* allowed. - self.error(node, '"%s" is an invalid variable name because' - ' it starts with "_"' % name) - if name.endswith('__roles__'): - self.error(node, '"%s" is an invalid variable name because ' - 'it ends with "__roles__".' % name) - if name == "printed": - self.error(node, '"printed" is a reserved name.') - - def checkAttrName(self, node): - """Verifies that an attribute name does not start with _. - - As long as guards (security proxies) have underscored names, - this underscore protection is important regardless of the - security policy. Special case: '_' is allowed. - """ - name = node.attrname - if name.startswith("_") and name != "_": - # Note: "_" *is* allowed. - self.error(node, '"%s" is an invalid attribute name ' - 'because it starts with "_".' % name) - if name.endswith('__roles__'): - self.error(node, '"%s" is an invalid attribute name ' - 'because it ends with "__roles__".' % name) - - def prepBody(self, body): - """Insert code for print at the beginning of the code suite.""" - - if self.funcinfo.print_used or self.funcinfo.printed_used: - # Add code at top for creating _print_target - body.insert(0, _print_target_node) - if not self.funcinfo.printed_used: - self.warnings.append( - "Prints, but never reads 'printed' variable.") - elif not self.funcinfo.print_used: - self.warnings.append( - "Doesn't print, but reads 'printed' variable.") - - def visitFunction(self, node, walker): - """Checks and mutates a function definition. - - Checks the name of the function and the argument names using - checkName(). It also calls prepBody() to prepend code to the - beginning of the code suite. - """ - self.checkName(node, node.name) - for argname in node.argnames: - if isinstance(argname, str): - self.checkName(node, argname) - else: - for name in argname: - self.checkName(node, name) - walker.visitSequence(node.defaults) - - former_funcinfo = self.funcinfo - self.funcinfo = FuncInfo() - node = walker.defaultVisitNode(node, exclude=('defaults',)) - self.prepBody(node.code.nodes) - self.funcinfo = former_funcinfo - return node - - def visitLambda(self, node, walker): - """Checks and mutates an anonymous function definition. - - Checks the argument names using checkName(). It also calls - prepBody() to prepend code to the beginning of the code suite. - """ - for argname in node.argnames: - self.checkName(node, argname) - return walker.defaultVisitNode(node) - - def visitPrint(self, node, walker): - """Checks and mutates a print statement. - - Adds a target to all print statements. 'print foo' becomes - 'print >> _print, foo', where _print is the default print - target defined for this scope. - - Alternatively, if the untrusted code provides its own target, - we have to check the 'write' method of the target. - 'print >> ob, foo' becomes - 'print >> (_getattr(ob, 'write') and ob), foo'. - Otherwise, it would be possible to call the write method of - templates and scripts; 'write' happens to be the name of the - method that changes them. - """ - node = walker.defaultVisitNode(node) - self.funcinfo.print_used = True - if node.dest is None: - node.dest = _print_target_name - else: - # Pre-validate access to the "write" attribute. - # "print >> ob, x" becomes - # "print >> (_getattr(ob, 'write') and ob), x" - node.dest = ast.And([ - ast.CallFunc(_getattr_name, [node.dest, _write_const]), - node.dest]) - return node - - visitPrintnl = visitPrint - - def visitName(self, node, walker): - """Prevents access to protected names as defined by checkName(). - - Also converts use of the name 'printed' to an expression. - """ - if node.name == 'printed': - # Replace name lookup with an expression. - self.funcinfo.printed_used = True - return _printed_expr - self.checkName(node, node.name) - self.used_names[node.name] = True - return node - - def visitCallFunc(self, node, walker): - """Checks calls with *-args and **-args. - - That's a way of spelling apply(), and needs to use our safe - _apply_ instead. - """ - walked = walker.defaultVisitNode(node) - if node.star_args is None and node.dstar_args is None: - # This is not an extended function call - return walked - # Otherwise transform foo(a, b, c, d=e, f=g, *args, **kws) into a call - # of _apply_(foo, a, b, c, d=e, f=g, *args, **kws). The interesting - # thing here is that _apply_() is defined with just *args and **kws, - # so it gets Python to collapse all the myriad ways to call functions - # into one manageable form. - # - # From there, _apply_() digs out the first argument of *args (it's the - # function to call), wraps args and kws in guarded accessors, then - # calls the function, returning the value. - # Transform foo(...) to _apply(foo, ...) - walked.args.insert(0, walked.node) - walked.node = _apply_name - return walked - - def visitAssName(self, node, walker): - """Checks a name assignment using checkName().""" - self.checkName(node, node.name) - return node - - def visitFor(self, node, walker): - # convert - # for x in expr: - # to - # for x in _getiter(expr): - # # Note that visitListCompFor is the same thing. - # - # Also for list comprehensions: - # [... for x in expr ...] - # to - # [... for x in _getiter(expr) ...] - node = walker.defaultVisitNode(node) - node.list = ast.CallFunc(_getiter_name, [node.list]) - return node - - visitListCompFor = visitFor - - def visitGenExprFor(self, node, walker): - # convert - # (... for x in expr ...) - # to - # (... for x in _getiter(expr) ...) - node = walker.defaultVisitNode(node) - node.iter = ast.CallFunc(_getiter_name, [node.iter]) - return node - - def visitGetattr(self, node, walker): - """Converts attribute access to a function call. - - 'foo.bar' becomes '_getattr(foo, "bar")'. - - Also prevents augmented assignment of attributes, which would - be difficult to support correctly. - """ - self.checkAttrName(node) - node = walker.defaultVisitNode(node) - if getattr(node, 'in_aug_assign', False): - # We're in an augmented assignment - # We might support this later... - self.error(node, 'Augmented assignment of ' - 'attributes is not allowed.') - return ast.CallFunc(_getattr_name, - [node.expr, ast.Const(node.attrname)]) - - def visitSubscript(self, node, walker): - """Checks all kinds of subscripts. - - 'foo[bar] += baz' is disallowed. - 'a = foo[bar, baz]' becomes 'a = _getitem(foo, (bar, baz))'. - 'a = foo[bar]' becomes 'a = _getitem(foo, bar)'. - 'a = foo[bar:baz]' becomes 'a = _getitem(foo, slice(bar, baz))'. - 'a = foo[:baz]' becomes 'a = _getitem(foo, slice(None, baz))'. - 'a = foo[bar:]' becomes 'a = _getitem(foo, slice(bar, None))'. - 'del foo[bar]' becomes 'del _write(foo)[bar]'. - 'foo[bar] = a' becomes '_write(foo)[bar] = a'. - - The _write function returns a security proxy. - """ - node = walker.defaultVisitNode(node) - if node.flags == OP_APPLY: - # Set 'subs' to the node that represents the subscript or slice. - if getattr(node, 'in_aug_assign', False): - # We're in an augmented assignment - # We might support this later... - self.error(node, 'Augmented assignment of ' - 'object items and slices is not allowed.') - if hasattr(node, 'subs'): - # Subscript. - subs = node.subs - if len(subs) > 1: - # example: ob[1,2] - subs = ast.Tuple(subs) - else: - # example: ob[1] - subs = subs[0] - else: - # Slice. - # example: obj[0:2] - lower = node.lower - if lower is None: - lower = _None_const - upper = node.upper - if upper is None: - upper = _None_const - subs = ast.Sliceobj([lower, upper]) - return ast.CallFunc(_getitem_name, [node.expr, subs]) - elif node.flags in (OP_DELETE, OP_ASSIGN): - # set or remove subscript or slice - node.expr = ast.CallFunc(_write_name, [node.expr]) - return node - - visitSlice = visitSubscript - - def visitAssAttr(self, node, walker): - """Checks and mutates attribute assignment. - - 'a.b = c' becomes '_write(a).b = c'. - The _write function returns a security proxy. - """ - self.checkAttrName(node) - node = walker.defaultVisitNode(node) - node.expr = ast.CallFunc(_write_name, [node.expr]) - return node - - def visitExec(self, node, walker): - self.error(node, 'Exec statements are not allowed.') - - def visitYield(self, node, walker): - self.error(node, 'Yield statements are not allowed.') - - def visitClass(self, node, walker): - """Checks the name of a class using checkName(). - - Should classes be allowed at all? They don't cause security - issues, but they aren't very useful either since untrusted - code can't assign instance attributes. - """ - self.checkName(node, node.name) - return walker.defaultVisitNode(node) - - def visitModule(self, node, walker): - """Adds prep code at module scope. - - Zope doesn't make use of this. The body of Python scripts is - always at function scope. - """ - node = walker.defaultVisitNode(node) - self.prepBody(node.node.nodes) - return node - - def visitAugAssign(self, node, walker): - """Makes a note that augmented assignment is in use. - - Note that although augmented assignment of attributes and - subscripts is disallowed, augmented assignment of names (such - as 'n += 1') is allowed. - - This could be a problem if untrusted code got access to a - mutable database object that supports augmented assignment. - """ - if node.node.__class__.__name__ == 'Name': - node = walker.defaultVisitNode(node) - newnode = ast.Assign( - [ast.AssName(node.node.name, OP_ASSIGN)], - ast.CallFunc( - _inplacevar_name, - [ast.Const(node.op), - ast.Name(node.node.name), - node.expr, - ] - ), - ) - newnode.lineno = node.lineno - return newnode - else: - node.node.in_aug_assign = True - return walker.defaultVisitNode(node) - - def visitImport(self, node, walker): - """Checks names imported using checkName().""" - for name, asname in node.names: - self.checkName(node, name) - if asname: - self.checkName(node, asname) - return node - - visitFrom = visitImport diff --git a/src/RestrictedPython/SelectCompiler.py b/src/RestrictedPython/SelectCompiler.py deleted file mode 100644 index b75a5ef..0000000 --- a/src/RestrictedPython/SelectCompiler.py +++ /dev/null @@ -1,39 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## -"""Compiler selector. -""" - -# flake8: NOQA: 401 -# isort: skip -# TODO: Drop whole file on getting rid of old implementation. - -from compiler import ast -from compiler.consts import OP_APPLY -from compiler.consts import OP_ASSIGN -from compiler.consts import OP_DELETE -from compiler.transformer import parse -from RCompile import compile_restricted -from RCompile import compile_restricted_eval -from RCompile import compile_restricted_exec -from RCompile import compile_restricted_function - -import compiler # Use the compiler from the standard library. -import warnings - - -warnings.warn( - "This Module (RestrictedPython.SelectCompiler) is deprecated" - "and will be gone soon.", - category=PendingDeprecationWarning, - stacklevel=1 -) diff --git a/src/RestrictedPython/__init__.py b/src/RestrictedPython/__init__.py index 04b55c5..a6d79a6 100644 --- a/src/RestrictedPython/__init__.py +++ b/src/RestrictedPython/__init__.py @@ -15,17 +15,9 @@ # flake8: NOQA: E401 # This is a file to define public API in the base namespace of the package. -# use: isor:skip to supress all isort related warnings / errors, +# use: isort:skip to supress all isort related warnings / errors, # as this file should be logically grouped imports - -# Old API --> Old Import Locations (Deprecated) -# from RestrictedPython.RCompile import compile_restricted -# from RestrictedPython.RCompile import compile_restricted_eval -# from RestrictedPython.RCompile import compile_restricted_exec -# from RestrictedPython.RCompile import compile_restricted_function - -# new API Style # compile_restricted methods: from RestrictedPython.compile import compile_restricted # isort:skip from RestrictedPython.compile import compile_restricted_eval # isort:skip diff --git a/src/RestrictedPython/tests/__init__.py b/src/RestrictedPython/tests/__init__.py deleted file mode 100644 index d2f3ead..0000000 --- a/src/RestrictedPython/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Python package.""" diff --git a/src/RestrictedPython/tests/class.py b/src/RestrictedPython/tests/class.py deleted file mode 100644 index cc86e8e..0000000 --- a/src/RestrictedPython/tests/class.py +++ /dev/null @@ -1,13 +0,0 @@ -class MyClass: - - def set(self, val): - self.state = val - - def get(self): - return self.state - -x = MyClass() -x.set(12) -x.set(x.get() + 1) -if x.get() != 13: - raise AssertionError("expected 13, got %d" % x.get()) diff --git a/src/RestrictedPython/tests/restricted_module.py b/src/RestrictedPython/tests/restricted_module.py deleted file mode 100644 index e6e7cc4..0000000 --- a/src/RestrictedPython/tests/restricted_module.py +++ /dev/null @@ -1,210 +0,0 @@ -import sys - - -def print0(): - print 'Hello, world!', - return printed - - -def print1(): - print 'Hello,', - print 'world!', - return printed - - -def printStuff(): - print 'a', 'b', 'c', - return printed - - -def printToNone(): - x = None - print >>x, 'Hello, world!', - return printed - - -def printLines(): - # This failed before Zope 2.4.0a2 - r = range(3) - for n in r: - for m in r: - print m + n * len(r), - print - return printed - - -def try_map(): - inc = lambda i: i + 1 - x = [1, 2, 3] - print map(inc, x), - return printed - - -def try_apply(): - def f(x, y, z): - return x + y + z - print f(*(300, 20), **{'z': 1}), - return printed - - -def try_inplace(): - x = 1 - x += 3 - - -def primes(): - # Somewhat obfuscated code on purpose - print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, - map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,20))), - return printed - - -def allowed_read(ob): - print ob.allowed - print ob.s - print ob[0] - print ob[2] - print ob[3:-1] - print len(ob) - return printed - - -def allowed_default_args(ob): - def f(a=ob.allowed, s=ob.s): - return a, s - - -def allowed_simple(): - q = {'x': 'a'} - q['y'] = 'b' - q.update({'z': 'c'}) - r = ['a'] - r.append('b') - r[2:2] = ['c'] - s = 'a' - s = s[:100] + 'b' - s += 'c' - if sys.version_info >= (2, 3): - t = ['l', 'm', 'n', 'o', 'p', 'q'] - t[1:5:2] = ['n', 'p'] - _ = q - - return q['x'] + q['y'] + q['z'] + r[0] + r[1] + r[2] + s - - -def allowed_write(ob): - ob.writeable = 1 - # ob.writeable += 1 - [1 for ob.writeable in 1, 2] - ob['safe'] = 2 - # ob['safe'] += 2 - [1 for ob['safe'] in 1, 2] - - -def denied_print(ob): - print >> ob, 'Hello, world!', - - -def denied_getattr(ob): - # ob.disallowed += 1 - ob.disallowed = 1 - return ob.disallowed - - -def denied_default_args(ob): - def f(d=ob.disallowed): - return d - - -def denied_setattr(ob): - ob.allowed = -1 - - -def denied_setattr2(ob): - # ob.allowed += -1 - ob.allowed = -1 - - -def denied_setattr3(ob): - [1 for ob.allowed in 1, 2] - - -def denied_getitem(ob): - ob[1] - - -def denied_getitem2(ob): - # ob[1] += 1 - ob[1] - - -def denied_setitem(ob): - ob['x'] = 2 - - -def denied_setitem2(ob): - # ob[0] += 2 - ob['x'] = 2 - - -def denied_setitem3(ob): - [1 for ob['x'] in 1, 2] - - -def denied_setslice(ob): - ob[0:1] = 'a' - - -def denied_setslice2(ob): - # ob[0:1] += 'a' - ob[0:1] = 'a' - - -def denied_setslice3(ob): - [1 for ob[0:1] in 1, 2] - - -##def strange_attribute(): -## # If a guard has attributes with names that don't start with an -## # underscore, those attributes appear to be an attribute of -## # anything. -## return [].attribute_of_anything - -def order_of_operations(): - return 3 * 4 * -2 + 2 * 12 - - -def rot13(ss): - mapping = {} - orda = ord('a') - ordA = ord('A') - for n in range(13): - c1 = chr(orda + n) - c2 = chr(orda + n + 13) - c3 = chr(ordA + n) - c4 = chr(ordA + n + 13) - mapping[c1] = c2 - mapping[c2] = c1 - mapping[c3] = c4 - mapping[c4] = c3 - del c1, c2, c3, c4, orda, ordA - res = '' - for c in ss: - res = res + mapping.get(c, c) - return res - - -def nested_scopes_1(): - # Fails if 'a' is consumed by the first function. - a = 1 - - def f1(): - return a - - def f2(): - return a - return f1() + f2() - - -class Classic: - pass diff --git a/src/RestrictedPython/tests/testCompile.py b/src/RestrictedPython/tests/testCompile.py deleted file mode 100644 index 859b20d..0000000 --- a/src/RestrictedPython/tests/testCompile.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## - -from RestrictedPython.RCompile import niceParse - -import compiler.ast -import unittest - - -class CompileTests(unittest.TestCase): - - def testUnicodeSource(self): - # We support unicode sourcecode. - source = u"u'Ä väry nice säntänce with umlauts.'" - - parsed = niceParse(source, "test.py", "exec") - self.failUnless(isinstance(parsed, compiler.ast.Module)) - parsed = niceParse(source, "test.py", "single") - self.failUnless(isinstance(parsed, compiler.ast.Module)) - parsed = niceParse(source, "test.py", "eval") - self.failUnless(isinstance(parsed, compiler.ast.Expression)) - - -def test_suite(): - return unittest.makeSuite(CompileTests) diff --git a/src/RestrictedPython/tests/testREADME.py b/src/RestrictedPython/tests/testREADME.py deleted file mode 100644 index 6800c4d..0000000 --- a/src/RestrictedPython/tests/testREADME.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# -# Copyright (c) 2007 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Run tests in README.txt -""" -from doctest import DocFileSuite - -import unittest - - -__docformat__ = "reStructuredText" - - -def test_suite(): - return unittest.TestSuite([ - DocFileSuite('README.rst', package='RestrictedPython'), ]) diff --git a/src/RestrictedPython/tests/testRestrictions.py b/src/RestrictedPython/tests/testRestrictions.py deleted file mode 100644 index 7527ffc..0000000 --- a/src/RestrictedPython/tests/testRestrictions.py +++ /dev/null @@ -1,484 +0,0 @@ -# Note that nothing should be imported from AccessControl, and in particular -# nothing from ZopeGuards.py. Transformed code may need several wrappers -# in order to run at all, and most of the production wrappers are defined -# in ZopeGuards. But RestrictedPython isn't supposed to depend on -# AccessControl, so we need to define throwaway wrapper implementations -# here instead. - -from RestrictedPython import PrintCollector -from RestrictedPython.RCompile import compile_restricted -from RestrictedPython.RCompile import RFunction -from RestrictedPython.RCompile import RModule -from RestrictedPython.tests import restricted_module -from RestrictedPython.tests import verify - -import os -import re -import sys -import unittest - - -try: - __file__ -except NameError: - __file__ = os.path.abspath(sys.argv[1]) -_FILEPATH = os.path.abspath(__file__) -_HERE = os.path.dirname(_FILEPATH) - - -def _getindent(line): - """Returns the indentation level of the given line.""" - indent = 0 - for c in line: - if c == ' ': - indent = indent + 1 - elif c == '\t': - indent = indent + 8 - else: - break - return indent - - -def find_source(fn, func): - """Given a func_code object, this function tries to find and return - the python source code of the function. - Originally written by - Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)""" - f = open(fn, "r") - for i in range(func.co_firstlineno): - line = f.readline() - ind = _getindent(line) - msg = "" - while line: - msg = msg + line - line = f.readline() - # the following should be <= ind, but then we get - # confused by multiline docstrings. Using == works most of - # the time... but not always! - if _getindent(line) == ind: - break - f.close() - return fn, msg - - -def get_source(func): - """Less silly interface to find_source""" - file = func.func_globals['__file__'] - if file.endswith('.pyc'): - file = file[:-1] - source = find_source(file, func.func_code)[1] - assert source.strip(), "Source should not be empty!" - return source - - -def create_rmodule(): - global rmodule - fn = os.path.join(_HERE, 'restricted_module.py') - f = open(fn, 'r') - source = f.read() - f.close() - # Sanity check - compile(source, fn, 'exec') - # Now compile it for real - code = compile_restricted(source, fn, 'exec') - rmodule = {'__builtins__': {'__import__': __import__, - 'None': None, - '__name__': 'restricted_module' - } - } - - builtins = getattr(__builtins__, '__dict__', __builtins__) - for name in ('map', 'reduce', 'int', 'pow', 'range', 'filter', - 'len', 'chr', 'ord', - ): - rmodule[name] = builtins[name] - exec code in rmodule - - -class AccessDenied (Exception): - pass - -DisallowedObject = [] - - -class RestrictedObject: - disallowed = DisallowedObject - allowed = 1 - _ = 2 - __ = 3 - _some_attr = 4 - __some_other_attr__ = 5 - s = 'Another day, another test...' - __writeable_attrs__ = ('writeable',) - - def __getitem__(self, idx): - if idx == 'protected': - raise AccessDenied - elif idx == 0 or idx == 'safe': - return 1 - elif idx == 1: - return DisallowedObject - else: - return self.s[idx] - - def __getslice__(self, lo, hi): - return self.s[lo:hi] - - def __len__(self): - return len(self.s) - - def __setitem__(self, idx, v): - if idx == 'safe': - self.safe = v - else: - raise AccessDenied - - def __setslice__(self, lo, hi, value): - raise AccessDenied - - write = DisallowedObject - - -def guarded_getattr(ob, name): - v = getattr(ob, name) - if v is DisallowedObject: - raise AccessDenied - return v - -SliceType = type(slice(0)) - - -def guarded_getitem(ob, index): - if type(index) is SliceType and index.step is None: - start = index.start - stop = index.stop - if start is None: - start = 0 - if stop is None: - v = ob[start:] - else: - v = ob[start:stop] - else: - v = ob[index] - if v is DisallowedObject: - raise AccessDenied - return v - - -def minimal_import(name, _globals, _locals, names): - if name != "__future__": - raise ValueError("Only future imports are allowed") - import __future__ - return __future__ - - -class TestGuard: - '''A guard class''' - def __init__(self, _ob, write=None): - self.__dict__['_ob'] = _ob - - # Write guard methods - - def __setattr__(self, name, value): - _ob = self.__dict__['_ob'] - writeable = getattr(_ob, '__writeable_attrs__', ()) - if name not in writeable: - raise AccessDenied - if name[:5] == 'func_': - raise AccessDenied - setattr(_ob, name, value) - - def __setitem__(self, index, value): - _ob = self.__dict__['_ob'] - _ob[index] = value - - def __setslice__(self, lo, hi, value): - _ob = self.__dict__['_ob'] - _ob[lo:hi] = value - -# A wrapper for _apply_. -apply_wrapper_called = [] - - -def apply_wrapper(func, *args, **kws): - apply_wrapper_called.append('yes') - return func(*args, **kws) - -inplacevar_wrapper_called = {} - - -def inplacevar_wrapper(op, x, y): - inplacevar_wrapper_called[op] = x, y - # This is really lame. But it's just a test. :) - globs = {'x': x, 'y': y} - exec('x' + op + 'y', globs) - return globs['x'] - - -class RestrictionTests(unittest.TestCase): - def execFunc(self, name, *args, **kw): - func = rmodule[name] - verify.verify(func.func_code) - func.func_globals.update({'_getattr_': guarded_getattr, - '_getitem_': guarded_getitem, - '_write_': TestGuard, - '_print_': PrintCollector, - # I don't want to write something as involved as ZopeGuard's - # SafeIter just for these tests. Using the builtin list() function - # worked OK for everything the tests did at the time this was added, - # but may fail in the future. If Python 2.1 is no longer an - # interesting platform then, using 2.2's builtin iter() here should - # work for everything. - '_getiter_': list, - '_apply_': apply_wrapper, - '_inplacevar_': inplacevar_wrapper, - }) - return func(*args, **kw) - - def test_Print(self): - for i in range(2): - res = self.execFunc('print%s' % i) - self.assertEqual(res, 'Hello, world!') - - def test_PrintToNone(self): - try: - res = self.execFunc('printToNone') - except AttributeError: - # Passed. "None" has no "write" attribute. - pass - else: - self.fail(0, res) - - def test_PrintStuff(self): - res = self.execFunc('printStuff') - self.assertEqual(res, 'a b c') - - def test_PrintLines(self): - res = self.execFunc('printLines') - self.assertEqual(res, '0 1 2\n3 4 5\n6 7 8\n') - - def test_Primes(self): - res = self.execFunc('primes') - self.assertEqual(res, '[2, 3, 5, 7, 11, 13, 17, 19]') - - def test_AllowedSimple(self): - res = self.execFunc('allowed_simple') - self.assertEqual(res, 'abcabcabc') - - def test_AllowedRead(self): - self.execFunc('allowed_read', RestrictedObject()) - - def test_AllowedWrite(self): - self.execFunc('allowed_write', RestrictedObject()) - - def test_AllowedArgs(self): - self.execFunc('allowed_default_args', RestrictedObject()) - - def test_TryMap(self): - res = self.execFunc('try_map') - self.assertEqual(res, "[2, 3, 4]") - - def test_Apply(self): - del apply_wrapper_called[:] - res = self.execFunc('try_apply') - self.assertEqual(apply_wrapper_called, ["yes"]) - self.assertEqual(res, "321") - - def test_Inplace(self): - inplacevar_wrapper_called.clear() - res = self.execFunc('try_inplace') - self.assertEqual(inplacevar_wrapper_called['+='], (1, 3)) - - def test_Denied(self): - for k in rmodule.keys(): - if k[:6] == 'denied': - try: - self.execFunc(k, RestrictedObject()) - except AccessDenied: - # Passed the test - pass - else: - self.fail('%s() did not trip security' % k) - - def test_OrderOfOperations(self): - res = self.execFunc('order_of_operations') - self.assertEqual(res, 0) - - def test_Rot13(self): - res = self.execFunc('rot13', 'Zope is k00l') - self.assertEqual(res, 'Mbcr vf x00y') - - def test_NestedScopes1(self): - res = self.execFunc('nested_scopes_1') - self.assertEqual(res, 2) - - def test_StackSize(self): - for k, rfunc in rmodule.items(): - if not k.startswith('_') and hasattr(rfunc, 'func_code'): - rss = rfunc.func_code.co_stacksize - ss = getattr(restricted_module, k).func_code.co_stacksize - self.failUnless( - rss >= ss, 'The stack size estimate for %s() ' - 'should have been at least %d, but was only %d' - % (k, ss, rss)) - - def _compile_file(self, name): - path = os.path.join(_HERE, name) - f = open(path, "r") - source = f.read() - f.close() - - co = compile_restricted(source, path, "exec") - verify.verify(co) - return co - - def test_UnpackSequence(self): - co = self._compile_file("unpack.py") - calls = [] - - def getiter(seq): - calls.append(seq) - return list(seq) - globals = {"_getiter_": getiter, '_inplacevar_': inplacevar_wrapper} - exec(co, globals, {}) - # The comparison here depends on the exact code that is - # contained in unpack.py. - # The test doing implicit unpacking in an "except:" clause is - # a pain, because there are two levels of unpacking, and the top - # level is unpacking the specific TypeError instance constructed - # by the test. We have to worm around that one. - ineffable = "a TypeError instance" - expected = [[1, 2], - (1, 2), - "12", - [1], - [1, [2, 3], 4], - [2, 3], - (1, (2, 3), 4), - (2, 3), - [1, 2, 3], - 2, - ('a', 'b'), - ((1, 2), (3, 4)), (1, 2), - ((1, 2), (3, 4)), (3, 4), - ineffable, [42, 666], - [[0, 1], [2, 3], [4, 5]], [0, 1], [2, 3], [4, 5], - ([[[1, 2]]], [[[3, 4]]]), [[[1, 2]]], [[1, 2]], [1, 2], - [[[3, 4]]], [[3, 4]], [3, 4], - ] - i = expected.index(ineffable) - self.assert_(isinstance(calls[i], TypeError)) - expected[i] = calls[i] - self.assertEqual(calls, expected) - - def test_UnpackSequenceExpression(self): - co = compile_restricted("[x for x, y in [(1, 2)]]", "", "eval") - verify.verify(co) - calls = [] - - def getiter(s): - calls.append(s) - return list(s) - globals = {"_getiter_": getiter} - exec(co, globals, {}) - self.assertEqual(calls, [[(1, 2)], (1, 2)]) - - def test_UnpackSequenceSingle(self): - co = compile_restricted("x, y = 1, 2", "", "single") - verify.verify(co) - calls = [] - - def getiter(s): - calls.append(s) - return list(s) - globals = {"_getiter_": getiter} - exec(co, globals, {}) - self.assertEqual(calls, [(1, 2)]) - - def test_Class(self): - getattr_calls = [] - setattr_calls = [] - - def test_getattr(obj, attr): - getattr_calls.append(attr) - return getattr(obj, attr) - - def test_setattr(obj): - setattr_calls.append(obj.__class__.__name__) - return obj - - co = self._compile_file("class.py") - globals = {"_getattr_": test_getattr, - "_write_": test_setattr, - } - exec(co, globals, {}) - # Note that the getattr calls don't correspond to the method call - # order, because the x.set method is fetched before its arguments - # are evaluated. - self.assertEqual(getattr_calls, - ["set", "set", "get", "state", "get", "state"]) - self.assertEqual(setattr_calls, ["MyClass", "MyClass"]) - - def test_Empty(self): - rf = RFunction("", "", "issue945", "empty.py", {}) - rf.parse() - rf2 = RFunction("", "# still empty\n\n# by", "issue945", "empty.py", {}) - rf2.parse() - - def test_SyntaxError(self): - err = ("def f(x, y):\n" - " if x, y < 2 + 1:\n" - " return x + y\n" - " else:\n" - " return x - y\n") - self.assertRaises(SyntaxError, - compile_restricted, err, "", "exec") - - # these two tests test_ that source code with Windows line - # endings still works. - - def test_LineEndingsRFunction(self): - from RestrictedPython.RCompile import RFunction - gen = RFunction( - p='', - body='# testing\r\nprint "testing"\r\nreturn printed\n', - name='test', - filename='', - globals=(), - ) - gen.mode = 'exec' - # if the source has any line ending other than \n by the time - # parse() is called, then you'll get a syntax error. - gen.parse() - - def test_LineEndingsRestrictedCompileMode(self): - from RestrictedPython.RCompile import RestrictedCompileMode - gen = RestrictedCompileMode( - '# testing\r\nprint "testing"\r\nreturn printed\n', - '' - ) - gen.mode = 'exec' - # if the source has any line ending other than \n by the time - # parse() is called, then you'll get a syntax error. - gen.parse() - - def test_Collector2295(self): - from RestrictedPython.RCompile import RestrictedCompileMode - gen = RestrictedCompileMode( - 'if False:\n pass\n# Me Grok, Say Hi', - '' - ) - gen.mode = 'exec' - # if the source has any line ending other than \n by the time - # parse() is called, then you'll get a syntax error. - gen.parse() - - -create_rmodule() - - -def test_suite(): - return unittest.makeSuite(RestrictionTests, 'test') - -if __name__ == '__main__': - unittest.main(defaultTest="test_suite") diff --git a/src/RestrictedPython/tests/unpack.py b/src/RestrictedPython/tests/unpack.py deleted file mode 100644 index dd57fa3..0000000 --- a/src/RestrictedPython/tests/unpack.py +++ /dev/null @@ -1,91 +0,0 @@ -# A series of short tests for unpacking sequences. - - -def u1(L): - x, y = L - assert x == 1 - assert y == 2 - -u1([1, 2]) -u1((1, 2)) - - -def u1a(L): - x, y = L - assert x == '1' - assert y == '2' - -u1a("12") - -try: - u1([1]) -except ValueError: - pass -else: - raise AssertionError("expected 'unpack list of wrong size'") - - -def u2(L): - x, (a, b), y = L - assert x == 1 - assert a == 2 - assert b == 3 - assert y == 4 - -u2([1, [2, 3], 4]) -u2((1, (2, 3), 4)) - -try: - u2([1, 2, 3]) -except TypeError: - pass -else: - raise AssertionError("expected 'iteration over non-sequence'") - - -def u3((x, y)): - assert x == 'a' - assert y == 'b' - return x, y - -u3(('a', 'b')) - - -def u4(x): - (a, b), c = d, (e, f) = x - assert a == 1 and b == 2 and c == (3, 4) - assert d == (1, 2) and e == 3 and f == 4 - - -u4(((1, 2), (3, 4))) - - -def u5(x): - try: - raise TypeError(x) - # This one is tricky to test, because the first level of unpacking - # has a TypeError instance. That's a headache for the test driver. - except TypeError, [(a, b)]: - assert a == 42 - assert b == 666 - -u5([42, 666]) - - -def u6(x): - expected = 0 - for i, j in x: - assert i == expected - expected += 1 - assert j == expected - expected += 1 - -u6([[0, 1], [2, 3], [4, 5]]) - - -def u7(x): - stuff = [i + j for toplevel, in x for i, j in toplevel] - assert stuff == [3, 7] - - -u7(([[[1, 2]]], [[[3, 4]]])) diff --git a/src/RestrictedPython/tests/verify.py b/src/RestrictedPython/tests/verify.py deleted file mode 100644 index 156ec86..0000000 --- a/src/RestrictedPython/tests/verify.py +++ /dev/null @@ -1,212 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## -"""Verify simple properties of bytecode. - -Some of the transformations performed by the RestrictionMutator are -tricky. This module checks the generated bytecode as a way to verify -the correctness of the transformations. Violations of some -restrictions are obvious from inspection of the bytecode. For -example, the bytecode should never contain a LOAD_ATTR call, because -all attribute access is performed via the _getattr_() checker -function. -""" - -import dis -import types -import warnings - - -def verify(code): - """Verify all code objects reachable from code. - - In particular, traverse into contained code objects in the - co_consts table. - """ - warnings.warn( - "RestrictedPython.test.verify is deprecated and will be gone soon." - "verify() tests on byte code level, which did not make sense" - "with new implementation which is Python Implementation independend.", - category=PendingDeprecationWarning, - stacklevel=1 - ) - verifycode(code) - for ob in code.co_consts: - if isinstance(ob, types.CodeType): - verify(ob) - - -def verifycode(code): - try: - _verifycode(code) - except: - dis.dis(code) - raise - - -def _verifycode(code): - line = code.co_firstlineno - # keep a window of the last three opcodes, with the most recent first - window = (None, None, None) - with_context = (None, None) - - for op in disassemble(code): - if op.line is not None: - line = op.line - if op.opname.endswith("LOAD_ATTR"): - # All the user code that generates LOAD_ATTR should be - # rewritten, but the code generated for a list comp - # includes a LOAD_ATTR to extract the append method. - # Another exception is the new-in-Python 2.6 'context - # managers', which do a LOAD_ATTR for __exit__ and - # __enter__. - if op.arg == "__exit__": - with_context = (op, with_context[1]) - elif op.arg == "__enter__": - with_context = (with_context[0], op) - elif not ((op.arg == "__enter__" and - window[0].opname == "ROT_TWO" and - window[1].opname == "DUP_TOP") or - (op.arg == "append" and - window[0].opname == "DUP_TOP" and - window[1].opname == "BUILD_LIST")): - raise ValueError("direct attribute access %s: %s, %s:%d" - % (op.opname, op.arg, code.co_filename, line)) - if op.opname in ("WITH_CLEANUP"): - # Here we check if the LOAD_ATTR for __exit__ and - # __enter__ were part of a 'with' statement by checking - # for the 'WITH_CLEANUP' bytecode. If one is seen, we - # clear the with_context variable and let it go. The - # access was safe. - with_context = (None, None) - if op.opname in ("STORE_ATTR", "DEL_ATTR"): - if not (window[0].opname == "CALL_FUNCTION" and - window[2].opname == "LOAD_GLOBAL" and - window[2].arg == "_write_"): - # check that arg is appropriately wrapped - for i, op in enumerate(window): - print(i, op.opname, op.arg) - raise ValueError("unguard attribute set/del at %s:%d" - % (code.co_filename, line)) - if op.opname.startswith("UNPACK"): - # An UNPACK opcode extracts items from iterables, and that's - # unsafe. The restricted compiler doesn't remove UNPACK opcodes, - # but rather *inserts* a call to _getiter_() before each, and - # that's the pattern we need to see. - if not (window[0].opname == "CALL_FUNCTION" and - window[1].opname == "ROT_TWO" and - window[2].opname == "LOAD_GLOBAL" and - window[2].arg == "_getiter_"): - raise ValueError("unguarded unpack sequence at %s:%d" % - (code.co_filename, line)) - - # should check CALL_FUNCTION_{VAR,KW,VAR_KW} but that would - # require a potentially unlimited history. need to refactor - # the "window" before I can do that. - - if op.opname == "LOAD_SUBSCR": - raise ValueError("unguarded index of sequence at %s:%d" % - (code.co_filename, line)) - - window = (op,) + window[:2] - - if not with_context == (None, None): - # An access to __enter__ and __exit__ was performed but not as - # part of a 'with' statement. This is not allowed. - for op in with_context: - if op is not None: - if op.line is not None: - line = op.line - raise ValueError("direct attribute access %s: %s, %s:%d" - % (op.opname, op.arg, code.co_filename, line)) - - -class Op(object): - __slots__ = ( - "opname", # string, name of the opcode - "argcode", # int, the number of the argument - "arg", # any, the object, name, or value of argcode - "line", # int, line number or None - "target", # boolean, is this op the target of a jump - "pos", # int, offset in the bytecode - ) - - def __init__(self, opcode, pos): - self.opname = dis.opname[opcode] - self.arg = None - self.line = None - self.target = False - self.pos = pos - - -def disassemble(co, lasti=-1): - code = co.co_code - labels = dis.findlabels(code) - linestarts = dict(findlinestarts(co)) - n = len(code) - i = 0 - extended_arg = 0 - free = co.co_cellvars + co.co_freevars - while i < n: - op = ord(code[i]) - o = Op(op, i) - i += 1 - if i in linestarts and i > 0: - o.line = linestarts[i] - if i in labels: - o.target = True - if op > dis.HAVE_ARGUMENT: - arg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg - extended_arg = 0 - i += 2 - if op == dis.EXTENDED_ARG: - extended_arg = arg << 16 - o.argcode = arg - if op in dis.hasconst: - o.arg = co.co_consts[arg] - elif op in dis.hasname: - o.arg = co.co_names[arg] - elif op in dis.hasjrel: - o.arg = i + arg - elif op in dis.haslocal: - o.arg = co.co_varnames[arg] - elif op in dis.hascompare: - o.arg = dis.cmp_op[arg] - elif op in dis.hasfree: - o.arg = free[arg] - yield o - - -# findlinestarts is copied from Python 2.4's dis module. The code -# didn't exist in 2.3, but it would be painful to code disassemble() -# without it. -def findlinestarts(code): - """Find the offsets in a byte code which are start of lines in the source. - - Generate pairs (offset, lineno) as described in Python/compile.c. - - """ - byte_increments = [ord(c) for c in code.co_lnotab[0::2]] - line_increments = [ord(c) for c in code.co_lnotab[1::2]] - - lastlineno = None - lineno = code.co_firstlineno - addr = 0 - for byte_incr, line_incr in zip(byte_increments, line_increments): - if byte_incr: - if lineno != lastlineno: - yield (addr, lineno) - lastlineno = lineno - addr += byte_incr - lineno += line_incr - if lineno != lastlineno: - yield (addr, lineno) diff --git a/tests/__init__.py b/tests/__init__.py index e93f012..bb3a495 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,3 @@ -from RestrictedPython._compat import IS_PY2 - import RestrictedPython @@ -72,16 +70,3 @@ def _function(source, glb=None): c_single = ('c_single', [RestrictedPython.compile.compile_restricted_single]) e_single = ('e_single', [_single(RestrictedPython.compile.compile_restricted_single)]) # NOQA: E501 - - -if IS_PY2: - from RestrictedPython import RCompile - c_exec[1].append(RCompile.compile_restricted_exec) - c_eval[1].append(RCompile.compile_restricted_eval) - c_single[1].append(RCompile.compile_restricted_single) - c_function[1].append(RCompile.compile_restricted_function) - - e_exec[1].append(_exec(RCompile.compile_restricted_exec)) - e_eval[1].append(_eval(RCompile.compile_restricted_eval)) - e_single[1].append(_single(RCompile.compile_restricted_single)) - e_function[1].append(_function(RCompile.compile_restricted_function)) diff --git a/tests/test_print_stmt.py b/tests/test_print_stmt.py index 1165600..2c7362d 100644 --- a/tests/test_print_stmt.py +++ b/tests/test_print_stmt.py @@ -3,7 +3,6 @@ from tests import c_exec import pytest -import RestrictedPython pytestmark = pytest.mark.skipif( @@ -156,13 +155,8 @@ def test_print_stmt__with_printed_no_print(c_exec): assert code is not None assert errors == () - - if c_exec is RestrictedPython.compile.compile_restricted_exec: - assert warnings == [ - "Line 2: Doesn't print, but reads 'printed' variable."] - - if c_exec is RestrictedPython.RCompile.compile_restricted_exec: - assert warnings == ["Doesn't print, but reads 'printed' variable."] + assert warnings == [ + "Line 2: Doesn't print, but reads 'printed' variable."] WARN_PRINTED_NO_PRINT_NESTED = """ @@ -179,15 +173,10 @@ def test_print_stmt__with_printed_no_print_nested(c_exec): assert code is not None assert errors == () - - if c_exec is RestrictedPython.compile.compile_restricted_exec: - assert warnings == [ - "Line 2: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 - "Line 3: Doesn't print, but reads 'printed' variable." - ] - - if c_exec is RestrictedPython.RCompile.compile_restricted_exec: - assert warnings == ["Doesn't print, but reads 'printed' variable."] + assert warnings == [ + "Line 2: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 + "Line 3: Doesn't print, but reads 'printed' variable." + ] WARN_PRINT_NO_PRINTED = """ @@ -202,15 +191,10 @@ def test_print_stmt__with_print_no_printed(c_exec): assert code is not None assert errors == () - - if c_exec is RestrictedPython.compile.compile_restricted_exec: - assert warnings == [ - "Line 3: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 - "Line 2: Prints, but never reads 'printed' variable." - ] - - if c_exec is RestrictedPython.RCompile.compile_restricted_exec: - assert warnings == ["Prints, but never reads 'printed' variable."] + assert warnings == [ + "Line 3: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 + "Line 2: Prints, but never reads 'printed' variable." + ] WARN_PRINT_NO_PRINTED_NESTED = """ @@ -227,16 +211,11 @@ def test_print_stmt__with_print_no_printed_nested(c_exec): assert code is not None assert errors == () - - if c_exec is RestrictedPython.compile.compile_restricted_exec: - assert warnings == [ - "Line 2: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 - "Line 4: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 - "Line 3: Prints, but never reads 'printed' variable.", - ] - - if c_exec is RestrictedPython.RCompile.compile_restricted_exec: - assert warnings == ["Prints, but never reads 'printed' variable."] + assert warnings == [ + "Line 2: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 + "Line 4: Print statement is deprecated and not avaliable anymore in Python 3.", # NOQA: E501 + "Line 3: Prints, but never reads 'printed' variable.", + ] # python2 generates a new frame/scope for: diff --git a/tox.ini b/tox.ini index 92f1e6a..53204ec 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ envlist = py27, py27-datetime, - py27-rp3, py34, py35, py36, @@ -44,15 +43,6 @@ deps = {[testenv]deps} DateTime -[testenv:py27-rp3] -basepython = python2.7 -commands = - coverage run {envbindir}/zope-testrunner --path=src/RestrictedPython --all -v {posargs} -deps = - .[test] - zope.testrunner - coverage - [testenv:coverage] basepython = python2.7 deps = coverage From 1ee4bcecc42760f338aedd702595a1cac4d3f828 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Fri, 15 Sep 2017 11:08:52 +0200 Subject: [PATCH 2/2] Remove code which was necessary to distinguish between the implementations. --- tests/__init__.py | 7 ++-- tests/test_compile.py | 51 +++++++++------------------ tests/test_print_function.py | 1 - tests/transformer/test_classdef.py | 5 +-- tests/transformer/test_eval_exec.py | 7 +--- tests/transformer/test_functiondef.py | 19 ++++------ tests/transformer/test_iterator.py | 11 +++--- tests/transformer/test_lambda.py | 34 +++++++----------- 8 files changed, 43 insertions(+), 92 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index bb3a495..a8909aa 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,8 +17,6 @@ def _exec(source, glb=None): glb = {} exec(code, glb) return glb - # The next line can be dropped after the old implementation was dropped. - _exec.compile_func = compile_func return _exec @@ -54,8 +52,9 @@ def _function(source, glb=None): return _function -# Define the arguments for @pytest.mark.parametrize to be able to test both the -# old and the new implementation to be equal: +# Define the arguments for @pytest.mark.parametrize. This was used to be able +# to test both the old and the new implementation are equal. It can be +# refactored into fixtures. # Compile in `exec` mode. c_exec = ('c_exec', [RestrictedPython.compile.compile_restricted_exec]) # Compile and execute in `exec` mode. diff --git a/tests/test_compile.py b/tests/test_compile.py index 46914ec..d27bc13 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -8,7 +8,6 @@ from tests import e_eval import pytest -import RestrictedPython.compile import types @@ -47,15 +46,13 @@ def test_compile__compile_restricted_exec__1(c_exec): @pytest.mark.parametrize(*c_exec) def test_compile__compile_restricted_exec__2(c_exec): """It compiles without restrictions if there is no policy.""" - if c_exec is RestrictedPython.compile.compile_restricted_exec: - # The old version does not support a custom policy - result = c_exec('_a = 42', policy=None) - assert result.errors == () - assert result.warnings == [] - assert result.used_names == {} - glob = {} - exec(result.code, glob) - assert glob['_a'] == 42 + result = c_exec('_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(*c_exec) @@ -68,11 +65,7 @@ def test_compile__compile_restricted_exec__3(c_exec): 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 c_exec 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.errors == errors assert result.warnings == [] assert result.used_names == {} assert result.code is None @@ -85,12 +78,8 @@ def test_compile__compile_restricted_exec__4(c_exec): assert result.code is None assert result.warnings == [] assert result.used_names == {} - if c_exec 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)',) + assert result.errors == ( + 'Line 1: SyntaxError: invalid syntax in on statement: asdf|',) @pytest.mark.parametrize(*c_exec) @@ -139,11 +128,8 @@ def test_compile__compile_restricted_eval__1(c_eval): Function definitions are not allowed in Expressions. """ result = c_eval(FUNCTION_DEF) - if c_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)',) + assert result.errors == ( + 'Line 1: SyntaxError: invalid syntax in on statement: def a():',) @pytest.mark.parametrize(*e_eval) @@ -164,15 +150,10 @@ def test_compile__compile_restricted_eval__used_names(c_eval): def test_compile__compile_restricted_csingle(c_single): """It compiles code as an Interactive.""" result = c_single('4 * 6') - if c_single is RestrictedPython.compile.compile_restricted_single: - # New implementation disallows single mode - assert result.code is None - assert result.errors == ( - 'Line None: Interactive statements are not allowed.', - ) - else: # RestrictedPython.RCompile.compile_restricted_single - assert result.code is not None - assert result.errors == () + assert result.code is None + assert result.errors == ( + 'Line None: Interactive statements are not allowed.', + ) PRINT_EXAMPLE = """ diff --git a/tests/test_print_function.py b/tests/test_print_function.py index 047e0fe..d29fa18 100644 --- a/tests/test_print_function.py +++ b/tests/test_print_function.py @@ -3,7 +3,6 @@ import RestrictedPython -# The old 'RCompile' has no clue about the print function. compiler = RestrictedPython.compile.compile_restricted_exec diff --git a/tests/transformer/test_classdef.py b/tests/transformer/test_classdef.py index 814f888..1a3e0a4 100644 --- a/tests/transformer/test_classdef.py +++ b/tests/transformer/test_classdef.py @@ -4,7 +4,6 @@ from tests import e_exec import pytest -import RestrictedPython GOOD_CLASS = ''' @@ -108,6 +107,4 @@ def test_RestrictingNodeTransformer__visit_ClassDef__5(e_exec): comb = restricted_globals['comb'] assert comb.class_att == 2342 assert comb.base_att == 42 - if e_exec.compile_func is RestrictedPython.compile.compile_restricted_exec: - # Class decorators are only supported by the new implementation. - assert comb.wrap_att == 23 + assert comb.wrap_att == 23 diff --git a/tests/transformer/test_eval_exec.py b/tests/transformer/test_eval_exec.py index 330570d..aaf8e93 100644 --- a/tests/transformer/test_eval_exec.py +++ b/tests/transformer/test_eval_exec.py @@ -3,7 +3,6 @@ from tests import c_exec import pytest -import RestrictedPython EXEC_STATEMENT = """\ @@ -46,8 +45,4 @@ def no_eval(): def test_RestrictingNodeTransformer__visit_Eval__1(c_exec): """It is an error if the code call the `eval` function.""" result = c_exec(EVAL_FUNCTION) - if c_exec is RestrictedPython.compile.compile_restricted_exec: - assert result.errors == ("Line 2: Eval calls are not allowed.",) - else: - # `eval()` is allowed in the old implementation. :-( - assert result.errors == () + assert result.errors == ("Line 2: Eval calls are not allowed.",) diff --git a/tests/transformer/test_functiondef.py b/tests/transformer/test_functiondef.py index 50e7f95..7f5b0c2 100644 --- a/tests/transformer/test_functiondef.py +++ b/tests/transformer/test_functiondef.py @@ -5,7 +5,6 @@ from tests import e_exec import pytest -import RestrictedPython functiondef_err_msg = 'Line 1: "_bad" is an invalid variable ' \ @@ -71,13 +70,11 @@ def test_RestrictingNodeTransformer__visit_FunctionDef__5( def test_RestrictingNodeTransformer__visit_FunctionDef__6( c_exec): """It prevents function arguments starting with `_` in tuples.""" - # The old `compile` breaks with tuples in function arguments: - if c_exec is RestrictedPython.compile.compile_restricted_exec: - result = c_exec("def foo(a, (c, (_bad, c))): pass") - # RestrictedPython.compile.compile_restricted_exec on Python 2 renders - # the error message twice. This is necessary as otherwise *_bad and - # **_bad would be allowed. - assert functiondef_err_msg in result.errors + result = c_exec("def foo(a, (c, (_bad, c))): pass") + # RestrictedPython.compile.compile_restricted_exec on Python 2 renders + # the error message twice. This is necessary as otherwise *_bad and + # **_bad would be allowed. + assert functiondef_err_msg in result.errors @pytest.mark.skipif( @@ -122,11 +119,7 @@ def test_RestrictingNodeTransformer__visit_FunctionDef__8( _getiter_.assert_called_once_with(val) _getiter_.reset_mock() - try: - e_exec(NESTED_SEQ_UNPACK, glb) - except AttributeError: - # The old RCompile did not support nested. - return + e_exec(NESTED_SEQ_UNPACK, glb) val = (1, 2, (3, (4, 5))) ret = glb['nested'](val) diff --git a/tests/transformer/test_iterator.py b/tests/transformer/test_iterator.py index fed9921..73ffd28 100644 --- a/tests/transformer/test_iterator.py +++ b/tests/transformer/test_iterator.py @@ -2,7 +2,6 @@ from tests import e_exec import pytest -import RestrictedPython import types @@ -163,9 +162,7 @@ def test_RestrictingNodeTransformer__guard_iter__2(e_exec, mocker): _getiter_.assert_has_calls(call_ref) _getiter_.reset_mock() - # The old code did not run with unpack sequence inside generators - if compile == RestrictedPython.compile.compile_restricted_exec: - ret = list(glb['generator'](it)) - assert ret == [3, 7, 11] - _getiter_.assert_has_calls(call_ref) - _getiter_.reset_mock() + ret = list(glb['generator'](it)) + assert ret == [3, 7, 11] + _getiter_.assert_has_calls(call_ref) + _getiter_.reset_mock() diff --git a/tests/transformer/test_lambda.py b/tests/transformer/test_lambda.py index 52ac701..c882ad4 100644 --- a/tests/transformer/test_lambda.py +++ b/tests/transformer/test_lambda.py @@ -5,7 +5,6 @@ from tests import e_exec import pytest -import RestrictedPython lambda_err_msg = 'Line 1: "_bad" is an invalid variable ' \ @@ -52,13 +51,11 @@ def test_RestrictingNodeTransformer__visit_Lambda__4(c_exec): @pytest.mark.parametrize(*c_exec) def test_RestrictingNodeTransformer__visit_Lambda__5(c_exec): """It prevents arguments starting with `_` in tuple unpacking.""" - # The old `compile` breaks with tuples in arguments: - if c_exec is RestrictedPython.compile.compile_restricted_exec: - result = c_exec("lambda (a, _bad): None") - # RestrictedPython.compile.compile_restricted_exec on Python 2 renders - # the error message twice. This is necessary as otherwise *_bad and - # **_bad would be allowed. - assert lambda_err_msg in result.errors + result = c_exec("lambda (a, _bad): None") + # RestrictedPython.compile.compile_restricted_exec on Python 2 renders + # the error message twice. This is necessary as otherwise *_bad and + # **_bad would be allowed. + assert lambda_err_msg in result.errors @pytest.mark.skipif( @@ -67,13 +64,11 @@ def test_RestrictingNodeTransformer__visit_Lambda__5(c_exec): @pytest.mark.parametrize(*c_exec) def test_RestrictingNodeTransformer__visit_Lambda__6(c_exec): """It prevents arguments starting with `_` in nested tuple unpacking.""" - # The old `compile` breaks with tuples in arguments: - if c_exec is RestrictedPython.compile.compile_restricted_exec: - result = c_exec("lambda (a, (c, (_bad, c))): None") - # RestrictedPython.compile.compile_restricted_exec on Python 2 renders - # the error message twice. This is necessary as otherwise *_bad and - # **_bad would be allowed. - assert lambda_err_msg in result.errors + result = c_exec("lambda (a, (c, (_bad, c))): None") + # RestrictedPython.compile.compile_restricted_exec on Python 2 renders + # the error message twice. This is necessary as otherwise *_bad and + # **_bad would be allowed. + assert lambda_err_msg in result.errors @pytest.mark.skipif( @@ -96,8 +91,7 @@ def check_getattr_in_lambda(arg=lambda _bad=(lambda ob, name: name): _bad2): def test_RestrictingNodeTransformer__visit_Lambda__8(c_exec): """It prevents arguments starting with `_` in weird lambdas.""" result = c_exec(BAD_ARG_IN_LAMBDA) - # RestrictedPython.compile.compile_restricted_exec finds both invalid - # names, while the old implementation seems to abort after the first. + # On Python 2 the first error message is contained twice: assert lambda_err_msg in result.errors @@ -116,11 +110,7 @@ def test_RestrictingNodeTransformer__visit_Lambda__9( } src = "m = lambda (a, (b, c)), *ag, **kw: a+b+c+sum(ag)+sum(kw.values())" - try: - e_exec(src, glb) - except AttributeError: - # Old implementation does not support tuple unpacking - return + e_exec(src, glb) ret = glb['m']((1, (2, 3)), 4, 5, 6, g=7, e=8) assert ret == 36