[Zope-Checkins] CVS: Zope/lib/python/RestrictedPython/tests - before_and_after.py:1.2 class.py:1.2 lambda.py:1.2 unpack.py:1.2 verify.py:1.2 restricted_module.py:1.11 security_in_syntax.py:1.8 testRestrictions.py:1.16

Tres Seaver tseaver at zope.com
Thu Jan 15 18:09:12 EST 2004


Update of /cvs-repository/Zope/lib/python/RestrictedPython/tests
In directory cvs.zope.org:/tmp/cvs-serv24317/RestrictedPython/tests

Modified Files:
	restricted_module.py security_in_syntax.py testRestrictions.py 
Added Files:
	before_and_after.py class.py lambda.py unpack.py verify.py 
Log Message:


  - Merge a number of entangled issues from 2.6 / 2.7 audit:

    Iteration over sequences could in some cases fail to check access 
    to an object obtained from the sequence. Subsequent checks (such 
    as for attributes access) of such an object would still be 
    performed, but it should not have been possible to obtain the 
    object in the first place.

    List and dictionary instance methods such as the get method of 
    dictionary objects were not security aware and could return an 
    object without checking access to that object. Subsequent checks 
    (such as for attributes access) of such an object would still be 
    performed, but it should not have been possible to obtain the 
    object in the first place.

    Use of "import as" in Python scripts could potentially rebind 
    names in ways that could be used to avoid appropriate security 
    checks.

    A number of newer built-ins were either unavailable in untrusted 
    code or did not perform adequate security checking.

    Unpacking via function calls, variable assignment, exception 
    variables and other contexts did not perform adequate security 
    checks, potentially allowing access to objects that should have 
    been protected.

    Class security was not properly intialized for PythonScripts, 
    potentially allowing access to variables that should be protected. 
    It turned out that most of the security assertions were in fact 
    activated as a side effect of other code, but this fix is still 
    appropriate to ensure that all security declarations are properly 
    applied.

    DTMLMethods with proxy rights could incorrectly transfer those 
    rights via acquisition when traversing to a parent object.


