[Zope-Checkins] CVS: Zope2 - Eval.py:1.1.2.2 RestrictionMutator.py:1.1.2.6
evan@serenade.digicool.com
evan@serenade.digicool.com
Tue, 24 Apr 2001 20:56:31 -0400
Update of /cvs-repository/Zope2/lib/python/RestrictedPython
In directory serenade.digicool.com:/home/evan/Zope/rp/lib/python/RestrictedPython
Modified Files:
Tag: RestrictedPythonBranch
Eval.py RestrictionMutator.py
Log Message:
Split up compile_restricted, added error handling.
Only load guards if they are used.
Handle printing and guards at module-level.
--- Updated File Eval.py in package Zope2 --
--- Eval.py 2001/04/19 22:50:59 1.1.2.1
+++ Eval.py 2001/04/25 00:56:31 1.1.2.2
@@ -93,24 +93,6 @@
nltosp = string.maketrans('\r\n',' ')
-##def careful_mul(env, *factors):
-## # r = result (product of all factors)
-## # c = count (product of all non-sequence factors)
-## # s flags whether any of the factors is a sequence
-## r=c=1
-## s=None
-## for factor in factors:
-## try:
-## l=len(factor)
-## s=1
-## except TypeError:
-## c=c*factor
-## if s and c > 1000:
-## raise TypeError, \
-## 'Illegal sequence repeat (too many repetitions: %d)' % c
-## r=r*factor
-## return r
-
default_globals={
'__builtins__': {},
'_guard': (lambda ob: ob),
@@ -134,15 +116,14 @@
if compile_restricted is None:
# Late binding because this will import the whole
# compiler suite.
- from RestrictionMutator import compile_restricted
+ from RestrictionMutator import compile_restricted_eval
expr = strip(expr)
self.__name__ = expr
expr = translate(expr, nltosp)
self.expr = expr
self.globals = globals
- used = {}
- self.code = compile_restricted(expr, '<string>', 'eval', used)
+ self.code, err, warn, used = compile_restricted_eval(expr, '<string>')
self.used = tuple(used.keys())
def eval(self, mapping):
--- Updated File RestrictionMutator.py in package Zope2 --
--- RestrictionMutator.py 2001/04/24 22:46:06 1.1.2.5
+++ RestrictionMutator.py 2001/04/25 00:56:31 1.1.2.6
@@ -1,11 +1,12 @@
-import string
+import sys
import MutatingWalker
from compiler import ast, visitor, pycodegen
from compiler.transformer import Transformer, parse
from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
+from parser import ParserError
+from traceback import format_exception_only
-
def rmLineno(node):
'''Strip lineno attributes from a code tree'''
if node.__dict__.has_key('lineno'):
@@ -23,14 +24,18 @@
def exprNode(txt):
'''Make a "clean" expression node'''
return stmtNode(txt).expr
+
+_prep_code = {}
+for _n in ('read', 'write'):
+ _prep_code[_n] = [ast.Global(['_%s_guard_' % _n]),
+ stmtNode('_%s_guard = _%s_guard_' % (_n, _n))]
+_prep_code['print'] = [ast.Global(['_print_target_class']),
+ stmtNode('_print_target = _print_target_class()')]
-_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('_guard')
-_write_guard_name = ast.Name('_guard')
+_read_guard_name = ast.Name('_read_guard')
+_write_guard_name = ast.Name('_write_guard')
class PrintCollector:
@@ -40,12 +45,14 @@
def write(self, text):
self.txt.append(text)
def __call__(self):
- return string.join(self.txt, '')
+ return ''.join(self.txt)
class FuncInfo:
_print_used = 0
_printed_used = 0
+ _read_used = 0
+ _write_used = 0
class RestrictionMutator:
@@ -53,23 +60,23 @@
def __init__(self):
self.warnings = []
+ self.errors = []
self.used_names = {}
- def raiseSyntaxError(self, node, info):
+ def error(self, node, info):
lineno = getattr(node, 'lineno', None)
if lineno is not None and lineno > 0:
- raise SyntaxError, ('Line %d: %s' % (lineno, info))
+ self.errors.append('Line %d: %s' % (lineno, info))
else:
- raise SyntaxError, info
+ self.errors.append(info)
def checkName(self, node, name):
if len(name) > 1 and name[0] == '_':
# Note: "_" *is* allowed.
- self.raiseSyntaxError(
- node, 'Names starting with "_" are not allowed.')
+ self.error(node, '"%s" is an invalid variable name, because'
+ ' it starts with "_"' % name)
if name == 'printed':
- self.raiseSyntaxError(
- node, '"printed" is a reserved name.')
+ self.error(node, '"printed" is a reserved name.')
def checkAttrName(self, node):
# This prevents access to protected attributes of guards
@@ -78,26 +85,34 @@
name = node.attrname
if len(name) > 1 and name[0] == '_':
# Note: "_" *is* allowed.
- self.raiseSyntaxError(
- node, 'Attribute names starting with "_" are not allowed.')
+ self.error(node, '"%s" is an invalid attribute name, '
+ 'because it starts with "_".' % name)
+
+ def prepBody(self, body):
+ info = self.funcinfo
+ if info._print_used or info._printed_used:
+ # Add code at top for creating _print_target
+ body[0:0] = _prep_code['print']
+ if not info._printed_used:
+ self.warnings.append(
+ "Prints, but never reads 'printed' variable.")
+ elif not info._print_used:
+ self.warnings.append(
+ "Doesn't print, but reads 'printed' variable.")
+ if info._read_used:
+ body[0:0] = _prep_code['read']
+ if info._write_used:
+ body[0:0] = _prep_code['write']
def visitFunction(self, node, walker):
self.checkName(node, node.name)
for argname in node.argnames:
self.checkName(node, argname)
+
former_funcinfo = self.funcinfo
- self.funcinfo = funcinfo = FuncInfo()
+ self.funcinfo = FuncInfo()
node = walker.defaultVisitNode(node)
- 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 funcinfo._printed_used:
- self.warnings.append(
- "Prints, but never reads 'printed' variable.")
- 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.prepBody(node.code.nodes)
self.funcinfo = former_funcinfo
return node
@@ -108,17 +123,15 @@
def visitPrint(self, node, walker):
node = walker.defaultVisitNode(node)
- funcinfo = self.funcinfo
- if funcinfo is not None:
- if node.dest is None:
- funcinfo._print_used = 1
- node.dest = _print_target_name
+ if node.dest is None:
+ self.funcinfo._print_used = 1
+ node.dest = _print_target_name
return node
visitPrintnl = visitPrint
def visitName(self, node, walker):
- if node.name == 'printed' and self.funcinfo is not None:
+ if node.name == 'printed':
# Replace name lookup with an expression.
self.funcinfo._printed_used = 1
return _printed_expr
@@ -134,6 +147,7 @@
self.checkAttrName(node)
node = walker.defaultVisitNode(node)
node.expr = ast.CallFunc(_read_guard_name, [node.expr])
+ self.funcinfo._read_used = 1
return node
def visitSubscript(self, node, walker):
@@ -141,9 +155,11 @@
if node.flags == OP_APPLY:
# get subscript or slice
node.expr = ast.CallFunc(_read_guard_name, [node.expr])
+ self.funcinfo._read_used = 1
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
return node
visitSlice = visitSubscript
@@ -152,65 +168,127 @@
self.checkAttrName(node)
node = walker.defaultVisitNode(node)
node.expr = ast.CallFunc(_write_guard_name, [node.expr])
+ self.funcinfo._write_used = 1
return node
def visitExec(self, node, walker):
- self.raiseSyntaxError(node, 'Exec statements are not allowed.')
+ self.error(node, 'Exec statements are not allowed.')
def visitClass(self, node, walker):
# Should classes be allowed at all??
self.checkName(node, node.name)
return walker.defaultVisitNode(node)
+ def visitModule(self, node, walker):
+ self.funcinfo = FuncInfo()
+ node = walker.defaultVisitNode(node)
+ self.prepBody(node.node.nodes)
+ return node
-DEBUG = 0
+def getSyntaxError(source, mode):
+ try:
+ compile(source, '<string>', mode)
+ except SyntaxError:
+ err = format_exception_only(SyntaxError, sys.exc_info()[1])
+ else:
+ err = ['Unknown parser error.']
+ return None, err, [], {}
-def compile_restricted(s, name, kind,
- used_names=None, warnings=None):
- '''
- Returns restricted compiled code.
+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)
+
+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)
+ code = gen.getCode().co_consts[1]
+ return code, None, rm.warnings, rm.used_names
+
+def compile_restricted_exec(s, filename='<string>'):
+ '''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
+ gen = pycodegen.NestedScopeModuleCodeGenerator(filename)
+ visitor.walk(tree, gen)
+ return gen.getCode(), None, rm.warnings, rm.used_names
+
+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(), None, rm.warnings, rm.used_names
+
+DEBUG = 1
+def compile_restricted(source, filename, mode):
+ '''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 "EvalCodeGenerator" exists
- # so here's a hack that gets around it.
- gen = pycodegen.ModuleCodeGenerator(name)
- gen.emit('SET_LINENO', 0)
- visitor.walk(tree, gen)
- gen.emit('RETURN_VALUE')
- code = gen.getCode()
+ if mode == 'eval':
+ r = compile_restricted_eval(source, filename)
+ elif mode == 'exec':
+ r = compile_restricted_exec(source, filename)
else:
- raise ValueError, 'Unsupported compile kind: ' + kind
+ raise ValueError, "compile_restricted() arg 3 must be 'exec' or 'eval'"
- 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
-
-
+ return r
class Noisy:
'''Test guard class that babbles about accesses'''
@@ -270,8 +348,8 @@
f = dict['f']
f.func_globals.update({
'_print_target_class': PrintCollector,
- '_guard_init': Noisy,
- '_guard': Noisy,
+ '_read_guard_': Noisy,
+ '_write_guard_': Noisy,
})
print f()
#import dis