[Zope-Checkins] CVS: Zope/lib/python/RestrictedPython - RCompile.py:1.2 RCompile_2_1.py:1.2 SelectCompiler.py:1.2 MutatingWalker.py:1.5 RestrictionMutator.py:1.9 __init__.py:1.4 Compilers.py:NONE

Shane Hathaway shane@digicool.com
Fri, 21 Dec 2001 14:35:18 -0500


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

Modified Files:
	MutatingWalker.py RestrictionMutator.py __init__.py 
Added Files:
	RCompile.py RCompile_2_1.py SelectCompiler.py 
Removed Files:
	Compilers.py 
Log Message:
Merged RestrictedPython-2_2-branch.


=== Zope/lib/python/RestrictedPython/RCompile.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 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
+# 
+##############################################################################
+"""Compiles restricted code using the compiler module from the
+Python standard library.
+"""
+
+__version__='$Revision$'[11:-2]
+
+
+from compiler import ast, parse, misc, syntax
+from compiler.pycodegen import AbstractCompileMode, Expression, \
+     Interactive, Module
+from traceback import format_exception_only
+
+import MutatingWalker
+from RestrictionMutator import RestrictionMutator
+
+
+def niceParse(source, filename, mode):
+    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):
+
+    def __init__(self, source, filename):
+        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()
+        rm = self.rm
+        MutatingWalker.walk(tree, rm)
+        if rm.errors:
+            raise SyntaxError, 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):
+        self.params = p
+        self.body = body
+        self.name = name
+        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
+        return tree
+
+
+def compileAndTuplize(gen):
+    try:
+        gen.compile()
+    except SyntaxError, v:
+        return None, (str(v),), gen.rm.warnings, gen.rm.used_names
+    return gen.getCode(), (), gen.rm.warnings, gen.rm.used_names
+
+def compile_restricted_function(p, body, name, filename):
+    """Compiles a restricted code object for a function.
+
+    The function can be reconstituted using the 'new' module:
+
+    new.function(<code>, <globals>)
+    """
+    gen = RFunction(p, body, name, filename)
+    return compileAndTuplize(gen)
+
+def compile_restricted_exec(s, filename='<string>'):
+    """Compiles a restricted code suite.
+    """
+    gen = RModule(s, filename)
+    return compileAndTuplize(gen)
+
+def compile_restricted_eval(s, filename='<string>'):
+    """Compiles a restricted expression.
+    """
+    gen = RExpression(s, filename)
+    return compileAndTuplize(gen)
+
+def compile_restricted(source, filename, mode):
+    """Replacement for the builtin compile() function.
+    """
+    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()
+


=== Zope/lib/python/RestrictedPython/RCompile_2_1.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 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
+# 
+##############################################################################
+
+__version__='$Revision$'[11:-2]
+
+import sys
+from traceback import format_exception_only
+
+def getSyntaxError(source, mode):
+    try:
+        compile(source, '<string>', mode)
+    except SyntaxError:
+        err = format_exception_only(SyntaxError, sys.exc_info()[1])
+        err = [line.rstrip() for line in err]
+    else:
+        err = ['Unknown parser error.']
+    return None, err, [], {}
+
+from parser import ParserError
+from compiler_2_1.transformer import Transformer
+
+def tryParsing(source, mode):
+    if mode == 'eval':
+        parser = Transformer().parseexpr
+    else:
+        parser = Transformer().parsesuite
+    try:
+        return parser(source), None
+    except ParserError:
+        return None, getSyntaxError(source, mode)
+
+import MutatingWalker
+from RestrictionMutator import RestrictionMutator
+from compiler_2_1 import ast, visitor, pycodegen
+
+def compile_restricted_function(p, body, name, filename):
+    '''Compile a restricted code object for a function.
+
+    The function can be reconstituted using the 'new' module:
+
+    new.function(<code>, <globals>)
+    '''
+    rm = RestrictionMutator()
+    # Parse the parameters and body, then combine them.
+    tree, err = tryParsing('def f(%s): pass' % p, 'exec')
+    if err:
+        if len(err) > 1:
+            # Drop the first line of the error and adjust the next two.
+            err[1].pop(0) 
+            err[1][0] = 'parameters: %s\n' % err[1][0][10:-8]
+            err[1][1] = '  ' + err[1][1]
+        return err
+    f = tree.node.nodes[0]
+    btree, err = tryParsing(body, 'exec')
+    if err: return err
+    f.code.nodes = btree.node.nodes
+    f.name = 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
+    MutatingWalker.walk(tree, rm)
+    if rm.errors:
+        return None, rm.errors, rm.warnings, rm.used_names
+    gen = pycodegen.NestedScopeModuleCodeGenerator(filename)
+    visitor.walk(tree, gen)
+    return gen.getCode(), (), rm.warnings, rm.used_names
+
+def compile_restricted_exec(s, filename='<string>', nested_scopes=1):
+    '''Compile a restricted code suite.'''
+    rm = RestrictionMutator()
+    tree, err = tryParsing(s, 'exec')
+    if err: return err
+    MutatingWalker.walk(tree, rm)
+    if rm.errors:
+        return None, rm.errors, rm.warnings, rm.used_names
+    if nested_scopes:
+        gen = pycodegen.NestedScopeModuleCodeGenerator(filename)
+    else:
+        gen = pycodegen.ModuleCodeGenerator(filename)
+    visitor.walk(tree, gen)
+    return gen.getCode(), (), rm.warnings, rm.used_names
+
+if 1:
+  def compile_restricted_eval(s, filename='<string>', nested_scopes=1):
+    '''Compile a restricted expression.'''
+    r = compile_restricted_exec('def f(): return \\\n' + s, filename,
+                                nested_scopes)
+    err = r[1]
+    if err:
+        if len(err) > 1:
+            err.pop(0) # Discard first line of error
+    else:
+        # Extract the code object representing the function body
+        r = (r[0].co_consts[1],) + r[1:]
+    return r
+
+else:
+
+  def compile_restricted_eval(s, filename='<string>'):
+    '''Compile a restricted expression.'''
+    rm = RestrictionMutator()
+    tree, err = tryParsing(s, 'eval')
+    if err:
+        err[1].pop(0) # Discard first line of error
+        return err
+    MutatingWalker.walk(tree, rm)
+    if rm.errors:
+        return None, rm.errors, rm.warnings, rm.used_names
+    # XXX No "EvalCodeGenerator" exists
+    # so here's a hack that gets around it.
+    gen = pycodegen.ModuleCodeGenerator(filename)
+    gen.emit('SET_LINENO', 0)
+    visitor.walk(tree, gen)
+    gen.emit('RETURN_VALUE')
+    return gen.getCode(), (), rm.warnings, rm.used_names
+
+DEBUG = 0
+def compile_restricted(source, filename, mode):
+    '''Returns restricted compiled code. The signature of this
+    function should match the signature of the builtin compile.'''
+    if DEBUG:
+        from time import clock
+        start = clock()
+
+    if mode == 'eval':
+        r = compile_restricted_eval(source, filename)
+    elif mode == 'exec':
+        r = compile_restricted_exec(source, filename)
+    else:
+        raise ValueError, "compile_restricted() arg 3 must be 'exec' or 'eval'"
+
+    if DEBUG:
+        end = clock()
+        print 'compile_restricted: %d ms for %s' % (
+            (end - start) * 1000, repr(filename))
+    code, errors, warnings, used_names = r
+    if errors:
+        raise SyntaxError, errors[0]
+    return code


=== Zope/lib/python/RestrictedPython/SelectCompiler.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 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
+# 
+##############################################################################
+'''
+Compiler selector.
+$Id$
+'''
+
+try:
+    import compiler  # Should only be found if Python >= 2.2.
+except ImportError:
+    # Use the compiler_2_1 package.
+    from compiler_2_1 import ast
+    from compiler_2_1.transformer import parse
+    from compiler_2_1.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
+
+    from RCompile_2_1 import \
+         compile_restricted, \
+         compile_restricted_function, \
+         compile_restricted_exec, \
+         compile_restricted_eval
+else:
+    # Use the compiler from the standard library.
+    from compiler import ast
+    from compiler.transformer import parse
+    from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
+
+    from RCompile import \
+         compile_restricted, \
+         compile_restricted_function, \
+         compile_restricted_exec, \
+         compile_restricted_eval
+
+


=== Zope/lib/python/RestrictedPython/MutatingWalker.py 1.4 => 1.5 ===
 __version__='$Revision$'[11:-2]
 
-from compiler import ast
+from SelectCompiler import ast
 
 ListType = type([])
 TupleType = type(())


=== Zope/lib/python/RestrictedPython/RestrictionMutator.py 1.8 => 1.9 ===
 __version__='$Revision$'[11:-2]
 
-from compiler import ast
-from compiler.transformer import parse
-from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
+from SelectCompiler import ast, parse, OP_ASSIGN, OP_DELETE, OP_APPLY
 
 # These utility functions allow us to generate AST subtrees without
 # line number attributes.  These trees can then be inserted into other
@@ -50,7 +48,9 @@
 # local, and therefore efficient to access, in function scopes.
 _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')
 
 # Constants.
@@ -78,9 +78,11 @@
     _getattr_used = 0
     _getitem_used = 0
     _write_used = 0
+    _is_suite = 0  # True for modules and functions, false for expressions
 
 
 class RestrictionMutator:
+
     def __init__(self):
         self.funcinfo = FuncInfo()
         self.warnings = []
@@ -113,6 +115,8 @@
                        'because it starts with "_".' % name)
 
     def prepBody(self, body):
+        """Appends prep code to the beginning of a code suite.
+        """
         info = self.funcinfo
         if info._print_used or info._printed_used:
             # Add code at top for creating _print_target
@@ -138,6 +142,7 @@
 
         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
@@ -189,8 +194,11 @@
             #expr.append(_write_guard_name)
             #self.funcinfo._write_used = 1
         self.funcinfo._getattr_used = 1
-        return ast.CallFunc(_getattr_name,
-                            [node.expr, ast.Const(node.attrname)])
+        if self.funcinfo._is_suite:
+            ga = _getattr_name
+        else:
+            ga = _getattr_name_expr
+        return ast.CallFunc(ga, [node.expr, ast.Const(node.attrname)])
 
     def visitSubscript(self, node, walker):
         node = walker.defaultVisitNode(node)
@@ -223,7 +231,11 @@
                 if upper is None:
                     upper = _None_const
                 subs = ast.Sliceobj([lower, upper])
-            return ast.CallFunc(_getitem_name, [node.expr, subs])
+            if self.funcinfo._is_suite:
+                gi = _getitem_name
+            else:
+                gi = _getitem_name_expr
+            return ast.CallFunc(gi, [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])
@@ -251,6 +263,7 @@
         return walker.defaultVisitNode(node)
 
     def visitModule(self, node, walker):
+        self.funcinfo._is_suite = 1
         node = walker.defaultVisitNode(node)
         self.prepBody(node.node.nodes)
         return node


=== Zope/lib/python/RestrictedPython/__init__.py 1.3 => 1.4 ===
 '''
 
-from Compilers import \
-     compile_restricted, \
-     compile_restricted_function, \
-     compile_restricted_exec, \
-     compile_restricted_eval
-
+from SelectCompiler import *
 from PrintCollector import PrintCollector
 

=== Removed File Zope/lib/python/RestrictedPython/Compilers.py ===