=== Zope/lib/python/RestrictedPython/tests/before_and_after.py 1.1 => 1.2 ===
--- /dev/null	Thu Jan 15 18:09:12 2004
+++ Zope/lib/python/RestrictedPython/tests/before_and_after.py	Thu Jan 15 18:09:11 2004
@@ -0,0 +1,246 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Restricted Python transformation examples
+
+This module contains pairs of functions. Each pair has a before and an
+after function.  The after function shows the source code equivalent
+of the before function after it has been modified by the restricted
+compiler.
+
+These examples are actually used in the testRestrictions.py
+checkBeforeAndAfter() unit tests, which verifies that the restricted compiler
+actually produces the same output as would be output by the normal compiler
+for the after function.
+
+$Id$
+"""
+
+# getattr
+
+def simple_getattr_before(x):
+    return x.y
+
+def simple_getattr_after(x):
+    return _getattr_(x, 'y')
+
+# set attr
+
+def simple_setattr_before():
+    x.y = "bar"
+
+def simple_setattr_after():
+    _write_(x).y = "bar"
+
+# for loop and list comprehensions
+
+def simple_forloop_before(x):
+    for x in [1, 2, 3]:
+        pass
+
+def simple_forloop_after(x):
+    for x in _getiter_([1, 2, 3]):
+        pass
+
+def nested_forloop_before(x):
+    for x in [1, 2, 3]:
+        for y in "abc":
+            pass
+
+def nested_forloop_after(x):
+    for x in _getiter_([1, 2, 3]):
+        for y in _getiter_("abc"):
+            pass
+
+def simple_list_comprehension_before():
+    x = [y**2 for y in whatever if y > 3]
+
+def simple_list_comprehension_after():
+    x = [y**2 for y in _getiter_(whatever) if y > 3]
+
+def nested_list_comprehension_before():
+    x = [x**2 + y**2 for x in whatever1 if x >= 0
+                     for y in whatever2 if y >= x]
+
+def nested_list_comprehension_after():
+    x = [x**2 + y**2 for x in _getiter_(whatever1) if x >= 0
+                     for y in _getiter_(whatever2) if y >= x]
+
+# print
+
+def simple_print_before():
+    print "foo"
+
+def simple_print_after():
+    _print = _print_()
+    print >> _print, "foo"
+
+# getitem
+
+def simple_getitem_before():
+    return x[0]
+
+def simple_getitem_after():
+    return _getitem_(x, 0)
+
+def simple_get_tuple_key_before():
+    x = y[1,2]
+
+def simple_get_tuple_key_after():
+    x = _getitem_(y, (1,2))
+
+# set item
+
+def simple_setitem_before():
+    x[0] = "bar"
+
+def simple_setitem_after():
+    _write_(x)[0] = "bar"
+
+# delitem
+
+def simple_delitem_before():
+    del x[0]
+
+def simple_delitem_after():
+    del _write_(x)[0]
+
+# a collection of function parallels to many of the above
+
+def function_with_print_before():
+    def foo():
+        print "foo"
+        return printed
+
+def function_with_print_after():
+    def foo():
+        _print = _print_()
+        print >> _print, "foo"
+        return _print()
+
+def function_with_getattr_before():
+    def foo():
+        return x.y
+
+def function_with_getattr_after():
+    def foo():
+        return _getattr_(x, 'y')
+
+def function_with_setattr_before():
+    def foo(x):
+        x.y = "bar"
+
+def function_with_setattr_after():
+    def foo(x):
+        _write_(x).y = "bar"
+
+def function_with_getitem_before():
+    def foo(x):
+        return x[0]
+
+def function_with_getitem_after():
+    def foo(x):
+        return _getitem_(x, 0)
+
+def function_with_forloop_before():
+    def foo():
+        for x in [1, 2, 3]:
+            pass
+
+def function_with_forloop_after():
+    def foo():
+        for x in _getiter_([1, 2, 3]):
+            pass
+
+# this, and all slices, won't work in these tests because the before code
+# parses the slice as a slice object, while the after code can't generate a
+# slice object in this way.  The after code as written below
+# is parsed as a call to the 'slice' name, not as a slice object.
+# XXX solutions?
+
+#def simple_slice_before():
+#    x = y[:4]
+
+#def simple_slice_after():
+#    _getitem = _getitem_
+#    x = _getitem(y, slice(None, 4))
+
+# Assignment stmts in Python can be very complicated.  The "no_unpack"
+# test makes sure we're not doing unnecessary rewriting.
+def no_unpack_before():
+    x = y
+    x = [y]
+    x = y,
+    x = (y, (y, y), [y, (y,)], x, (x, y))
+    x = y = z = (x, y, z)
+
+no_unpack_after = no_unpack_before    # that is, should be untouched
+
+
+# apply() variations.  Native apply() is unsafe because, e.g.,
+#
+#     def f(a, b, c):
+#         whatever
+#
+#     apply(f, two_element_sequence, dict_with_key_c)
+#
+# or (different spelling of the same thing)
+#
+#     f(*two_element_sequence, **dict_with_key_c)
+#
+# makes the elements of two_element_sequence visible to f via its 'a' and
+# 'b' arguments, and the dict_with_key_c['c'] value visible via its 'c'
+# argument.  That is, it's a devious way to extract values without going
+# thru security checks.
+
+def star_call_before():
+    foo(*a)
+
+def star_call_after():
+    _apply_(foo, *a)
+
+def star_call_2_before():
+    foo(0, *a)
+
+def star_call_2_after():
+    _apply_(foo, 0, *a)
+
+def starstar_call_before():
+    foo(**d)
+
+def starstar_call_after():
+    _apply_(foo, **d)
+
+def star_and_starstar_call_before():
+    foo(*a, **d)
+
+def star_and_starstar_call_after():
+    _apply_(foo, *a, **d)
+
+def positional_and_star_and_starstar_call_before():
+    foo(b, *a, **d)
+
+def positional_and_star_and_starstar_call_after():
+    _apply_(foo, b, *a, **d)
+
+def positional_and_defaults_and_star_and_starstar_call_before():
+    foo(b, x=y, w=z, *a, **d)
+
+def positional_and_defaults_and_star_and_starstar_call_after():
+    _apply_(foo, b, x=y, w=z, *a, **d)
+
+def lambda_with_getattr_in_defaults_before():
+    f = lambda x=y.z: x
+
+def lambda_with_getattr_in_defaults_after():
+    f = lambda x=_getattr_(y, "z"): x


=== Zope/lib/python/RestrictedPython/tests/class.py 1.1 => 1.2 ===
--- /dev/null	Thu Jan 15 18:09:12 2004
+++ Zope/lib/python/RestrictedPython/tests/class.py	Thu Jan 15 18:09:11 2004
@@ -0,0 +1,13 @@
+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()


=== Zope/lib/python/RestrictedPython/tests/lambda.py 1.1 => 1.2 ===
--- /dev/null	Thu Jan 15 18:09:12 2004
+++ Zope/lib/python/RestrictedPython/tests/lambda.py	Thu Jan 15 18:09:11 2004
@@ -0,0 +1,5 @@
+f = lambda x, y=1: x + y
+if f(2) != 3:
+    raise ValueError
+if f(2, 2) != 4:
+    raise ValueError


=== Zope/lib/python/RestrictedPython/tests/unpack.py 1.1 => 1.2 ===
--- /dev/null	Thu Jan 15 18:09:12 2004
+++ Zope/lib/python/RestrictedPython/tests/unpack.py	Thu Jan 15 18:09:11 2004
@@ -0,0 +1,81 @@
+# 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]]]) )


=== Zope/lib/python/RestrictedPython/tests/verify.py 1.1 => 1.2 ===
--- /dev/null	Thu Jan 15 18:09:12 2004
+++ Zope/lib/python/RestrictedPython/tests/verify.py	Thu Jan 15 18:09:11 2004
@@ -0,0 +1,170 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+
+def verify(code):
+    """Verify all code objects reachable from code.
+
+    In particular, traverse into contained code objects in the
+    co_consts table.
+    """
+    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)
+
+    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.
+            if not (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, co.co_filename, line))
+        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]
+
+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)


=== Zope/lib/python/RestrictedPython/tests/restricted_module.py 1.10 => 1.11 ===
--- Zope/lib/python/RestrictedPython/tests/restricted_module.py:1.10	Wed Aug 14 17:44:31 2002
+++ Zope/lib/python/RestrictedPython/tests/restricted_module.py	Thu Jan 15 18:09:11 2004
@@ -1,4 +1,5 @@
 from __future__ import nested_scopes
+import sys
 
 def print0():
     print 'Hello, world!',
@@ -27,6 +28,18 @@
         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 primes():
     # Somewhat obfuscated code on purpose
     print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
@@ -57,6 +70,9 @@
     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
@@ -154,3 +170,7 @@
     def f2():
         return a
     return f1() + f2()
+
+class Classic:
+    pass
+


=== Zope/lib/python/RestrictedPython/tests/security_in_syntax.py 1.7 => 1.8 ===
--- Zope/lib/python/RestrictedPython/tests/security_in_syntax.py:1.7	Tue Jan  6 11:48:34 2004
+++ Zope/lib/python/RestrictedPython/tests/security_in_syntax.py	Thu Jan 15 18:09:11 2004
@@ -50,3 +50,7 @@
         # The name of choice (say, _write) is now assigned to an exception
         # object.  Hard to exploit, but conceivable.
         pass
+
+def keyword_arg_with_bad_name():
+    def f(okname=1, __badname=2):
+        pass


=== Zope/lib/python/RestrictedPython/tests/testRestrictions.py 1.15 => 1.16 ===
--- Zope/lib/python/RestrictedPython/tests/testRestrictions.py:1.15	Thu Nov  6 12:11:50 2003
+++ Zope/lib/python/RestrictedPython/tests/testRestrictions.py	Thu Jan 15 18:09:11 2004
@@ -1,18 +1,26 @@
-from string import rfind
-import sys, os
-
+import os
+import re
+import sys
 import unittest
+
+# 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 compile_restricted, PrintCollector
 from RestrictedPython.Eval import RestrictionCapableEval
-from RestrictedPython.tests import restricted_module, security_in_syntax
-from types import FunctionType
+from RestrictedPython.tests import before_and_after, restricted_module, verify
+from RestrictedPython.RCompile import RModule
 
-if __name__=='__main__':
-    here = os.getcwd()
-else:
-    here = os.path.dirname(__file__)
-    if not here:
-        here = os.getcwd()
+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."""
@@ -42,9 +50,14 @@
     f.close()
     return fn, msg
 
