[Zope-Checkins] CVS: Zope/lib/python/RestrictedPython - Eval.py:1.6 Guards.py:1.14 Limits.py:1.5 RCompile.py:1.6 RestrictionMutator.py:1.13 SelectCompiler.py:1.6

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


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

Modified Files:
	Eval.py Guards.py Limits.py RCompile.py RestrictionMutator.py 
	SelectCompiler.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/Eval.py 1.5 => 1.6 ===
--- Zope/lib/python/RestrictedPython/Eval.py:1.5	Wed Aug 14 17:44:31 2002
+++ Zope/lib/python/RestrictedPython/Eval.py	Thu Jan 15 18:09:09 2004
@@ -11,13 +11,16 @@
 #
 ##############################################################################
 """Restricted Python Expressions
+
+$Id$
 """
-__rcs_id__='$Id$'
+
 __version__='$Revision$'[11:-2]
 
+from RestrictedPython import compile_restricted_eval
+
 from string import translate, strip
 import string
-compile_restricted_eval = None
 
 nltosp = string.maketrans('\r\n','  ')
 
@@ -30,8 +33,8 @@
 PROFILE = 0
 
 class RestrictionCapableEval:
-    """A base class for restricted code.
-    """
+    """A base class for restricted code."""
+    
     globals = {'__builtins__': None}
     rcode = None  # restricted
     ucode = None  # unrestricted
@@ -52,12 +55,6 @@
 
     def prepRestrictedCode(self):
         if self.rcode is None:
-            global compile_restricted_eval
-            if compile_restricted_eval is None:
-                # Late binding because this will import the whole
-                # compiler suite.
-                from RestrictedPython import compile_restricted_eval
-
             if PROFILE:
                 from time import clock
                 start = clock()


=== Zope/lib/python/RestrictedPython/Guards.py 1.13 => 1.14 ===
--- Zope/lib/python/RestrictedPython/Guards.py:1.13	Thu Nov 20 17:30:12 2003
+++ Zope/lib/python/RestrictedPython/Guards.py	Thu Jan 15 18:09:09 2004
@@ -10,56 +10,81 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
-from __future__ import nested_scopes
-
-__version__='$Revision$'[11:-2]
+__version__ = '$Revision$'[11:-2]
 
 import exceptions
-import new
+
+# This tiny set of safe builtins is extended by users of the module.
+# AccessControl.ZopeGuards contains a large set of wrappers for builtins.
+# DocumentTemplate.DT_UTil contains a few.
 
 safe_builtins = {}
-for name in ('None', 'abs', 'chr', 'divmod', 'float', 'hash', 'hex', 'int',
-             'len', 'max', 'min', 'oct', 'ord', 'round', 'str', 'pow',
-             'apply', 'callable', 'cmp', 'complex', 'isinstance',
-             'issubclass', 'long', 'repr', 'range', 'list', 'tuple',
-             'unichr', 'unicode', 'True', 'False', 'bool',
-             'dict', 'sum', 'enumerate',):
+
+for name in ['False', 'None', 'True', 'abs', 'basestring', 'bool', 'callable',
+             'chr', 'cmp', 'complex', 'divmod', 'float', 'hash',
+             'hex', 'id', 'int', 'isinstance', 'issubclass', 'len',
+             'long', 'oct', 'ord', 'pow', 'range', 'repr', 'round',
+             'str', 'tuple', 'unichr', 'unicode', 'xrange', 'zip']:
+
     safe_builtins[name] = __builtins__[name]
 
+# Wrappers provided by this module:
+# delattr
+# setattr
+
+# Wrappers provided by ZopeGuards:
+# __import__
+# apply
+# dict
+# enumerate
+# filter
+# getattr
+# hasattr
+# iter
+# list
+# map
+# max
+# min
+# sum
+
+# Builtins that are intentionally disabled
+# compile   - don't let them produce new code
+# dir       - a general purpose introspector, probably hard to wrap
+# execfile  - no direct I/O
+# file      - no direct I/O
+# globals   - uncontrolled namespace access
+# input     - no direct I/O
+# locals    - uncontrolled namespace access
+# open      - no direct I/O
+# raw_input - no direct I/O
+# vars      - uncontrolled namespace access
+
+# There are several strings that describe Python.  I think there's no
+# point to including these, although they are obviously safe:
+# copyright, credits, exit, help, license, quit
+
+# Not provided anywhere.  Do something about these?  Several are
+# related to new-style classes, which we are too scared of to support
+# <0.3 wink>.  coerce, buffer, and reload are esoteric enough that no
+# one should care.
+
+# buffer
+# classmethod
+# coerce
+# eval
+# intern
+# object
+# property
+# reload
+# slice
+# staticmethod
+# super
+# type
+
 for name in dir(exceptions):
     if name[0] != "_":
         safe_builtins[name] = getattr(exceptions, name)
 
