Point to the one and only RestrictedPython we now have (the one that has been set free

as its own project, egg).
parent ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Restricted Python Expressions
$Id$
"""
__version__='$Revision: 1.6 $'[11:-2]
from RestrictedPython import compile_restricted_eval
from string import translate, strip
import string
nltosp = string.maketrans('\r\n',' ')
default_guarded_getattr = getattr # No restrictions.
def default_guarded_getitem(ob, index):
# No restrictions.
return ob[index]
PROFILE = 0
class RestrictionCapableEval:
"""A base class for restricted code."""
globals = {'__builtins__': None}
rcode = None # restricted
ucode = None # unrestricted
used = None
def __init__(self, expr):
"""Create a restricted expression
where:
expr -- a string containing the expression to be evaluated.
"""
expr = strip(expr)
self.__name__ = expr
expr = translate(expr, nltosp)
self.expr = expr
self.prepUnrestrictedCode() # Catch syntax errors.
def prepRestrictedCode(self):
if self.rcode is None:
if PROFILE:
from time import clock
start = clock()
co, err, warn, used = compile_restricted_eval(
self.expr, '<string>')
if PROFILE:
end = clock()
print 'prepRestrictedCode: %d ms for %s' % (
(end - start) * 1000, `self.expr`)
if err:
raise SyntaxError, err[0]
self.used = tuple(used.keys())
self.rcode = co
def prepUnrestrictedCode(self):
if self.ucode is None:
# Use the standard compiler.
co = compile(self.expr, '<string>', 'eval')
if self.used is None:
# Examine the code object, discovering which names
# the expression needs.
names=list(co.co_names)
used={}
i=0
code=co.co_code
l=len(code)
LOAD_NAME=101
HAVE_ARGUMENT=90
while(i < l):
c=ord(code[i])
if c==LOAD_NAME:
name=names[ord(code[i+1])+256*ord(code[i+2])]
used[name]=1
i=i+3
elif c >= HAVE_ARGUMENT: i=i+3
else: i=i+1
self.used=tuple(used.keys())
self.ucode=co
def eval(self, mapping):
# This default implementation is probably not very useful. :-(
# This is meant to be overridden.
self.prepRestrictedCode()
code = self.rcode
d = {'_getattr_': default_guarded_getattr,
'_getitem_': default_guarded_getitem}
d.update(self.globals)
has_key = d.has_key
for name in self.used:
try:
if not has_key(name):
d[name] = mapping[name]
except KeyError:
# Swallow KeyErrors since the expression
# might not actually need the name. If it
# does need the name, a NameError will occur.
pass
return eval(code, d)
def __call__(self, **kw):
return self.eval(kw)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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: 1.14 $'[11:-2]
import exceptions
# 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 ['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 _write_wrapper():
# Construct the write wrapper class
def _handler(secattr, error_msg):
# Make a class method.
def handler(self, *args):
try:
f = getattr(self.ob, secattr)
except AttributeError:
raise TypeError, error_msg
f(*args)
return handler
class Wrapper:
def __len__(self):
# Required for slices with negative bounds.
return len(self.ob)
def __init__(self, ob):
self.__dict__['ob'] = ob
__setitem__ = _handler('__guarded_setitem__',
'object does not support item or slice assignment')
__delitem__ = _handler('__guarded_delitem__',
'object does not support item or slice assignment')
__setattr__ = _handler('__guarded_setattr__',
'attribute-less object (assign or del)')
__delattr__ = _handler('__guarded_delattr__',
'attribute-less object (assign or del)')
return Wrapper
def _full_write_guard():
# Nested scope abuse!
# safetype and Wrapper variables are used by guard()
safetype = {dict: True, list: True}.has_key
Wrapper = _write_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'):
return ob
# Hand the object to the Wrapper instance, then return the instance.
return Wrapper(ob)
return guard
full_write_guard = _full_write_guard()
def guarded_setattr(object, name, value):
setattr(full_write_guard(object), name, value)
safe_builtins['setattr'] = guarded_setattr
def guarded_delattr(object, name):
delattr(full_write_guard(object), name)
safe_builtins['delattr'] = guarded_delattr
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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: 1.5 $'[11:-2]
limited_builtins = {}
def limited_range(iFirst, *args):
# limited range function from Martijn Pieters
RANGELIMIT = 1000
if not len(args):
iStart, iEnd, iStep = 0, iFirst, 1
elif len(args) == 1:
iStart, iEnd, iStep = iFirst, args[0], 1
elif len(args) == 2:
iStart, iEnd, iStep = iFirst, args[0], args[1]
else:
raise AttributeError, 'range() requires 1-3 int arguments'
if iStep == 0: raise ValueError, 'zero step for range()'
iLen = int((iEnd - iStart) / iStep)
if iLen < 0: iLen = 0
if iLen >= RANGELIMIT: raise ValueError, 'range() too large'
return range(iStart, iEnd, iStep)
limited_builtins['range'] = limited_range
def limited_list(seq):
if isinstance(seq, str):
raise TypeError, 'cannot convert string to list'
return list(seq)
limited_builtins['list'] = limited_list
def limited_tuple(seq):
if isinstance(seq, str):
raise TypeError, 'cannot convert string to tuple'
return tuple(seq)
limited_builtins['tuple'] = limited_tuple
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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: 1.6 $'[11:-2]
from SelectCompiler 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, exclude=None):
for name, child in node.__dict__.items():
if exclude is not None and name in exclude:
continue
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)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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: 1.4 $'[11:-2]
class PrintCollector:
'''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 ''.join(self.txt)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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: 1.6 $'[11:-2]
from compiler import ast, parse, misc, syntax, pycodegen
from compiler.pycodegen import AbstractCompileMode, Expression, \
Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp
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):
"""Abstract base class for hooking up custom CodeGenerator."""
# See concrete subclasses below.
def __init__(self, source, filename):
if source:
source = '\n'.join(source.splitlines())
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()
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
def compile(self):
tree = self._get_tree()
gen = self.CodeGeneratorClass(tree)
self.code = gen.getCode()
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, globalize=None):
"""Compiles a restricted code object for a function.
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)
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()
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
if body:
body = '\n'.join(body.splitlines())
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, if there are any nodes at all
if len(f.code.nodes) > 0:
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
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""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.
$Revision: 1.13 $
"""
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
# trees without affecting line numbers shown in tracebacks, etc.
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
# 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_")
_inplacevar_name = ast.Name("_inplacevar_")
# Constants.
_None_const = ast.Const(None)
_write_const = ast.Const("write")
_printed_expr = stmtNode("_print()").expr
_print_target_node = stmtNode("_print = _print_()")
class FuncInfo:
print_used = False
printed_used = False
class RestrictionMutator:
def __init__(self):
self.warnings = []
self.errors = []
self.used_names = {}
self.funcinfo = FuncInfo()
def error(self, node, info):
"""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))
else:
self.errors.append(info)
def checkName(self, node, name):
"""Verifies that a name being assigned is safe.
This is to prevent people from doing things like:
__metatype__ = mytype (opens up metaclasses, a big unknown
in terms of security)
__path__ = foo (could this confuse the import machinery?)
_getattr = somefunc (not very useful, but could open a hole)
Note that assigning a variable is not the only way to assign
a name. def _badname, class _badname, import foo as _badname,
and perhaps other statements assign names. Special case:
'_' is allowed.
"""
if name.startswith("_") and name != "_":
# Note: "_" *is* allowed.
self.error(node, '"%s" is an invalid variable name because'
' it starts with "_"' % name)
if name.endswith('__roles__'):
self.error(node, '"%s" is an invalid variable name because '
'it ends with "__roles__".' % name)
if name == "printed":
self.error(node, '"printed" is a reserved name.')
def checkAttrName(self, node):
"""Verifies that an attribute name does not start with _.
As long as guards (security proxies) have underscored names,
this underscore protection is important regardless of the
security policy. Special case: '_' is allowed.
"""
name = node.attrname
if name.startswith("_") and name != "_":
# Note: "_" *is* allowed.
self.error(node, '"%s" is an invalid attribute name '
'because it starts with "_".' % name)
if name.endswith('__roles__'):
self.error(node, '"%s" is an invalid attribute name '
'because it ends with "__roles__".' % name)
def prepBody(self, body):
"""Insert code for print at the beginning of the code suite."""
if self.funcinfo.print_used or self.funcinfo.printed_used:
# Add code at top for creating _print_target
body.insert(0, _print_target_node)
if not self.funcinfo.printed_used:
self.warnings.append(
"Prints, but never reads 'printed' variable.")
elif not self.funcinfo.print_used:
self.warnings.append(
"Doesn't print, but reads 'printed' variable.")
def visitFunction(self, node, walker):
"""Checks and mutates a function definition.
Checks the name of the function and the argument names using
checkName(). It also calls prepBody() to prepend code to the
beginning of the code suite.
"""
self.checkName(node, node.name)
for argname in node.argnames:
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()
node = walker.defaultVisitNode(node, exclude=('defaults',))
self.prepBody(node.code.nodes)
self.funcinfo = former_funcinfo
return node
def visitLambda(self, node, walker):
"""Checks and mutates an anonymous function definition.
Checks the argument names using checkName(). It also calls
prepBody() to prepend code to the beginning of the code suite.
"""
for argname in node.argnames:
self.checkName(node, argname)
return walker.defaultVisitNode(node)
def visitPrint(self, node, walker):
"""Checks and mutates a print statement.
Adds a target to all print statements. 'print foo' becomes
'print >> _print, foo', where _print is the default print
target defined for this scope.
Alternatively, if the untrusted code provides its own target,
we have to check the 'write' method of the target.
'print >> ob, foo' becomes
'print >> (_getattr(ob, 'write') and ob), foo'.
Otherwise, it would be possible to call the write method of
templates and scripts; 'write' happens to be the name of the
method that changes them.
"""
node = walker.defaultVisitNode(node)
self.funcinfo.print_used = True
if node.dest is None:
node.dest = _print_target_name
else:
# Pre-validate access to the "write" attribute.
# "print >> ob, x" becomes
# "print >> (_getattr(ob, 'write') and ob), x"
node.dest = ast.And([
ast.CallFunc(_getattr_name, [node.dest, _write_const]),
node.dest])
return node
visitPrintnl = visitPrint
def visitName(self, node, walker):
"""Prevents access to protected names as defined by checkName().
Also converts use of the name 'printed' to an expression.
"""
if node.name == 'printed':
# Replace name lookup with an expression.
self.funcinfo.printed_used = True
return _printed_expr
self.checkName(node, node.name)
self.used_names[node.name] = True
return node
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.
#
# Also for list comprehensions:
# [... 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 visitGenExprFor(self, node, walker):
# convert
# (... for x in expr ...)
# to
# (... for x in _getiter(expr) ...)
node = walker.defaultVisitNode(node)
node.iter = ast.CallFunc(_getiter_name, [node.iter])
return node
def visitGetattr(self, node, walker):
"""Converts attribute access to a function call.
'foo.bar' becomes '_getattr(foo, "bar")'.
Also prevents augmented assignment of attributes, which would
be difficult to support correctly.
"""
self.checkAttrName(node)
node = walker.defaultVisitNode(node)
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.')
return ast.CallFunc(_getattr_name,
[node.expr, ast.Const(node.attrname)])
def visitSubscript(self, node, walker):
"""Checks all kinds of subscripts.
'foo[bar] += baz' is disallowed.
'a = foo[bar, baz]' becomes 'a = _getitem(foo, (bar, baz))'.
'a = foo[bar]' becomes 'a = _getitem(foo, bar)'.
'a = foo[bar:baz]' becomes 'a = _getitem(foo, slice(bar, baz))'.
'a = foo[:baz]' becomes 'a = _getitem(foo, slice(None, baz))'.
'a = foo[bar:]' becomes 'a = _getitem(foo, slice(bar, None))'.
'del foo[bar]' becomes 'del _write(foo)[bar]'.
'foo[bar] = a' becomes '_write(foo)[bar] = a'.
The _write function returns a security proxy.
"""
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', 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.')
if hasattr(node, 'subs'):
# Subscript.
subs = node.subs
if len(subs) > 1:
# example: ob[1,2]
subs = ast.Tuple(subs)
else:
# example: ob[1]
subs = subs[0]
else:
# Slice.
# example: obj[0:2]
lower = node.lower
if lower is None:
lower = _None_const
upper = node.upper
if upper is None:
upper = _None_const
subs = ast.Sliceobj([lower, upper])
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_name, [node.expr])
return node
visitSlice = visitSubscript
def visitAssAttr(self, node, walker):
"""Checks and mutates attribute assignment.
'a.b = c' becomes '_write(a).b = c'.
The _write function returns a security proxy.
"""
self.checkAttrName(node)
node = walker.defaultVisitNode(node)
node.expr = ast.CallFunc(_write_name, [node.expr])
return node
def visitExec(self, node, walker):
self.error(node, 'Exec statements are not allowed.')
def visitYield(self, node, walker):
self.error(node, 'Yield statements are not allowed.')
def visitClass(self, node, walker):
"""Checks the name of a class using checkName().
Should classes be allowed at all? They don't cause security
issues, but they aren't very useful either since untrusted
code can't assign instance attributes.
"""
self.checkName(node, node.name)
return walker.defaultVisitNode(node)
def visitModule(self, node, walker):
"""Adds prep code at module scope.
Zope doesn't make use of this. The body of Python scripts is
always at function scope.
"""
node = walker.defaultVisitNode(node)
self.prepBody(node.node.nodes)
return node
def visitAugAssign(self, node, walker):
"""Makes a note that augmented assignment is in use.
Note that although augmented assignment of attributes and
subscripts is disallowed, augmented assignment of names (such
as 'n += 1') is allowed.
This could be a problem if untrusted code got access to a
mutable database object that supports augmented assignment.
"""
if node.node.__class__.__name__ == 'Name':
node = walker.defaultVisitNode(node)
newnode = ast.Assign(
[ast.AssName(node.node.name, OP_ASSIGN)],
ast.CallFunc(
_inplacevar_name,
[ast.Const(node.op),
ast.Name(node.node.name),
node.expr,
]
),
)
newnode.lineno = node.lineno
return newnode
else:
node.node.in_aug_assign = True
return walker.defaultVisitNode(node)
def visitImport(self, node, walker):
"""Checks names imported using checkName()."""
for name, asname in node.names:
self.checkName(node, name)
if asname:
self.checkName(node, asname)
return node
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__doc__='''Read-only Mapping class based on MultiMapping
$Id$'''
__version__='$Revision: 1.3 $'[11:-2]
from MultiMapping import MultiMapping
_marker = []
class SafeMapping(MultiMapping):
'''Mapping with security declarations and limited method exposure.
Since it subclasses MultiMapping, this class can be used to wrap
one or more mapping objects. Restricted Python code will not be
able to mutate the SafeMapping or the wrapped mappings, but will be
able to read any value.
'''
__allow_access_to_unprotected_subobjects__ = 1
push = pop = None
def _push(self, ob):
MultiMapping.push(self, ob)
def _pop(self, *args):
if args:
return apply(MultiMapping.pop, (self,) + args)
else:
return MultiMapping.pop(self)
def has_get(self, key):
v = self.get(key, _marker)
if v is _marker:
return 0, None
else:
return 1, v
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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$
"""
# Use the compiler from the standard library.
import compiler
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
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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: 1.7 $'[11:-2]
import string, math, random
import DocumentTemplate.sequence
from DateTime.DateTime import DateTime
import sets
utility_builtins = {}
utility_builtins['string'] = string
utility_builtins['math'] = math
utility_builtins['random'] = random
utility_builtins['sequence'] = DocumentTemplate.sequence
utility_builtins['DateTime'] = DateTime
utility_builtins['sets'] = sets
def same_type(arg1, *args):
'''Compares the class or type of two or more objects.'''
t = getattr(arg1, '__class__', type(arg1))
for arg in args:
if getattr(arg, '__class__', type(arg)) is not t:
return 0
return 1
utility_builtins['same_type'] = same_type
def test(*args):
l=len(args)
for i in range(1, l, 2):
if args[i-1]: return args[i]
if l%2: return args[-1]
utility_builtins['test'] = test
def reorder(s, with=None, without=()):
# s, with, and without are sequences treated as sets.
# The result is subtract(intersect(s, with), without),
# unless with is None, in which case it is subtract(s, without).
if with is None: with=s
d={}
tt=type(())
for i in s:
if type(i) is tt and len(i)==2: k, v = i
else: k= v = i
d[k]=v
r=[]
a=r.append
h=d.has_key
for i in without:
if type(i) is tt and len(i)==2: k, v = i
else: k= v = i
if h(k): del d[k]
for i in with:
if type(i) is tt and len(i)==2: k, v = i
else: k= v = i
if h(k):
a((k,d[k]))
del d[k]
return r
utility_builtins['reorder'] = reorder
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
'''
RestrictedPython package.
$Id$
'''
from SelectCompiler import *
from PrintCollector import PrintCollector
How it works
============
Every time I see this code, I have to relearn it. These notes will
hopefully make this a little easier. :)
- The important module is RCompile. The entry points are the
compile_restricted_* functions.
+ compile_restricted_function is used by Python scripts.
+ compile_restricted_eval is used by ZPT
and by DTML indirectly through Eval.RestrictionCapableEval.
- OK, so lets see how this works by following the logic of
compile_restricted_eval.
- First, we create an RExpression, passing the source and a
"file name", to be used in tracebacks.
Now, an RExpression is just:
+ a subclass of RestrictedCompileMode and Expression.
Expression is a subclass of AbstractCompileMode that sets it's
mode to 'eval' and everided compile. Sigh.
+ RestrictedCompileMode is a subclass of AbstractCompileMode
that changes a bunch of things. :) These include compile, so we
can ignore the compile we got from Expression. It would have
been simpler to just set the dang mode in RExpression. Sigh.
RestrictedCompileMode seem to be the interestng base class. I
assume it implements the interesting functionality. We'll see
below...
- Next, we call compileAndTuplize.
+ This calls compile on the RExpression. It has an error
handler that does something that I hope I don't care about. :)
+ It then calls the genCode method on the RExpression. This is
boring, so we'll not worry about it.
- The compile method provided by RestrictedCompileMode is
interesting.
+ First it calls _get_tree.
* It uses compiler.parse to parse the source
* it uses MutatingWalker.walk to mutaate the tree using the
RestrictedCompileMode's 'rm' attr, which is a
RestrictionMutator.
The RestrictionMutator has the recipies for mutating the parse
tree. (Note, for comparison, that Zope3's
zope.security.untrustedpython.rcompile module an alternative
RestrictionMutator that provides a much smaller set of
changes.)
A mutator has visit method for different kinds of AST
nodes. These visit methods may mutate nodes or return new
nodes that replace the originally visited nodes. There is a
default visitor that visits a node's children and replaces the
children who's visitors returned new nodes.
The walk function just calls the visitor for the root node of
the given tree. Note _get_tree ignores the walk return value,
thus assuming that the visitor for the root node doesn't
return a new node. This is a theoretical bug that we can
ignore.
+ Second, it generates the code. This too is boring.
- So this seems simple enough. ;) When we want to add a check, we
need to update or add a visit function in RestrictionMutator.
How does a visit function work.
- First, we usually call walker.defaultVisitNode(node). This
transforms the node's child nodes.
- Then we hack the node, or possibly return the node. To do this, we
have to know how the node works.
- The hack often involved changing the code to call some checker
function. These have names like _name_. These are names that
would be illegal in the input source.
If this is a new function, we have to provide it in
AccessControl.ZopeGuards._safe_globals.
- Don't forget to add a test case to tests.before_and_after.
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""Restricted Python transformation examples
This module contains pairs of functions. Each pair has a before and an
after function. The after function shows the source code equivalent
of the before function after it has been modified by the restricted
compiler.
These examples are actually used in the testRestrictions.py
checkBeforeAndAfter() unit tests, which verifies that the restricted compiler
actually produces the same output as would be output by the normal compiler
for the after function.
$Id$
"""
# getattr
def simple_getattr_before(x):
return x.y
def simple_getattr_after(x):
return _getattr_(x, 'y')
# set attr
def simple_setattr_before():
x.y = "bar"
def simple_setattr_after():
_write_(x).y = "bar"
# for loop and list comprehensions
def simple_forloop_before(x):
for x in [1, 2, 3]:
pass
def simple_forloop_after(x):
for x in _getiter_([1, 2, 3]):
pass
def nested_forloop_before(x):
for x in [1, 2, 3]:
for y in "abc":
pass
def nested_forloop_after(x):
for x in _getiter_([1, 2, 3]):
for y in _getiter_("abc"):
pass
def simple_list_comprehension_before():
x = [y**2 for y in whatever if y > 3]
def simple_list_comprehension_after():
x = [y**2 for y in _getiter_(whatever) if y > 3]
def nested_list_comprehension_before():
x = [x**2 + y**2 for x in whatever1 if x >= 0
for y in whatever2 if y >= x]
def nested_list_comprehension_after():
x = [x**2 + y**2 for x in _getiter_(whatever1) if x >= 0
for y in _getiter_(whatever2) if y >= x]
# print
def simple_print_before():
print "foo"
def simple_print_after():
_print = _print_()
print >> _print, "foo"
# getitem
def simple_getitem_before():
return x[0]
def simple_getitem_after():
return _getitem_(x, 0)
def simple_get_tuple_key_before():
x = y[1,2]
def simple_get_tuple_key_after():
x = _getitem_(y, (1,2))
# set item
def simple_setitem_before():
x[0] = "bar"
def simple_setitem_after():
_write_(x)[0] = "bar"
# delitem
def simple_delitem_before():
del x[0]
def simple_delitem_after():
del _write_(x)[0]
# a collection of function parallels to many of the above
def function_with_print_before():
def foo():
print "foo"
return printed
def function_with_print_after():
def foo():
_print = _print_()
print >> _print, "foo"
return _print()
def function_with_getattr_before():
def foo():
return x.y
def function_with_getattr_after():
def foo():
return _getattr_(x, 'y')
def function_with_setattr_before():
def foo(x):
x.y = "bar"
def function_with_setattr_after():
def foo(x):
_write_(x).y = "bar"
def function_with_getitem_before():
def foo(x):
return x[0]
def function_with_getitem_after():
def foo(x):
return _getitem_(x, 0)
def function_with_forloop_before():
def foo():
for x in [1, 2, 3]:
pass
def function_with_forloop_after():
def foo():
for x in _getiter_([1, 2, 3]):
pass
# this, and all slices, won't work in these tests because the before code
# parses the slice as a slice object, while the after code can't generate a
# slice object in this way. The after code as written below
# is parsed as a call to the 'slice' name, not as a slice object.
# XXX solutions?
#def simple_slice_before():
# x = y[:4]
#def simple_slice_after():
# _getitem = _getitem_
# x = _getitem(y, slice(None, 4))
# Assignment stmts in Python can be very complicated. The "no_unpack"
# test makes sure we're not doing unnecessary rewriting.
def no_unpack_before():
x = y
x = [y]
x = y,
x = (y, (y, y), [y, (y,)], x, (x, y))
x = y = z = (x, y, z)
no_unpack_after = no_unpack_before # that is, should be untouched
# apply() variations. Native apply() is unsafe because, e.g.,
#
# def f(a, b, c):
# whatever
#
# apply(f, two_element_sequence, dict_with_key_c)
#
# or (different spelling of the same thing)
#
# f(*two_element_sequence, **dict_with_key_c)
#
# makes the elements of two_element_sequence visible to f via its 'a' and
# 'b' arguments, and the dict_with_key_c['c'] value visible via its 'c'
# argument. That is, it's a devious way to extract values without going
# thru security checks.
def star_call_before():
foo(*a)
def star_call_after():
_apply_(foo, *a)
def star_call_2_before():
foo(0, *a)
def star_call_2_after():
_apply_(foo, 0, *a)
def starstar_call_before():
foo(**d)
def starstar_call_after():
_apply_(foo, **d)
def star_and_starstar_call_before():
foo(*a, **d)
def star_and_starstar_call_after():
_apply_(foo, *a, **d)
def positional_and_star_and_starstar_call_before():
foo(b, *a, **d)
def positional_and_star_and_starstar_call_after():
_apply_(foo, b, *a, **d)
def positional_and_defaults_and_star_and_starstar_call_before():
foo(b, x=y, w=z, *a, **d)
def positional_and_defaults_and_star_and_starstar_call_after():
_apply_(foo, b, x=y, w=z, *a, **d)
def lambda_with_getattr_in_defaults_before():
f = lambda x=y.z: x
def lambda_with_getattr_in_defaults_after():
f = lambda x=_getattr_(y, "z"): x
# augmented operators
# Note that we don't have to worry about item, attr, or slice assignment,
# as they are disallowed. Yay!
## def inplace_id_add_before():
## x += y+z
## def inplace_id_add_after():
## x = _inplacevar_('+=', x, y+z)
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""Restricted Python transformation examples
This module contains pairs of functions. Each pair has a before and an
after function. The after function shows the source code equivalent
of the before function after it has been modified by the restricted
compiler.
These examples are actually used in the testRestrictions.py
checkBeforeAndAfter() unit tests, which verifies that the restricted compiler
actually produces the same output as would be output by the normal compiler
for the after function.
$Id$
"""
def simple_generator_expression_before():
x = (y**2 for y in whatever if y > 3)
def simple_generator_expression_after():
x = (y**2 for y in _getiter_(whatever) if y > 3)
def nested_generator_expression_before():
x = (x**2 + y**2 for x in whatever1 if x >= 0
for y in whatever2 if y >= x)
def nested_generator_expression_after():
x = (x**2 + y**2 for x in _getiter_(whatever1) if x >= 0
for y in _getiter_(whatever2) if y >= x)
class MyClass:
def set(self, val):
self.state = val
def get(self):
return self.state
x = MyClass()
x.set(12)
x.set(x.get() + 1)
if x.get() != 13:
raise AssertionError, "expected 13, got %d" % x.get()
f = lambda x, y=1: x + y
if f(2) != 3:
raise ValueError
if f(2, 2) != 4:
raise ValueError
from __future__ import nested_scopes
import sys
def print0():
print 'Hello, world!',
return printed
def print1():
print 'Hello,',
print 'world!',
return printed
def printStuff():
print 'a', 'b', 'c',
return printed
def printToNone():
x = None
print >>x, 'Hello, world!',
return printed
def printLines():
# This failed before Zope 2.4.0a2
r = range(3)
for n in r:
for m in r:
print m + n * len(r),
print
return printed
def try_map():
inc = lambda i: i+1
x = [1, 2, 3]
print map(inc, x),
return printed
def try_apply():
def f(x, y, z):
return x + y + z
print f(*(300, 20), **{'z': 1}),
return printed
def try_inplace():
x = 1
x += 3
def primes():
# Somewhat obfuscated code on purpose
print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,20))),
return printed
def allowed_read(ob):
print ob.allowed
print ob.s
print ob[0]
print ob[2]
print ob[3:-1]
print len(ob)
return printed
def allowed_default_args(ob):
def f(a=ob.allowed, s=ob.s):
return a, s
def allowed_simple():
q = {'x':'a'}
q['y'] = 'b'
q.update({'z': 'c'})
r = ['a']
r.append('b')
r[2:2] = ['c']
s = 'a'
s = s[:100] + 'b'
s += 'c'
if sys.version_info >= (2, 3):
t = ['l', 'm', 'n', 'o', 'p', 'q']
t[1:5:2] = ['n', 'p']
_ = q
return q['x'] + q['y'] + q['z'] + r[0] + r[1] + r[2] + s
def allowed_write(ob):
ob.writeable = 1
#ob.writeable += 1
[1 for ob.writeable in 1,2]
ob['safe'] = 2
#ob['safe'] += 2
[1 for ob['safe'] in 1,2]
def denied_print(ob):
print >> ob, 'Hello, world!',
def denied_getattr(ob):
#ob.disallowed += 1
ob.disallowed = 1
return ob.disallowed
def denied_default_args(ob):
def f(d=ob.disallowed):
return d
def denied_setattr(ob):
ob.allowed = -1
def denied_setattr2(ob):
#ob.allowed += -1
ob.allowed = -1
def denied_setattr3(ob):
[1 for ob.allowed in 1,2]
def denied_getitem(ob):
ob[1]
def denied_getitem2(ob):
#ob[1] += 1
ob[1]
def denied_setitem(ob):
ob['x'] = 2
def denied_setitem2(ob):
#ob[0] += 2
ob['x'] = 2
def denied_setitem3(ob):
[1 for ob['x'] in 1,2]
def denied_setslice(ob):
ob[0:1] = 'a'
def denied_setslice2(ob):
#ob[0:1] += 'a'
ob[0:1] = 'a'
def denied_setslice3(ob):
[1 for ob[0:1] in 1,2]
##def strange_attribute():
## # If a guard has attributes with names that don't start with an
## # underscore, those attributes appear to be an attribute of
## # anything.
## return [].attribute_of_anything
def order_of_operations():
return 3 * 4 * -2 + 2 * 12
def rot13(ss):
mapping = {}
orda = ord('a')
ordA = ord('A')
for n in range(13):
c1 = chr(orda + n)
c2 = chr(orda + n + 13)
c3 = chr(ordA + n)
c4 = chr(ordA + n + 13)
mapping[c1] = c2
mapping[c2] = c1
mapping[c3] = c4
mapping[c4] = c3
del c1, c2, c3, c4, orda, ordA
res = ''
for c in ss:
res = res + mapping.get(c, c)
return res
def nested_scopes_1():
# Fails if 'a' is consumed by the first function.
a = 1
def f1():
return a
def f2():
return a
return f1() + f2()
class Classic:
pass
# These are all supposed to raise a SyntaxError when using
# compile_restricted() but not when using compile().
# Each function in this module is compiled using compile_restricted().
from __future__ import generators
def overrideGuardWithFunction():
def _getattr(o): return o
def overrideGuardWithLambda():
lambda o, _getattr=None: o
def overrideGuardWithClass():
class _getattr:
pass
def overrideGuardWithName():
_getattr = None
def overrideGuardWithArgument():
def f(_getattr=None):
pass
def reserved_names():
printed = ''
def bad_name():
__ = 12
def bad_attr():
some_ob._some_attr = 15
def no_exec():
exec 'q = 1'
def no_yield():
yield 42
def check_getattr_in_lambda(arg=lambda _getattr=(lambda ob, name: name):
_getattr):
42
def import_as_bad_name():
import os as _leading_underscore
def except_using_bad_name():
try:
foo
except NameError, _leading_underscore:
# The name of choice (say, _write) is now assigned to an exception
# object. Hard to exploit, but conceivable.
pass
def keyword_arg_with_bad_name():
def f(okname=1, __badname=2):
pass
def no_augmeneted_assignment_to_sub():
a[b] += c
def no_augmeneted_assignment_to_attr():
a.b += c
def no_augmeneted_assignment_to_slice():
a[x:y] += c
def no_augmeneted_assignment_to_slice2():
a[x:y:z] += c
import os
import re
import sys
import unittest
# Note that nothing should be imported from AccessControl, and in particular
# nothing from ZopeGuards.py. Transformed code may need several wrappers
# in order to run at all, and most of the production wrappers are defined
# in ZopeGuards. But RestrictedPython isn't supposed to depend on
# AccessControl, so we need to define throwaway wrapper implementations
# here instead.
from RestrictedPython import compile_restricted, PrintCollector
from RestrictedPython.Eval import RestrictionCapableEval
from RestrictedPython.tests import restricted_module, verify
from RestrictedPython.RCompile import RModule, RFunction
try:
__file__
except NameError:
__file__ = os.path.abspath(sys.argv[1])
_FILEPATH = os.path.abspath( __file__ )
_HERE = os.path.dirname( _FILEPATH )
def _getindent(line):
"""Returns the indentation level of the given line."""
indent = 0
for c in line:
if c == ' ': indent = indent + 1
elif c == '\t': indent = indent + 8
else: break
return indent
def find_source(fn, func):
"""Given a func_code object, this function tries to find and return
the python source code of the function. Originally written by
Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)"""
f = open(fn,"r")
for i in range(func.co_firstlineno):
line = f.readline()
ind = _getindent(line)
msg = ""
while line:
msg = msg + line
line = f.readline()
# the following should be <= ind, but then we get
# confused by multiline docstrings. Using == works most of
# the time... but not always!
if _getindent(line) == ind: break
f.close()
return fn, msg
def get_source(func):
"""Less silly interface to find_source"""
file = func.func_globals['__file__']
if file.endswith('.pyc'):
file = file[:-1]
source = find_source(file, func.func_code)[1]
assert source.strip(), "Source should not be empty!"
return source
def create_rmodule():
global rmodule
fn = os.path.join(_HERE, 'restricted_module.py')
f = open(fn, 'r')
source = f.read()
f.close()
# Sanity check
compile(source, fn, 'exec')
# Now compile it for real
code = compile_restricted(source, fn, 'exec')
rmodule = {'__builtins__':{'__import__':__import__, 'None':None,
'__name__': 'restricted_module'}}
builtins = getattr(__builtins__, '__dict__', __builtins__)
for name in ('map', 'reduce', 'int', 'pow', 'range', 'filter',
'len', 'chr', 'ord',
):
rmodule[name] = builtins[name]
exec code in rmodule
class AccessDenied (Exception): pass
DisallowedObject = []
class RestrictedObject:
disallowed = DisallowedObject
allowed = 1
_ = 2
__ = 3
_some_attr = 4
__some_other_attr__ = 5
s = 'Another day, another test...'
__writeable_attrs__ = ('writeable',)
def __getitem__(self, idx):
if idx == 'protected':
raise AccessDenied
elif idx == 0 or idx == 'safe':
return 1
elif idx == 1:
return DisallowedObject
else:
return self.s[idx]
def __getslice__(self, lo, hi):
return self.s[lo:hi]
def __len__(self):
return len(self.s)
def __setitem__(self, idx, v):
if idx == 'safe':
self.safe = v
else:
raise AccessDenied
def __setslice__(self, lo, hi, value):
raise AccessDenied
write = DisallowedObject
def guarded_getattr(ob, name):
v = getattr(ob, name)
if v is DisallowedObject:
raise AccessDenied
return v
SliceType = type(slice(0))
def guarded_getitem(ob, index):
if type(index) is SliceType and index.step is None:
start = index.start
stop = index.stop
if start is None:
start = 0
if stop is None:
v = ob[start:]
else:
v = ob[start:stop]
else:
v = ob[index]
if v is DisallowedObject:
raise AccessDenied
return v
def minimal_import(name, _globals, _locals, names):
if name != "__future__":
raise ValueError, "Only future imports are allowed"
import __future__
return __future__
class TestGuard:
'''A guard class'''
def __init__(self, _ob, write=None):
self.__dict__['_ob'] = _ob
# Write guard methods
def __setattr__(self, name, value):
_ob = self.__dict__['_ob']
writeable = getattr(_ob, '__writeable_attrs__', ())
if name not in writeable:
raise AccessDenied
if name[:5] == 'func_':
raise AccessDenied
setattr(_ob, name, value)
def __setitem__(self, index, value):
_ob = self.__dict__['_ob']
_ob[index] = value
def __setslice__(self, lo, hi, value):
_ob = self.__dict__['_ob']
_ob[lo:hi] = value
# A wrapper for _apply_.
apply_wrapper_called = []
def apply_wrapper(func, *args, **kws):
apply_wrapper_called.append('yes')
return func(*args, **kws)
inplacevar_wrapper_called = {}
def inplacevar_wrapper(op, x, y):
inplacevar_wrapper_called[op] = x, y
# This is really lame. But it's just a test. :)
globs = {'x': x, 'y': y}
exec 'x'+op+'y' in globs
return globs['x']
class RestrictionTests(unittest.TestCase):
def execFunc(self, name, *args, **kw):
func = rmodule[name]
verify.verify(func.func_code)
func.func_globals.update({'_getattr_': guarded_getattr,
'_getitem_': guarded_getitem,
'_write_': TestGuard,
'_print_': PrintCollector,
# I don't want to write something as involved as ZopeGuard's
# SafeIter just for these tests. Using the builtin list() function
# worked OK for everything the tests did at the time this was added,
# but may fail in the future. If Python 2.1 is no longer an
# interesting platform then, using 2.2's builtin iter() here should
# work for everything.
'_getiter_': list,
'_apply_': apply_wrapper,
'_inplacevar_': inplacevar_wrapper,
})
return func(*args, **kw)
def checkPrint(self):
for i in range(2):
res = self.execFunc('print%s' % i)
self.assertEqual(res, 'Hello, world!')
def checkPrintToNone(self):
try:
res = self.execFunc('printToNone')
except AttributeError:
# Passed. "None" has no "write" attribute.
pass
else:
self.fail(0, res)
def checkPrintStuff(self):
res = self.execFunc('printStuff')
self.assertEqual(res, 'a b c')
def checkPrintLines(self):
res = self.execFunc('printLines')
self.assertEqual(res, '0 1 2\n3 4 5\n6 7 8\n')
def checkPrimes(self):
res = self.execFunc('primes')
self.assertEqual(res, '[2, 3, 5, 7, 11, 13, 17, 19]')
def checkAllowedSimple(self):
res = self.execFunc('allowed_simple')
self.assertEqual(res, 'abcabcabc')
def checkAllowedRead(self):
self.execFunc('allowed_read', RestrictedObject())
def checkAllowedWrite(self):
self.execFunc('allowed_write', RestrictedObject())
def checkAllowedArgs(self):
self.execFunc('allowed_default_args', RestrictedObject())
def checkTryMap(self):
res = self.execFunc('try_map')
self.assertEqual(res, "[2, 3, 4]")
def checkApply(self):
del apply_wrapper_called[:]
res = self.execFunc('try_apply')
self.assertEqual(apply_wrapper_called, ["yes"])
self.assertEqual(res, "321")
def checkInplace(self):
inplacevar_wrapper_called.clear()
res = self.execFunc('try_inplace')
self.assertEqual(inplacevar_wrapper_called['+='], (1, 3))
def checkDenied(self):
for k in rmodule.keys():
if k[:6] == 'denied':
try:
self.execFunc(k, RestrictedObject())
except AccessDenied:
# Passed the test
pass
else:
self.fail('%s() did not trip security' % k)
def checkSyntaxSecurity(self):
# Ensures that each of the functions in security_in_syntax.py
# throws a SyntaxError when using compile_restricted.
fn = os.path.join(_HERE, 'security_in_syntax.py')
f = open(fn, 'r')
source = f.read()
f.close()
# Unrestricted compile.
code = compile(source, fn, 'exec')
m = {'__builtins__': {'__import__':minimal_import}}
exec code in m
for k, v in m.items():
if hasattr(v, 'func_code'):
filename, source = find_source(fn, v.func_code)
source = "from __future__ import generators\n\n" + source
# Now compile it with restrictions
try:
code = compile_restricted(source, filename, 'exec')
except SyntaxError:
# Passed the test.
pass
else:
self.fail('%s should not have compiled' % k)
def checkOrderOfOperations(self):
res = self.execFunc('order_of_operations')
self.assertEqual(res, 0)
def checkRot13(self):
res = self.execFunc('rot13', 'Zope is k00l')
self.assertEqual(res, 'Mbcr vf x00y')
def checkNestedScopes1(self):
res = self.execFunc('nested_scopes_1')
self.assertEqual(res, 2)
def checkUnrestrictedEval(self):
expr = RestrictionCapableEval("{'a':[m.pop()]}['a'] + [m[0]]")
v = [12, 34]
expect = v[:]
expect.reverse()
res = expr.eval({'m':v})
self.assertEqual(res, expect)
v = [12, 34]
res = expr(m=v)
self.assertEqual(res, expect)
def checkStackSize(self):
for k, rfunc in rmodule.items():
if not k.startswith('_') and hasattr(rfunc, 'func_code'):
rss = rfunc.func_code.co_stacksize
ss = getattr(restricted_module, k).func_code.co_stacksize
self.failUnless(
rss >= ss, 'The stack size estimate for %s() '
'should have been at least %d, but was only %d'
% (k, ss, rss))
def checkBeforeAndAfter(self):
from RestrictedPython.RCompile import RModule
from RestrictedPython.tests import before_and_after
from compiler import parse
defre = re.compile(r'def ([_A-Za-z0-9]+)_(after|before)\(')
beforel = [name for name in before_and_after.__dict__
if name.endswith("_before")]
for name in beforel:
before = getattr(before_and_after, name)
before_src = get_source(before)
before_src = re.sub(defre, r'def \1(', before_src)
rm = RModule(before_src, '')
tree_before = rm._get_tree()
after = getattr(before_and_after, name[:-6]+'after')
after_src = get_source(after)
after_src = re.sub(defre, r'def \1(', after_src)
tree_after = parse(after_src)
self.assertEqual(str(tree_before), str(tree_after))
rm.compile()
verify.verify(rm.getCode())
if sys.version_info[:2] >= (2, 4):
def checkBeforeAndAfter24(self):
from RestrictedPython.RCompile import RModule
from RestrictedPython.tests import before_and_after24
from compiler import parse
defre = re.compile(r'def ([_A-Za-z0-9]+)_(after|before)\(')
beforel = [name for name in before_and_after24.__dict__
if name.endswith("_before")]
for name in beforel:
before = getattr(before_and_after24, name)
before_src = get_source(before)
before_src = re.sub(defre, r'def \1(', before_src)
rm = RModule(before_src, '')
tree_before = rm._get_tree()
after = getattr(before_and_after24, name[:-6]+'after')
after_src = get_source(after)
after_src = re.sub(defre, r'def \1(', after_src)
tree_after = parse(after_src)
self.assertEqual(str(tree_before), str(tree_after))
rm.compile()
verify.verify(rm.getCode())
def _compile_file(self, name):
path = os.path.join(_HERE, name)
f = open(path, "r")
source = f.read()
f.close()
co = compile_restricted(source, path, "exec")
verify.verify(co)
return co
def checkUnpackSequence(self):
co = self._compile_file("unpack.py")
calls = []
def getiter(seq):
calls.append(seq)
return list(seq)
globals = {"_getiter_": getiter, '_inplacevar_': inplacevar_wrapper}
exec co in globals, {}
# The comparison here depends on the exact code that is
# contained in unpack.py.
# The test doing implicit unpacking in an "except:" clause is
# a pain, because there are two levels of unpacking, and the top
# level is unpacking the specific TypeError instance constructed
# by the test. We have to worm around that one.
ineffable = "a TypeError instance"
expected = [[1, 2],
(1, 2),
"12",
[1],
[1, [2, 3], 4],
[2, 3],
(1, (2, 3), 4),
(2, 3),
[1, 2, 3],
2,
('a', 'b'),
((1, 2), (3, 4)), (1, 2),
((1, 2), (3, 4)), (3, 4),
ineffable, [42, 666],
[[0, 1], [2, 3], [4, 5]], [0, 1], [2, 3], [4, 5],
([[[1, 2]]], [[[3, 4]]]), [[[1, 2]]], [[1, 2]], [1, 2],
[[[3, 4]]], [[3, 4]], [3, 4],
]
i = expected.index(ineffable)
self.assert_(isinstance(calls[i], TypeError))
expected[i] = calls[i]
self.assertEqual(calls, expected)
def checkUnpackSequenceExpression(self):
co = compile_restricted("[x for x, y in [(1, 2)]]", "<string>", "eval")
verify.verify(co)
calls = []
def getiter(s):
calls.append(s)
return list(s)
globals = {"_getiter_": getiter}
exec co in globals, {}
self.assertEqual(calls, [[(1,2)], (1, 2)])
def checkUnpackSequenceSingle(self):
co = compile_restricted("x, y = 1, 2", "<string>", "single")
verify.verify(co)
calls = []
def getiter(s):
calls.append(s)
return list(s)
globals = {"_getiter_": getiter}
exec co in globals, {}
self.assertEqual(calls, [(1, 2)])
def checkClass(self):
getattr_calls = []
setattr_calls = []
def test_getattr(obj, attr):
getattr_calls.append(attr)
return getattr(obj, attr)
def test_setattr(obj):
setattr_calls.append(obj.__class__.__name__)
return obj
co = self._compile_file("class.py")
globals = {"_getattr_": test_getattr,
"_write_": test_setattr,
}
exec co in globals, {}
# Note that the getattr calls don't correspond to the method call
# order, because the x.set method is fetched before its arguments
# are evaluated.
self.assertEqual(getattr_calls,
["set", "set", "get", "state", "get", "state"])
self.assertEqual(setattr_calls, ["MyClass", "MyClass"])
def checkLambda(self):
co = self._compile_file("lambda.py")
exec co in {}, {}
def checkEmpty(self):
rf = RFunction("", "", "issue945", "empty.py", {})
rf.parse()
rf2 = RFunction("", "# still empty\n\n# by", "issue945", "empty.py", {})
rf2.parse()
def checkSyntaxError(self):
err = ("def f(x, y):\n"
" if x, y < 2 + 1:\n"
" return x + y\n"
" else:\n"
" return x - y\n")
self.assertRaises(SyntaxError,
compile_restricted, err, "<string>", "exec")
# these two tests check that source code with Windows line
# endings still works.
def checkLineEndingsRFunction(self):
from RestrictedPython.RCompile import RFunction
gen = RFunction(
p='',
body='# testing\r\nprint "testing"\r\nreturn printed\n',
name='test',
filename='<test>',
globals=(),
)
gen.mode = 'exec'
# if the source has any line ending other than \n by the time
# parse() is called, then you'll get a syntax error.
gen.parse()
def checkLineEndingsRestrictedCompileMode(self):
from RestrictedPython.RCompile import RestrictedCompileMode
gen = RestrictedCompileMode(
'# testing\r\nprint "testing"\r\nreturn printed\n',
'<testing>'
)
gen.mode='exec'
# if the source has any line ending other than \n by the time
# parse() is called, then you'll get a syntax error.
gen.parse()
create_rmodule()
def test_suite():
return unittest.makeSuite(RestrictionTests, 'check')
if __name__=='__main__':
unittest.main(defaultTest="test_suite")
# A series of short tests for unpacking sequences.
def u1(L):
x, y = L
assert x == 1
assert y == 2
u1([1,2])
u1((1, 2))
def u1a(L):
x, y = L
assert x == '1'
assert y == '2'
u1a("12")
try:
u1([1])
except ValueError:
pass
else:
raise AssertionError, "expected 'unpack list of wrong size'"
def u2(L):
x, (a, b), y = L
assert x == 1
assert a == 2
assert b == 3
assert y == 4
u2([1, [2, 3], 4])
u2((1, (2, 3), 4))
try:
u2([1, 2, 3])
except TypeError:
pass
else:
raise AssertionError, "expected 'iteration over non-sequence'"
def u3((x, y)):
assert x == 'a'
assert y == 'b'
return x, y
u3(('a', 'b'))
def u4(x):
(a, b), c = d, (e, f) = x
assert a == 1 and b == 2 and c == (3, 4)
assert d == (1, 2) and e == 3 and f == 4
u4( ((1, 2), (3, 4)) )
def u5(x):
try:
raise TypeError(x)
# This one is tricky to test, because the first level of unpacking
# has a TypeError instance. That's a headache for the test driver.
except TypeError, [(a, b)]:
assert a == 42
assert b == 666
u5([42, 666])
def u6(x):
expected = 0
for i, j in x:
assert i == expected
expected += 1
assert j == expected
expected += 1
u6([[0, 1], [2, 3], [4, 5]])
def u7(x):
stuff = [i + j for toplevel, in x for i, j in toplevel]
assert stuff == [3, 7]
u7( ([[[1, 2]]], [[[3, 4]]]) )
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Verify simple properties of bytecode.
Some of the transformations performed by the RestrictionMutator are
tricky. This module checks the generated bytecode as a way to verify
the correctness of the transformations. Violations of some
restrictions are obvious from inspection of the bytecode. For
example, the bytecode should never contain a LOAD_ATTR call, because
all attribute access is performed via the _getattr_() checker
function.
"""
import dis
import types
def verify(code):
"""Verify all code objects reachable from code.
In particular, traverse into contained code objects in the
co_consts table.
"""
verifycode(code)
for ob in code.co_consts:
if isinstance(ob, types.CodeType):
verify(ob)
def verifycode(code):
try:
_verifycode(code)
except:
dis.dis(code)
raise
def _verifycode(code):
line = code.co_firstlineno
# keep a window of the last three opcodes, with the most recent first
window = (None, None, None)
for op in disassemble(code):
if op.line is not None:
line = op.line
if op.opname.endswith("LOAD_ATTR"):
# All the user code that generates LOAD_ATTR should be
# rewritten, but the code generated for a list comp
# includes a LOAD_ATTR to extract the append method.
if not (op.arg == "append" and
window[0].opname == "DUP_TOP" and
window[1].opname == "BUILD_LIST"):
raise ValueError("direct attribute access %s: %s, %s:%d"
% (op.opname, op.arg, co.co_filename, line))
if op.opname in ("STORE_ATTR", "DEL_ATTR"):
if not (window[0].opname == "CALL_FUNCTION" and
window[2].opname == "LOAD_GLOBAL" and
window[2].arg == "_write_"):
# check that arg is appropriately wrapped
for i, op in enumerate(window):
print i, op.opname, op.arg
raise ValueError("unguard attribute set/del at %s:%d"
% (code.co_filename, line))
if op.opname.startswith("UNPACK"):
# An UNPACK opcode extracts items from iterables, and that's
# unsafe. The restricted compiler doesn't remove UNPACK opcodes,
# but rather *inserts* a call to _getiter_() before each, and
# that's the pattern we need to see.
if not (window[0].opname == "CALL_FUNCTION" and
window[1].opname == "ROT_TWO" and
window[2].opname == "LOAD_GLOBAL" and
window[2].arg == "_getiter_"):
raise ValueError("unguarded unpack sequence at %s:%d" %
(code.co_filename, line))
# should check CALL_FUNCTION_{VAR,KW,VAR_KW} but that would
# require a potentially unlimited history. need to refactor
# the "window" before I can do that.
if op.opname == "LOAD_SUBSCR":
raise ValueError("unguarded index of sequence at %s:%d" %
(code.co_filename, line))
window = (op,) + window[:2]
class Op(object):
__slots__ = (
"opname", # string, name of the opcode
"argcode", # int, the number of the argument
"arg", # any, the object, name, or value of argcode
"line", # int, line number or None
"target", # boolean, is this op the target of a jump
"pos", # int, offset in the bytecode
)
def __init__(self, opcode, pos):
self.opname = dis.opname[opcode]
self.arg = None
self.line = None
self.target = False
self.pos = pos
def disassemble(co, lasti=-1):
code = co.co_code
labels = dis.findlabels(code)
linestarts = dict(findlinestarts(co))
n = len(code)
i = 0
extended_arg = 0
free = co.co_cellvars + co.co_freevars
while i < n:
op = ord(code[i])
o = Op(op, i)
i += 1
if i in linestarts and i > 0:
o.line = linestarts[i]
if i in labels:
o.target = True
if op > dis.HAVE_ARGUMENT:
arg = ord(code[i]) + ord(code[i+1]) * 256 + extended_arg
extended_arg = 0
i += 2
if op == dis.EXTENDED_ARG:
extended_arg = arg << 16
o.argcode = arg
if op in dis.hasconst:
o.arg = co.co_consts[arg]
elif op in dis.hasname:
o.arg = co.co_names[arg]
elif op in dis.hasjrel:
o.arg = i + arg
elif op in dis.haslocal:
o.arg = co.co_varnames[arg]
elif op in dis.hascompare:
o.arg = dis.cmp_op[arg]
elif op in dis.hasfree:
o.arg = free[arg]
yield o
# findlinestarts is copied from Python 2.4's dis module. The code
# didn't exist in 2.3, but it would be painful to code disassemble()
# without it.
def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source.
Generate pairs (offset, lineno) as described in Python/compile.c.
"""
byte_increments = [ord(c) for c in code.co_lnotab[0::2]]
line_increments = [ord(c) for c in code.co_lnotab[1::2]]
lastlineno = None
lineno = code.co_firstlineno
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
if byte_incr:
if lineno != lastlineno:
yield (addr, lineno)
lastlineno = lineno
addr += byte_incr
lineno += line_incr
if lineno != lastlineno:
yield (addr, lineno)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment