[Zope-Checkins] CVS: Zope2 - RestrictionMutator.py:1.1.2.3

shane@digicool.com shane@digicool.com
Thu, 19 Apr 2001 18:50:11 -0400 (EDT)


Update of /cvs-repository/Zope2/lib/python/RestrictedPython
In directory korak:/tmp/cvs-serv5343

Modified Files:
      Tag: RestrictedPythonBranch
	RestrictionMutator.py 
Log Message:
Rearranged some more and added a compile() lookalike function.



--- Updated File RestrictionMutator.py in package Zope2 --
--- RestrictionMutator.py	2001/04/18 21:02:18	1.1.2.2
+++ RestrictionMutator.py	2001/04/19 22:50:11	1.1.2.3
@@ -1,9 +1,11 @@
 
 import string
-from compiler import ast
-from compiler.transformer import parse
+import MutatingWalker
+from compiler import ast, visitor, pycodegen
+from compiler.transformer import Transformer, parse
 from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
 
+
 def rmLineno(node):
     '''Strip lineno attributes from a code tree'''
     if node.__dict__.has_key('lineno'):
@@ -22,13 +24,13 @@
     '''Make a "clean" expression node'''
     return stmtNode(txt).expr
 
-_decl_globals_code = ast.Global(['_print_target_class', '_guards'])
-_prep_guards_code = stmtNode('_read_guard, _write_guard = _guards')
+_decl_globals_code = ast.Global(['_print_target_class', '_guard_init'])
+_prep_guards_code = stmtNode('_guard = _guard_init')
 _print_code = stmtNode('_print_target = _print_target_class()')
 _printed_expr = exprNode('_print_target()')
 _print_target_name = ast.Name('_print_target')
-_read_guard_name = ast.Name('_read_guard')
-_write_guard_name = ast.Name('_write_guard')
+_read_guard_name = ast.Name('_guard')
+_write_guard_name = ast.Name('_guard')
 
 
 class PrintCollector:
@@ -40,24 +42,27 @@
     def __call__(self):
         return string.join(self.txt, '')
 
-class RestrictionMutator:
+
+class FuncInfo:
     _print_used = 0
     _printed_used = 0
 
+
+class RestrictionMutator:
+    funcinfo = None
+
     def __init__(self):
-        self._print_stack = []
-        self.used_names = {}
         self.warnings = []
+        self.used_names = {}
 
     def raiseSyntaxError(self, node, info):
         lineno = getattr(node, 'lineno', None)
-        if lineno is not None:
+        if lineno is not None and lineno > 0:
             raise SyntaxError, ('Line %d: %s' % (lineno, info))
         else:
             raise SyntaxError, info
 
-    def checkName(self, node):
-        name = node.name
+    def checkName(self, node, name):
         if len(name) > 1 and name[0] == '_':
             # Note: "_" *is* allowed.
             self.raiseSyntaxError(
@@ -67,9 +72,9 @@
                 node, '"printed" is a reserved name.')
 
     def checkAttrName(self, node):
-        # This prevents access to protected attributes of wrappers
+        # This prevents access to protected attributes of guards
         # and is thus essential regardless of the security policy,
-        # unless we use cells...
+        # unless some other solution is devised.
         name = node.attrname
         if len(name) > 1 and name[0] == '_':
             # Note: "_" *is* allowed.
@@ -77,45 +82,52 @@
                 node, 'Attribute names starting with "_" are not allowed.')
 
     def visitFunction(self, node, walker):
-        self.checkName(node)
-        self._print_stack.append((self._print_used, self._printed_used))
-        self._print_used = 0
-        self._printed_used = 0
+        self.checkName(node, node.name)
+        for argname in node.argnames:
+            self.checkName(node, argname)
+        former_funcinfo = self.funcinfo
+        self.funcinfo = funcinfo = FuncInfo()
         node = walker.defaultVisitNode(node)
-        if self._print_used or self._printed_used:
+        if funcinfo._print_used or funcinfo._printed_used:
             # Add code at top of function for creating _print_target
             node.code.nodes.insert(0, _print_code)
-            if not self._printed_used:
+            if not funcinfo._printed_used:
                 self.warnings.append(
                     "Prints, but never reads 'printed' variable.")
-            elif not self._print_used:
+            elif not funcinfo._print_used:
                 self.warnings.append(
                     "Doesn't print, but reads 'printed' variable.")
         node.code.nodes[0:0] = [_decl_globals_code, _prep_guards_code]
-        self._print_used, self._printed_used = self._print_stack.pop()
+        self.funcinfo = former_funcinfo
         return node
 
+    def visitLambda(self, node, walker):
+        for argname in node.argnames:
+            self.checkName(node, argname)
+        return walker.defaultVisitNode(node)
+
     def visitPrint(self, node, walker):
         node = walker.defaultVisitNode(node)
-        if self._print_stack:
+        funcinfo = self.funcinfo
+        if funcinfo is not None:
             if node.dest is None:
-                self._print_used = 1
+                funcinfo._print_used = 1
                 node.dest = _print_target_name
         return node
 
     visitPrintnl = visitPrint
 
     def visitName(self, node, walker):
-        if node.name == 'printed' and self._print_stack:
+        if node.name == 'printed' and self.funcinfo is not None:
             # Replace name lookup with an expression.
-            self._printed_used = 1
+            self.funcinfo._printed_used = 1
             return _printed_expr
-        self.checkName(node)
+        self.checkName(node, node.name)
         self.used_names[node.name] = 1
         return node
 
     def visitAssName(self, node, walker):
-        self.checkName(node)
+        self.checkName(node, node.name)
         return node
 
     def visitGetattr(self, node, walker):
@@ -143,13 +155,66 @@
         return node
 
     def visitExec(self, node, walker):
-        raise SyntaxError, 'Exec statements are not allowed.'
+        self.raiseSyntaxError(node, 'Exec statements are not allowed.')
 
     def visitClass(self, node, walker):
-        self.checkName(node)
+        # Should classes be allowed at all??
+        self.checkName(node, node.name)
         return walker.defaultVisitNode(node)
 
 
+DEBUG = 1
+
+def compile_restricted(s, name, kind,
+                       used_names=None, warnings=None):
+    '''
+    Returns restricted compiled code.
+    '''
+    if DEBUG:
+        from time import clock
+        start = clock()
+
+    rm = RestrictionMutator()
+    if kind == 'exec':
+        tree = Transformer().parsesuite(s)
+        MutatingWalker.walk(tree, rm)
+        gen = pycodegen.NestedScopeModuleCodeGenerator(name)
+        visitor.walk(tree, gen)
+        code = gen.getCode()
+    elif kind == 'eval':
+        tree = Transformer().parseexpr(s)
+        MutatingWalker.walk(tree, rm)
+        # XXX No "NestedScopeEvalCodeGenerator" exists
+        # so here's a hack that gets around it.
+        gen = pycodegen.NestedScopeModuleCodeGenerator(name)
+        tree = ast.Module(None, ast.Stmt([ast.Assign([ast.AssName(
+            '__', OP_ASSIGN)], ast.Lambda((), (), 0, tree))]))
+        visitor.walk(tree, gen)
+        code = gen.getCode()
+        d = {}
+        exec code in d
+        code = d['__'].func_code
+    else:
+        raise ValueError, 'Unsupported compile kind: ' + kind
+
+    if used_names is not None:
+        # Fill with the names used.
+        used_names.update(rm.used_names)
+    if warnings is not None:
+        # Fill in warnings.
+        warnings.extend(rm.warnings)
+
+    if DEBUG:
+        end = clock()
+        print 'compile_restricted: %d ms for %s' % (
+            (end - start) * 1000, repr(s))
+##        import dis
+##        dis.dis(code)
+##        print `code.co_code`
+    return code
+
+
+
 class Noisy:
     '''Test guard class that babbles about accesses'''
     def __init__(self, _ob):
@@ -187,7 +252,6 @@
         print '__setslice__', `_ob`, lo, hi, value
         _ob[lo:hi] = value
 
-
 if __name__ == '__main__':
     tree = parse('''
 def f():
@@ -195,17 +259,13 @@
  print "... wOrLd!".lower()
  x = {}
  x['p'] = printed[1:-1]
- x['p'] += printed * 2
+ x['p'] += (lambda ob: ob * 2)(printed)
  return x['p']
 ''')
-    import MutatingWalker
-    from compiler.pycodegen import NestedScopeModuleCodeGenerator
-    from compiler import visitor
 
-    print tree
     MutatingWalker.walk(tree, RestrictionMutator())
     print tree
-    gen = NestedScopeModuleCodeGenerator('some_python_script')
+    gen = pycodegen.NestedScopeModuleCodeGenerator('some_python_script')
     visitor.walk(tree, gen, verbose=1)
     code = gen.getCode()
     dict = {'__builtins__': None}
@@ -213,7 +273,8 @@
     f = dict['f']
     f.func_globals.update({
         '_print_target_class': PrintCollector,
-        '_guards': (Noisy, Noisy),
+        '_guard_init': Noisy,
+        '_guard': Noisy,
         })
     print f()
     #import dis