-
-def _full_read_guard(g_attr, g_item):
-    # Nested scope abuse!
-    # The arguments are used by class Wrapper
-    # safetype variable is used by guard()
-    safetype = {type(()): 1, type([]): 1, type({}): 1, type(''): 1}.has_key
-    def guard(ob, write=None, safetype=safetype):
-        # Don't bother wrapping simple types, or objects that claim to
-        # handle their own read security.
-        if safetype(type(ob)) or getattr(ob, '_guarded_reads', 0):
-            return ob
-        # ob is shared by class Wrapper, so the class instance wraps it.
-        class Wrapper:
-            def __len__(self):
-                # Required for slices with negative bounds
-                return len(ob)
-            def __getattr__(self, name):
-                return g_attr(ob, name)
-            def __getitem__(self, i):
-                # Must handle both item and slice access.
-                return g_item(ob, i)
-            # Optional, for combined read/write guard
-            def __setitem__(self, index, val):
-                write(ob)[index] = val
-            def __setattr__(self, attr, val):
-                setattr(write(ob), attr, val)
-        return Wrapper()
-    return guard
-
-
 def _write_wrapper():
     # Construct the write wrapper class
     def _handler(secattr, error_msg):
@@ -90,9 +115,9 @@
 def _full_write_guard():
     # Nested scope abuse!
     # safetype and Wrapper variables are used by guard()
-    safetype = {type([]): 1, type({}): 1}.has_key
+    safetype = {dict: True, list: True}.has_key
     Wrapper = _write_wrapper()
-    def guard(ob, safetype=safetype, Wrapper=Wrapper):
+    def guard(ob):
         # Don't bother wrapping simple types, or objects that claim to
         # handle their own write security.
         if safetype(type(ob)) or hasattr(ob, '_guarded_writes'):


=== Zope/lib/python/RestrictedPython/Limits.py 1.4 => 1.5 ===
--- Zope/lib/python/RestrictedPython/Limits.py:1.4	Wed Aug 14 17:44:31 2002
+++ Zope/lib/python/RestrictedPython/Limits.py	Thu Jan 15 18:09:09 2004
@@ -34,13 +34,13 @@
 limited_builtins['range'] = limited_range
 
 def limited_list(seq):
-    if type(seq) is type(''):
+    if isinstance(seq, str):
         raise TypeError, 'cannot convert string to list'
     return list(seq)
 limited_builtins['list'] = limited_list
 
 def limited_tuple(seq):
-    if type(seq) is type(''):
+    if isinstance(seq, str):
         raise TypeError, 'cannot convert string to tuple'
     return tuple(seq)
 limited_builtins['tuple'] = limited_tuple


=== Zope/lib/python/RestrictedPython/RCompile.py 1.5 => 1.6 ===
--- Zope/lib/python/RestrictedPython/RCompile.py:1.5	Thu Nov  6 12:11:49 2003
+++ Zope/lib/python/RestrictedPython/RCompile.py	Thu Jan 15 18:09:09 2004
@@ -16,11 +16,9 @@
 
 __version__='$Revision$'[11:-2]
 
-
-from compiler import ast, parse, misc, syntax
+from compiler import ast, parse, misc, syntax, pycodegen
 from compiler.pycodegen import AbstractCompileMode, Expression, \
-     Interactive, Module
-from traceback import format_exception_only
+     Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp
 
 import MutatingWalker
 from RestrictionMutator import RestrictionMutator
@@ -39,8 +37,9 @@
         # Some other error occurred.
         raise
 
-
-class RestrictedCompileMode (AbstractCompileMode):
+class RestrictedCompileMode(AbstractCompileMode):
+    """Abstract base class for hooking up custom CodeGenerator."""
+    # See concrete subclasses below.
 
     def __init__(self, source, filename):
         self.rm = RestrictionMutator()