+def get_source(func):
+    """Less silly interface to find_source""" # Sheesh
+    code = func.func_code
+    return find_source(code.co_filename, code)[1]
+
 def create_rmodule():
     global rmodule
-    fn = os.path.join(here, 'restricted_module.py')
+    fn = os.path.join(_HERE, 'restricted_module.py')
     f = open(fn, 'r')
     source = f.read()
     f.close()
@@ -52,7 +65,8 @@
     compile(source, fn, 'exec')
     # Now compile it for real
     code = compile_restricted(source, fn, 'exec')
-    rmodule = {'__builtins__':{'__import__':__import__, 'None':None}}
+    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',
@@ -60,8 +74,6 @@
         rmodule[name] = builtins[name]
     exec code in rmodule
 
-create_rmodule()
-
 class AccessDenied (Exception): pass
 
 DisallowedObject = []
@@ -158,20 +170,35 @@
         _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)
 
 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})
+                                  '_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,
+                                  })
         return func(*args, **kw)
 
     def checkPrint(self):
         for i in range(2):
             res = self.execFunc('print%s' % i)
-            assert res == 'Hello, world!', res
+            self.assertEqual(res, 'Hello, world!')
 
     def checkPrintToNone(self):
         try:
@@ -180,23 +207,23 @@
             # Passed.  "None" has no "write" attribute.
             pass
         else:
-            assert 0, res
+            self.fail(0, res)
 
     def checkPrintStuff(self):
         res = self.execFunc('printStuff')
-        assert res == 'a b c', res
+        self.assertEqual(res, 'a b c')
 
     def checkPrintLines(self):
         res = self.execFunc('printLines')
-        assert res == '0 1 2\n3 4 5\n6 7 8\n', res
+        self.assertEqual(res,  '0 1 2\n3 4 5\n6 7 8\n')
 
     def checkPrimes(self):
         res = self.execFunc('primes')
-        assert res == '[2, 3, 5, 7, 11, 13, 17, 19]', res
+        self.assertEqual(res, '[2, 3, 5, 7, 11, 13, 17, 19]')
 
     def checkAllowedSimple(self):
         res = self.execFunc('allowed_simple')
-        assert res == 'abcabcabc', res
+        self.assertEqual(res, 'abcabcabc')
 
     def checkAllowedRead(self):
         self.execFunc('allowed_read', RestrictedObject())
@@ -207,6 +234,16 @@
     def checkAllowedArgs(self):
         self.execFunc('allowed_default_args', RestrictedObject())
 
+    def checkTryMap(self):
+        res = self.execFunc('try_map')
+        self.assertEqual(res, "[2, 3, 4]")
+
+    def checkApply(self):
+        del apply_wrapper_called[:]
+        res = self.execFunc('try_apply')
+        self.assertEqual(apply_wrapper_called, ["yes"])
+        self.assertEqual(res, "321")
+
     def checkDenied(self):
         for k in rmodule.keys():
             if k[:6] == 'denied':
@@ -216,12 +253,12 @@
                     # Passed the test
                     pass
                 else:
-                    raise AssertionError, '%s() did not trip security' % k
+                    self.fail('%s() did not trip security' % k)
 
     def checkSyntaxSecurity(self):
         # Ensures that each of the functions in security_in_syntax.py
         # throws a SyntaxError when using compile_restricted.
-        fn = os.path.join(here, 'security_in_syntax.py')
+        fn = os.path.join(_HERE, 'security_in_syntax.py')
         f = open(fn, 'r')
         source = f.read()
         f.close()
@@ -240,19 +277,19 @@
                     # Passed the test.
                     pass
                 else:
