[Zope-Checkins] CVS: Zope2 - MutatingWalker.py:1.1.2.1 RestrictionMutator.py:1.1.2.1

shane@digicool.com shane@digicool.com
Wed, 18 Apr 2001 14:45:43 -0400 (EDT)


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

Added Files:
      Tag: RestrictedPythonBranch
	MutatingWalker.py RestrictionMutator.py 
Log Message:
Moved from Packages/RestrictedPython.



--- Added File MutatingWalker.py in package Zope2 ---

from compiler import ast

ListType = type([])
TupleType = type(())
SequenceTypes = (ListType, TupleType)

class MutatingWalker:

    def __init__(self, visitor):
        self.visitor = visitor
        self._cache = {}

    def defaultVisitNode(self, node, walker=None):
        for name, child in node.__dict__.items():
            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)


--- Added File RestrictionMutator.py in package Zope2 ---

import string
from compiler import ast
from compiler.transformer import parse

def rmLineno(node):
    '''Strip lineno attributes from a code tree'''
    if node.__dict__.has_key('lineno'):
        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

def exprNode(txt):
    '''Make a "clean" expression node'''
    return stmtNode(txt).expr

_decl_globals = ast.Global(['_print_target_class', '_read_guard',
                            '_write_guard'])
_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')


class PrintRedirector:
    '''Collect written text, and return it when called.'''
    def __init__(self):
        self.txt = []
    def write(self, text):
        self.txt.append(text)
    def __call__(self):
        return string.join(self.txt, '')

class RestrictionMutator:
    _print_used = 0
    _printed_used = 0

    def __init__(self):
        self._print_stack = []
        self.used_names = {}
        self.warnings = []

    def raiseSyntaxError(self, node, info):
        lineno = getattr(node, 'lineno', None)
        if lineno is not None:
            raise SyntaxError, ('Line %d: %s' % (lineno, info))
        else:
            raise SyntaxError, info

    def checkName(self, node):
        name = node.name
        if len(name) > 1 and name[0] == '_':
            # Note: "_" *is* allowed.
            self.raiseSyntaxError(
                node, 'Names starting with "_" are not allowed.')
        if name == 'printed':
            self.raiseSyntaxError(
                node, '"printed" is a reserved name.')

    def checkAttrName(self, node):
        # This prevents access to protected attributes of wrappers
        # and is thus essential regardless of the security policy,
        # unless we use cells...
        name = node.attrname
        if len(name) > 1 and name[0] == '_':
            # Note: "_" *is* allowed.
            self.raiseSyntaxError(
                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
        node = walker.defaultVisitNode(node)
        if self._print_used or self._printed_used:
            # Add code at top of function for creating _print_target
            node.code.nodes.insert(0, _print_code)
            if not self._printed_used:
                self.warnings.append(
                    "Prints, but never reads 'printed' variable.")
            elif not self._print_used:
                self.warnings.append(
                    "Doesn't print, but reads 'printed' variable.")
        node.code.nodes.insert(0, _decl_globals)
        self._print_used, self._printed_used = self._print_stack.pop()
        return node

    def visitPrint(self, node, walker):
        node = walker.defaultVisitNode(node)
        if self._print_stack:
            if node.dest is None:
                self._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:
            # Replace name lookup with an expression.
            self._printed_used = 1
            return _printed_expr
        self.checkName(node)
        self.used_names[node.name] = 1
        return node

    def visitAssName(self, node, walker):
        self.checkName(node)
        return node

    def visitGetattr(self, node, walker):
        self.checkAttrName(node)
        node = walker.defaultVisitNode(node)
        node.expr = ast.CallFunc(_read_guard_name, [node.expr])
        return node

    def visitSubscript(self, node, walker):
        node = walker.defaultVisitNode(node)
        if node.flags == 'OP_APPLY':
            node.expr = ast.CallFunc(_read_guard_name, [node.expr])
        elif node.flags in ('OP_DELETE', 'OP_ASSIGN'):
            node.expr = ast.CallFunc(_write_guard_name, [node.expr])
        return node

    visitSlice = visitSubscript

    def visitAssAttr(self, node, walker):
        self.checkAttrName(node)
        node = walker.defaultVisitNode(node)
        node.expr = ast.CallFunc(_write_guard_name, [node.expr])
        return node

    def visitExec(self, node, walker):
        raise SyntaxError, 'Exec statements are not allowed.'

    def visitClass(self, node, walker):
        self.checkName(node)
        return walker.defaultVisitNode(node)


class Noisy:
    '''Test class that babbles about accesses'''
    def __init__(self, _ob):
        self.__dict__['_ob'] = _ob
    # Read guard methods
    def __len__(self):
        _ob = self.__dict__['_ob']
        return len(_ob)
    def __getattr__(self, name):
        _ob = self.__dict__['_ob']
        print '__getattr__', `_ob`, name
        return getattr(_ob, name)
    def __getitem__(self, index):
        _ob = self.__dict__['_ob']
        print '__getitem__', `_ob`, index
        return _ob[index]
    def __getslice__(self, lo, hi):
        _ob = self.__dict__['_ob']
        print '__getslice__', `_ob`, lo, hi
        return _ob[lo:hi]
    # Write guard methods
    def __setattr__(self, name, value):
        _ob = self.__dict__['_ob']
        print '__setattr__', `_ob`, name, value
        setattr(_ob, name, value)
    def __setitem__(self, index, value):
        _ob = self.__dict__['_ob']
        print '__setitem__', `_ob`, index, value
        _ob[index] = value
    def __setslice__(self, lo, hi, value):
        _ob = self.__dict__['_ob']
        print '__setslice__', `_ob`, lo, hi, value
        _ob[lo:hi] = value


if __name__ == '__main__':
    tree = parse('''
def f():
 print "Hello",
 print "... wOrLd!".lower()
 x = {}
 x['p'] = printed[1:-1]
 x['p'] += printed * 2
 return x['p']
''')
    import MutatingWalker
    from compiler.pycodegen import NestedScopeModuleCodeGenerator
    from compiler import visitor

    MutatingWalker.walk(tree, RestrictionMutator())
    print tree
    gen = NestedScopeModuleCodeGenerator('some_python_script')
    visitor.walk(tree, gen, verbose=1)
    code = gen.getCode()
    dict = {'__builtins__': None}
    exec code in dict
    f = dict['f']
    f.func_globals.update({
        '_print_target_class': PrintRedirector,
        '_read_guard': Noisy,
        '_write_guard': Noisy,
        })
    print f()
    #import dis
    #dis.dis(f.func_code)

    # Test: f can't access f.func_globals (otherwise it can override
    #       _*_guard)
    # Test: no access to builtin getattr.  getattr(guarded_ob, '_ob')
    # Test: no access to globals()