@@ -51,59 +50,17 @@
 
     def _get_tree(self):
         tree = self.parse()
-        rm = self.rm
-        MutatingWalker.walk(tree, rm)
-        if rm.errors:
-            raise SyntaxError, rm.errors[0]
+        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
 
-
-class RExpression(RestrictedCompileMode, Expression):
-    mode = "eval"
-    compile = Expression.compile
-
-
-class RInteractive(RestrictedCompileMode, Interactive):
-    mode = "single"
-    compile = Interactive.compile
-
-
-class RModule(RestrictedCompileMode, Module):
-    mode = "exec"
-    compile = Module.compile
-
-
-class RFunction(RModule):
-    """A restricted Python function built from parts.
-    """
-
-    def __init__(self, p, body, name, filename, globalize=None):
-        self.params = p
-        self.body = body
-        self.name = name
-        self.globalize = globalize
-        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, '<function parameters>', '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.
-        stmt1 = f.code.nodes[0]
-        if (isinstance(stmt1, ast.Discard) and
-            isinstance(stmt1.expr, ast.Const) and
-            type(stmt1.expr.value) is type('')):
-            f.doc = stmt1.expr.value
-        if self.globalize:
-            f.code.nodes.insert(0, ast.Global(map(str, self.globalize)))
-        return tree
+    def compile(self):
+        tree = self._get_tree()
+        gen = self.CodeGeneratorClass(tree)
+        self.code = gen.getCode()
 
 
 def compileAndTuplize(gen):
@@ -119,25 +76,26 @@
     The function can be reconstituted using the 'new' module:
 
     new.function(<code>, <globals>)
+
+    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).
     """
-    gen = RFunction(p, body, name, filename, globalize=globalize)
+    gen = RFunction(p, body, name, filename, globalize)
     return compileAndTuplize(gen)
 
 def compile_restricted_exec(s, filename='<string>'):
-    """Compiles a restricted code suite.
-    """
+    """Compiles a restricted code suite."""
     gen = RModule(s, filename)
     return compileAndTuplize(gen)
 
 def compile_restricted_eval(s, filename='<string>'):
-    """Compiles a restricted expression.
-    """
+    """Compiles a restricted expression."""
     gen = RExpression(s, filename)
     return compileAndTuplize(gen)
 
 def compile_restricted(source, filename, mode):
-    """Replacement for the builtin compile() function.
-    """
+    """Replacement for the builtin compile() function."""
     if mode == "single":
         gen = RInteractive(source, filename)
     elif mode == "exec":
@@ -149,3 +107,129 @@
                          "'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
+        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, '<function parameters>', '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.
+        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


=== Zope/lib/python/RestrictedPython/RestrictionMutator.py 1.12 => 1.13 ===
--- Zope/lib/python/RestrictedPython/RestrictionMutator.py:1.12	Thu Nov  6 12:11:49 2003
+++ Zope/lib/python/RestrictedPython/RestrictionMutator.py	Thu Jan 15 18:09:09 2004
@@ -10,12 +10,14 @@
 # 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.
-'''
-__version__='$Revision$'[11:-2]
+
+$Revision$
+"""
 
 from SelectCompiler import ast, parse, OP_ASSIGN, OP_DELETE, OP_APPLY
 
@@ -23,7 +25,7 @@
 # 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'''
+    """Strip lineno attributes from a code tree."""
     if node.__dict__.has_key('lineno'):
         del node.lineno
     for child in node.getChildren():
@@ -31,66 +33,42 @@
             rmLineno(child)
 
 def stmtNode(txt):
-    '''Make a "clean" statement node'''
+    """Make a "clean" statement node."""
     node = parse(txt).node.nodes[0]
     rmLineno(node)
     return node
 
-def exprNode(txt):
-    '''Make a "clean" expression node'''
-    return stmtNode(txt).expr
-
-# There should be up to four objects in the global namespace.  If a
-# wrapper function or print target is needed in a particular module or
-# function, it is obtained from one of these objects.  There is a
-# local and a global binding for each object: the global name has a
-# trailing underscore, while the local name does not.
-_print_target_name = ast.Name('_print')
-_getattr_name = ast.Name('_getattr')
-_getattr_name_expr = ast.Name('_getattr_')
-_getitem_name = ast.Name('_getitem')
-_getitem_name_expr = ast.Name('_getitem_')
-_write_guard_name = ast.Name('_write')
+# 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_")
 
 # Constants.
 _None_const = ast.Const(None)