-                    raise AssertionError, '%s should not have compiled' % k
+                    self.fail('%s should not have compiled' % k)
 
     def checkOrderOfOperations(self):
         res = self.execFunc('order_of_operations')
-        assert (res == 0), res
+        self.assertEqual(res, 0)
 
     def checkRot13(self):
         res = self.execFunc('rot13', 'Zope is k00l')
-        assert (res == 'Mbcr vf x00y'), res
+        self.assertEqual(res, 'Mbcr vf x00y')
 
     def checkNestedScopes1(self):
         res = self.execFunc('nested_scopes_1')
-        assert (res == 2), res
+        self.assertEqual(res, 2)
 
     def checkUnrestrictedEval(self):
         expr = RestrictionCapableEval("{'a':[m.pop()]}['a'] + [m[0]]")
@@ -260,10 +297,10 @@
         expect = v[:]
         expect.reverse()
         res = expr.eval({'m':v})
-        assert res == expect, res
+        self.assertEqual(res, expect)
         v = [12, 34]
         res = expr(m=v)
-        assert res == expect
+        self.assertEqual(res, expect)
 
     def checkStackSize(self):
         for k, rfunc in rmodule.items():
@@ -275,23 +312,145 @@
                     'should have been at least %d, but was only %d'
                     % (k, ss, rss))
 
+
+    def checkBeforeAndAfter(self):
+        from RestrictedPython.RCompile import RModule
+
+        from compiler import parse
+
+        defre = re.compile(r'def ([_A-Za-z0-9]+)_(after|before)\(')
+
+        beforel = [name for name in before_and_after.__dict__
+                   if name.endswith("_before")]
+
+        for name in beforel:
+            before = getattr(before_and_after, name)
+            before_src = get_source(before)
+            before_src = re.sub(defre, r'def \1(', before_src)
+            rm = RModule(before_src, '')
+            tree_before = rm._get_tree()
+
+            after = getattr(before_and_after, name[:-6]+'after')
+            after_src = get_source(after)
+            after_src = re.sub(defre, r'def \1(', after_src)
+            tree_after = parse(after_src)
+
+            self.assertEqual(str(tree_before), str(tree_after))
+
+            rm.compile()
+            verify.verify(rm.getCode())
+
+    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 checkUnpackSequence(self):
+        co = self._compile_file("unpack.py")
+        calls = []
+        def getiter(seq):
+            calls.append(seq)
+            return list(seq)
+        globals = {"_getiter_": getiter}
+        exec co in 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 checkUnpackSequenceExpression(self):
+        co = compile_restricted("[x for x, y in [(1, 2)]]", "<string>", "eval")
+        verify.verify(co)
+        calls = []
+        def getiter(s):
+            calls.append(s)
+            return list(s)
+        globals = {"_getiter_": getiter}
+        exec co in globals, {}
+        self.assertEqual(calls, [[(1,2)], (1, 2)])
+
+    def checkUnpackSequenceSingle(self):
+        co = compile_restricted("x, y = 1, 2", "<string>", "single")
+        verify.verify(co)
+        calls = []
+        def getiter(s):
+            calls.append(s)
+            return list(s)
+        globals = {"_getiter_": getiter}
+        exec co in globals, {}
+        self.assertEqual(calls, [(1, 2)])
+
+    def checkClass(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 in 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 checkLambda(self):
+        co = self._compile_file("lambda.py")
+        exec co in {}, {}
+
+    def checkSyntaxError(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, "<string>", "exec")
+
+create_rmodule()
+
 def test_suite():
     return unittest.makeSuite(RestrictionTests, 'check')
 
-def main():
-    alltests=test_suite()
-    runner = unittest.TextTestRunner()
-    runner.run(alltests)
-
-def debug():
-    test_suite().debug()
-
-def pdebug():
-    import pdb
-    pdb.run('debug()')
-
 if __name__=='__main__':
-    if len(sys.argv) > 1:
-        globals()[sys.argv[1]]()
-    else:
-        main()
+    unittest.main(defaultTest="test_suite")




More information about the Zope-Checkins mailing list