-_write_const = ast.Const('write')
-
-# Example prep code:
-#
-# global _getattr_
-# _getattr = _getattr_
-_prep_code = {}
-for _n in ('getattr', 'getitem', 'write', 'print'):
-    _prep_code[_n] = [ast.Global(['_%s_' % _n]),
-                      stmtNode('_%s = _%s_' % (_n, _n))]
-# Call the global _print instead of copying it.
-_prep_code['print'][1] = stmtNode('_print = _print_()')
+_write_const = ast.Const("write")
 
-_printed_expr = exprNode('_print()')
+_printed_expr = stmtNode("_print()").expr
+_print_target_node = stmtNode("_print = _print_()")
 
-
-# Keep track of which restrictions have been applied in a given scope.
 class FuncInfo:
-    _print_used = 0
-    _printed_used = 0
-    _getattr_used = 0
-    _getitem_used = 0
-    _write_used = 0
-    _is_suite = 0  # True for modules and functions, false for expressions
-
+    print_used = False
+    printed_used = False
 
 class RestrictionMutator:
 
     def __init__(self):
-        self.funcinfo = FuncInfo()
         self.warnings = []
         self.errors = []
         self.used_names = {}
+        self.funcinfo = FuncInfo()
 
     def error(self, node, info):
-        """Records a security error discovered during compilation.
-        """
+        """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))
@@ -112,11 +90,11 @@
         and perhaps other statements assign names.  Special case:
         '_' is allowed.
         """
-        if len(name) > 1 and name[0] == '_':
+        if name.startswith("_") and name != "_":
             # Note: "_" *is* allowed.
             self.error(node, '"%s" is an invalid variable name because'
                        ' it starts with "_"' % name)
-        if name == 'printed':
+        if name == "printed":
             self.error(node, '"printed" is a reserved name.')
 
     def checkAttrName(self, node):
@@ -127,38 +105,23 @@
         security policy.  Special case: '_' is allowed.
         """
         name = node.attrname
-        if len(name) > 1 and name[0] == '_':
+        if name.startswith("_") and name != "_":
             # Note: "_" *is* allowed.
             self.error(node, '"%s" is an invalid attribute name '
                        'because it starts with "_".' % name)
 
     def prepBody(self, body):
-        """Prepends preparation code to a code suite.
-
-        For example, if a code suite uses getattr operations,
-        this places the following code at the beginning of the suite:
+        """Insert code for print at the beginning of the code suite."""
 
-            global _getattr_
-            _getattr = _getattr_
-
-        Similarly for _getitem_, _print_, and _write_.
-        """
-        info = self.funcinfo
-        if info._print_used or info._printed_used:
+        if self.funcinfo.print_used or self.funcinfo.printed_used:
             # Add code at top for creating _print_target
-            body[0:0] = _prep_code['print']
-            if not info._printed_used:
+            body.insert(0, _print_target_node)
+            if not self.funcinfo.printed_used:
                 self.warnings.append(
                     "Prints, but never reads 'printed' variable.")
-            elif not info._print_used:
+            elif not self.funcinfo.print_used:
                 self.warnings.append(
                     "Doesn't print, but reads 'printed' variable.")
-        if info._getattr_used:
-            body[0:0] = _prep_code['getattr']
-        if info._getitem_used:
-            body[0:0] = _prep_code['getitem']
-        if info._write_used:
-            body[0:0] = _prep_code['write']
 
     def visitFunction(self, node, walker):
         """Checks and mutates a function definition.
@@ -169,12 +132,15 @@
         """
         self.checkName(node, node.name)
         for argname in node.argnames:
-            self.checkName(node, argname)
+            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()
-        self.funcinfo._is_suite = 1
         node = walker.defaultVisitNode(node, exclude=('defaults',))
         self.prepBody(node.code.nodes)
         self.funcinfo = former_funcinfo
@@ -206,11 +172,10 @@
         method that changes them.
         """
         node = walker.defaultVisitNode(node)
-        self.funcinfo._print_used = 1
+        self.funcinfo.print_used = True
         if node.dest is None:
             node.dest = _print_target_name
         else:
-            self.funcinfo._getattr_used = 1
             # Pre-validate access to the "write" attribute.
             # "print >> ob, x" becomes
             # "print >> (_getattr(ob, 'write') and ob), x"
@@ -228,18 +193,58 @@
         """
         if node.name == 'printed':
             # Replace name lookup with an expression.
-            self.funcinfo._printed_used = 1
+            self.funcinfo.printed_used = True
             return _printed_expr
         self.checkName(node, node.name)
-        self.used_names[node.name] = 1
+        self.used_names[node.name] = True
         return node
 
-    def visitAssName(self, node, walker):
-        """Checks a name assignment using checkName().
+    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.  Exactly the same
+        # transformation is needed to convert
+        #   [... 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 visitGetattr(self, node, walker):
         """Converts attribute access to a function call.
 
@@ -250,21 +255,13 @@
         """
         self.checkAttrName(node)
         node = walker.defaultVisitNode(node)
-        if getattr(node, 'in_aug_assign', 0):
+        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.')
-            #expr.append(_write_guard_name)
-            #self.funcinfo._write_used = 1
-        self.funcinfo._getattr_used = 1
-        if self.funcinfo._is_suite:
-            # Use the local function _getattr().
-            ga = _getattr_name
-        else:
-            # Use the global function _getattr_().
-            ga = _getattr_name_expr
-        return ast.CallFunc(ga, [node.expr, ast.Const(node.attrname)])
+        return ast.CallFunc(_getattr_name,
+                            [node.expr, ast.Const(node.attrname)])
 
     def visitSubscript(self, node, walker):
         """Checks all kinds of subscripts.
@@ -283,14 +280,11 @@
         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', 0):
+            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.')
-                #expr.append(_write_guard_name)
-                #self.funcinfo._write_used = 1
-            self.funcinfo._getitem_used = 1
             if hasattr(node, 'subs'):
                 # Subscript.
                 subs = node.subs
@@ -310,15 +304,10 @@
                 if upper is None:
                     upper = _None_const
                 subs = ast.Sliceobj([lower, upper])
-            if self.funcinfo._is_suite:
-                gi = _getitem_name
-            else:
-                gi = _getitem_name_expr
-            return ast.CallFunc(gi, [node.expr, subs])
+            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_guard_name, [node.expr])
-            self.funcinfo._write_used = 1
+            node.expr = ast.CallFunc(_write_name, [node.expr])
         return node
 
     visitSlice = visitSubscript
@@ -331,8 +320,7 @@
         """
         self.checkAttrName(node)
         node = walker.defaultVisitNode(node)
-        node.expr = ast.CallFunc(_write_guard_name, [node.expr])
-        self.funcinfo._write_used = 1
+        node.expr = ast.CallFunc(_write_name, [node.expr])
         return node
 
     def visitExec(self, node, walker):
@@ -357,7 +345,6 @@
         Zope doesn't make use of this.  The body of Python scripts is
         always at function scope.
         """
-        self.funcinfo._is_suite = 1
         node = walker.defaultVisitNode(node)
         self.prepBody(node.node.nodes)
         return node
@@ -372,12 +359,11 @@
         This could be a problem if untrusted code got access to a
         mutable database object that supports augmented assignment.
         """
-        node.node.in_aug_assign = 1
+        node.node.in_aug_assign = True
         return walker.defaultVisitNode(node)
 
     def visitImport(self, node, walker):
-        """Checks names imported using checkName().
-        """
+        """Checks names imported using checkName()."""
         for name, asname in node.names:
             self.checkName(node, name)
             if asname:


=== Zope/lib/python/RestrictedPython/SelectCompiler.py 1.5 => 1.6 ===
--- Zope/lib/python/RestrictedPython/SelectCompiler.py:1.5	Thu Nov  6 12:11:49 2003
+++ Zope/lib/python/RestrictedPython/SelectCompiler.py	Thu Jan 15 18:09:09 2004
@@ -10,12 +10,10 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
-'''
-Compiler selector.
-$Id$
-'''
+"""Compiler selector.
 
-import sys
+$Id$
+"""
 
 # Use the compiler from the standard library.
 import compiler




More information about the Zope-Checkins mailing list