Commit 1e456686 authored by Boxiang Sun's avatar Boxiang Sun

Original implementation.

parents
Changes
=======
3.6.0 (2010-07-09)
------------------
- Added name check for names assigned during imports using the
"from x import y" format.
- Added test for name check when assigning an alias using multiple-context with
statements in Python 2.7.
- Added tests for protection of the iterators for dict and set comprehensions
in Python 2.7.
3.6.0a1 (2010-06-05)
--------------------
- Removed support for DocumentTemplate.sequence - this is handled in the
DocumentTemplate package itself.
3.5.2 (2010-04-30)
------------------
- Removed a testing dependency on zope.testing.
3.5.1 (2009-03-17)
------------------
- Added tests for ``Utilities`` module.
- Filtered DeprecationWarnings when importing Python's ``sets`` module.
3.5.0 (2009-02-09)
------------------
- Dropped legacy support for Python 2.1 / 2.2 (``__future__`` imports
of ``nested_scopes`` / ``generators``.).
3.4.3 (2008-10-26)
------------------
- Fixed deprecation warning: ``with`` is now a reserved keyword on
Python 2.6. That means RestrictedPython should run on Python 2.6
now. Thanks to Ranjith Kannikara, GSoC Student for the patch.
- Added tests for ternary if expression and for 'with' keyword and
context managers.
3.4.2 (2007-07-28)
------------------
- Changed homepage URL to the CheeseShop site
- Greatly improved README.txt
3.4.1 (2007-06-23)
------------------
- Fixed http://www.zope.org/Collectors/Zope/2295: Bare conditional in
a Zope 2 PythonScript followed by a comment causes SyntaxError.
3.4.0 (2007-06-04)
------------------
- RestrictedPython now has its own release cycle as a separate egg.
- Synchronized with RestrictedPython from Zope 2 tree.
3.2.0 (2006-01-05)
------------------
- Corresponds to the verison of the RestrictedPython package shipped
as part of the Zope 3.2.0 release.
- No changes from 3.1.0.
3.1.0 (2005-10-03)
------------------
- Corresponds to the verison of the RestrictedPython package shipped
as part of the Zope 3.1.0 release.
- Removed unused fossil module, ``SafeMapping``.
- Replaced use of deprecated 'whrandom' module with 'random' (aliased
to 'whrandom' for backward compatibility).
3.0.0 (2004-11-07)
------------------
- Corresponds to the verison of the RestrictedPython package shipped
as part of the Zope X3.0.0 release.
Zope Foundation and Contributors
\ No newline at end of file
Zope Public License (ZPL) Version 2.1
A copyright notice accompanies this license document that identifies the
copyright holders.
This license has been certified as open source. It has also been designated as
GPL compatible by the Free Software Foundation (FSF).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions in source code must retain the accompanying copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the accompanying copyright
notice, this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Names of the copyright holders must not be used to endorse or promote
products derived from this software without prior written permission from the
copyright holders.
4. The right to distribute this software or to use it for any purpose does not
give you the right to use Servicemarks (sm) or Trademarks (tm) of the
copyright
holders. Use of them is covered by separate agreement with the copyright
holders.
5. If any files are modified, you must cause the modified files to carry
prominent notices stating that you changed the files and the date of any
change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This diff is collapsed.
Please refer to src/RestrictedPython/README.txt.
##############################################################################
#
# Copyright (c) 2006 Zope Foundation 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.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
"""
import os, shutil, sys, tempfile, urllib2
from optparse import OptionParser
tmpeggs = tempfile.mkdtemp()
is_jython = sys.platform.startswith('java')
# parsing arguments
parser = OptionParser()
parser.add_option("-v", "--version", dest="version",
help="use a specific zc.buildout version")
parser.add_option("-d", "--distribute",
action="store_true", dest="distribute", default=False,
help="Use Disribute rather than Setuptools.")
parser.add_option("-c", None, action="store", dest="config_file",
help=("Specify the path to the buildout configuration "
"file to be used."))
options, args = parser.parse_args()
# if -c was provided, we push it back into args for buildout' main function
if options.config_file is not None:
args += ['-c', options.config_file]
if options.version is not None:
VERSION = '==%s' % options.version
else:
VERSION = ''
USE_DISTRIBUTE = options.distribute
args = args + ['bootstrap']
to_reload = False
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
to_reload = True
raise ImportError
except ImportError:
ez = {}
if USE_DISTRIBUTE:
exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
else:
exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
if to_reload:
reload(pkg_resources)
else:
import pkg_resources
if sys.platform == 'win32':
def quote(c):
if ' ' in c:
return '"%s"' % c # work around spawn lamosity on windows
else:
return c
else:
def quote (c):
return c
cmd = 'from setuptools.command.easy_install import main; main()'
ws = pkg_resources.working_set
if USE_DISTRIBUTE:
requirement = 'distribute'
else:
requirement = 'setuptools'
if is_jython:
import subprocess
assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
quote(tmpeggs), 'zc.buildout' + VERSION],
env=dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse(requirement)).location
),
).wait() == 0
else:
assert os.spawnle(
os.P_WAIT, sys.executable, quote (sys.executable),
'-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse(requirement)).location
),
) == 0
ws.add_entry(tmpeggs)
ws.require('zc.buildout' + VERSION)
import zc.buildout.buildout
zc.buildout.buildout.main(args)
shutil.rmtree(tmpeggs)
[buildout]
develop = .
parts = interpreter test
[interpreter]
recipe = zc.recipe.egg
interpreter = python
eggs = RestrictedPython
[test]
recipe = zc.recipe.testrunner
eggs = RestrictedPython
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
##############################################################################
#
# Copyright (c) 2006 Zope Foundation 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.
#
##############################################################################
"""Setup for RestrictedPython package
"""
import os
from setuptools import setup, find_packages
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(name='RestrictedPython',
version='3.6.0',
url='http://pypi.python.org/pypi/RestrictedPython',
license='ZPL 2.1',
description='RestrictedPython provides a restricted execution '
'environment for Python, e.g. for running untrusted code.',
author='Zope Foundation and Contributors',
author_email='zope-dev@zope.org',
long_description=(read('src', 'RestrictedPython', 'README.txt')
+ '\n' +
read('CHANGES.txt')),
packages = find_packages('src'),
package_dir = {'': 'src'},
install_requires = ['setuptools'],
include_package_data = True,
zip_safe = False,
)
This diff is collapsed.
CHANGES.txt
COPYRIGHT.txt
LICENSE.txt
README.txt
bootstrap.py
buildout.cfg
setup.py
src/RestrictedPython/Eval.py
src/RestrictedPython/Guards.py
src/RestrictedPython/Limits.py
src/RestrictedPython/MutatingWalker.py
src/RestrictedPython/PrintCollector.py
src/RestrictedPython/RCompile.py
src/RestrictedPython/README.txt
src/RestrictedPython/RestrictionMutator.py
src/RestrictedPython/SelectCompiler.py
src/RestrictedPython/Utilities.py
src/RestrictedPython/__init__.py
src/RestrictedPython/notes.txt
src/RestrictedPython.egg-info/PKG-INFO
src/RestrictedPython.egg-info/SOURCES.txt
src/RestrictedPython.egg-info/dependency_links.txt
src/RestrictedPython.egg-info/not-zip-safe
src/RestrictedPython.egg-info/requires.txt
src/RestrictedPython.egg-info/top_level.txt
src/RestrictedPython/tests/__init__.py
src/RestrictedPython/tests/before_and_after.py
src/RestrictedPython/tests/before_and_after24.py
src/RestrictedPython/tests/before_and_after25.py
src/RestrictedPython/tests/before_and_after26.py
src/RestrictedPython/tests/before_and_after27.py
src/RestrictedPython/tests/class.py
src/RestrictedPython/tests/lambda.py
src/RestrictedPython/tests/restricted_module.py
src/RestrictedPython/tests/security_in_syntax.py
src/RestrictedPython/tests/security_in_syntax26.py
src/RestrictedPython/tests/security_in_syntax27.py
src/RestrictedPython/tests/testCompile.py
src/RestrictedPython/tests/testREADME.py
src/RestrictedPython/tests/testRestrictions.py
src/RestrictedPython/tests/testUtiliities.py
src/RestrictedPython/tests/unpack.py
src/RestrictedPython/tests/verify.py
\ No newline at end of file
setuptools
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# 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
"""
__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 Foundation and Contributors.
#
# 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
# all
# any
# 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
# bytes
# bytearray
# classmethod
# coerce
# eval
# intern
# memoryview
# 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 Foundation and Contributors.
#
# 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 Foundation and Contributors.
#
# 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 Foundation and Contributors.
#
# 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 Foundation and Contributors.
#
# 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):
if isinstance(source, unicode):
# Use the utf-8-sig BOM so the compiler
# detects this as a UTF-8 encoded string.
source = '\xef\xbb\xbf' + source.encode('utf-8')
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()) + '\n'
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()) + '\n'
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
.. contents::
Overview
========
RestrictedPython provides a ``restricted_compile`` function that works
like the built-in ``compile`` function, except that it allows the
controlled and restricted execution of code:
>>> src = '''
... def hello_world():
... return "Hello World!"
... '''
>>> from RestrictedPython import compile_restricted
>>> code = compile_restricted(src, '<string>', 'exec')
The resulting code can be executed using the ``exec`` built-in:
>>> exec(code)
As a result, the ``hello_world`` function is now available in the
global namespace:
>>> hello_world()
'Hello World!'
Compatibility
=============
This release of RestrictedPython is compatible with Python 2.3, 2.4, 2.5, 2.6,
and 2.7.
Implementing a policy
=====================
RestrictedPython only provides the raw material for restricted
execution. To actually enforce any restrictions, you need to supply a
policy implementation by providing restricted versions of ``print``,
``getattr``, ``setattr``, ``import``, etc. These restricted
implementations are hooked up by providing a set of specially named
objects in the global dict that you use for execution of code.
Specifically:
1. ``_print_`` is a callable object that returns a handler for print
statements. This handler must have a ``write()`` method that
accepts a single string argument, and must return a string when
called. ``RestrictedPython.PrintCollector.PrintCollector`` is a
suitable implementation.
2. ``_write_`` is a guard function taking a single argument. If the
object passed to it may be written to, it should be returned,
otherwise the guard function should raise an exception. ``_write``
is typically called on an object before a ``setattr`` operation.
3. ``_getattr_`` and ``_getitem_`` are guard functions, each of which
takes two arguments. The first is the base object to be accessed,
while the second is the attribute name or item index that will be
read. The guard function should return the attribute or subitem,
or raise an exception.
4. ``__import__`` is the normal Python import hook, and should be used
to control access to Python packages and modules.
5. ``__builtins__`` is the normal Python builtins dictionary, which
should be weeded down to a set that cannot be used to get around
your restrictions. A usable "safe" set is
``RestrictedPython.Guards.safe_builtins``.
To help illustrate how this works under the covers, here's an example
function::
def f(x):
x.foo = x.foo + x[0]
print x
return printed
and (sort of) how it looks after restricted compilation::
def f(x):
# Make local variables from globals.
_print = _print_()
_write = _write_
_getattr = _getattr_
_getitem = _getitem_
# Translation of f(x) above
_write(x).foo = _getattr(x, 'foo') + _getitem(x, 0)
print >>_print, x
return _print()
Examples
========
``print``
---------
To support the ``print`` statement in restricted code, we supply a
``_print_`` object (note that it's a *factory*, e.g. a class or a
callable, from which the restricted machinery will create the object):
>>> from RestrictedPython.PrintCollector import PrintCollector
>>> _print_ = PrintCollector
>>> src = '''
... print "Hello World!"
... '''
>>> code = compile_restricted(src, '<string>', 'exec')
>>> exec(code)
As you can see, the text doesn't appear on stdout. The print
collector collects it. We can have access to the text using the
``printed`` variable, though:
>>> src = '''
... print "Hello World!"
... result = printed
... '''
>>> code = compile_restricted(src, '<string>', 'exec')
>>> exec(code)
>>> result
'Hello World!\n'
Built-ins
---------
By supplying a different ``__builtins__`` dictionary, we can rule out
unsafe operations, such as opening files:
>>> from RestrictedPython.Guards import safe_builtins
>>> restricted_globals = dict(__builtins__ = safe_builtins)
>>> src = '''
... open('/etc/passwd')
... '''
>>> code = compile_restricted(src, '<string>', 'exec')
>>> exec(code) in restricted_globals
Traceback (most recent call last):
...
NameError: name 'open' is not defined
Guards
------
Here's an example of a write guard that never lets restricted code
modify (assign, delete an attribute or item) except dictionaries and
lists:
>>> from RestrictedPython.Guards import full_write_guard
>>> _write_ = full_write_guard
>>> _getattr_ = getattr
>>> class BikeShed(object):
... colour = 'green'
...
>>> shed = BikeShed()
Normally accessing attriutes works as expected, because we're using
the standard ``getattr`` function for the ``_getattr_`` guard:
>>> src = '''
... print shed.colour
... result = printed
... '''
>>> code = compile_restricted(src, '<string>', 'exec')
>>> exec(code)
>>> result
'green\n'
However, changing an attribute doesn't work:
>>> src = '''
... shed.colour = 'red'
... '''
>>> code = compile_restricted(src, '<string>', 'exec')
>>> exec(code)
Traceback (most recent call last):
...
TypeError: attribute-less object (assign or del)
As said, this particular write guard (``full_write_guard``) will allow
restricted code to modify lists and dictionaries:
>>> fibonacci = [1, 1, 2, 3, 4]
>>> transl = dict(one=1, two=2, tres=3)
>>> src = '''
... # correct mistake in list
... fibonacci[-1] = 5
... # one item doesn't belong
... del transl['tres']
... '''
>>> code = compile_restricted(src, '<string>', 'exec')
>>> exec(code)
>>> fibonacci
[1, 1, 2, 3, 5]
>>> sorted(transl.keys())
['one', 'two']
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# 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.
"""
# 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 Foundation and Contributors.
#
# 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 math
import random
import string
import warnings
_old_filters = warnings.filters[:]
warnings.filterwarnings('ignore', category=DeprecationWarning)
try:
try:
import sets
except ImportError:
sets = None
finally:
warnings.filters[:] = _old_filters
utility_builtins = {}
utility_builtins['string'] = string
utility_builtins['math'] = math
utility_builtins['random'] = random
utility_builtins['whrandom'] = random
utility_builtins['sets'] = sets
try:
import DateTime
utility_builtins['DateTime']= DateTime.DateTime
except ImportError:
pass
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):
length = len(args)
for i in range(1, length, 2):
if args[i-1]:
return args[i]
if length % 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
orig = {}
for item in s:
if isinstance(item, tuple) and len(item) == 2:
key, value = item
else:
key = value = item
orig[key] = value
result = []
for item in without:
if isinstance(item, tuple) and len(item) == 2:
key, ignored = item
else:
key = item
if key in orig:
del orig[key]
for item in with_:
if isinstance(item, tuple) and len(item) == 2:
key, ignored = item
else:
key = item
if key in orig:
result.append((key, orig[key]))
del orig[key]
return result
utility_builtins['reorder'] = reorder
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# 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.
'''
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 mutate 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 whose 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 Foundation 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.
"""
# 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 Foundation 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.
"""
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)
##############################################################################
#
# Copyright (c) 2008 Zope Foundation 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.
"""
def simple_ternary_if_before():
x.y = y.z if y.z else y.x
def simple_ternary_if_after():
_write_(x).y = _getattr_(y, 'z') if _getattr_(y, 'z') else _getattr_(y, 'x')
##############################################################################
#
# Copyright (c) 2008 Zope Foundation 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.
"""
def simple_context_before():
with whatever as x:
x.y = z
def simple_context_after():
with whatever as x:
_write_(x).y = z
def simple_context_assign_attr_before():
with whatever as x.y:
x.y = z
def simple_context_assign_attr_after():
with whatever as _write_(x).y:
_write_(x).y = z
def simple_context_load_attr_before():
with whatever.w as z:
x.y = z
def simple_context_load_attr_after():
with _getattr_(whatever, 'w') as z:
_write_(x).y = z
##############################################################################
#
# Copyright (c) 2010 Zope Foundation 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.
"""
# dictionary and set comprehensions
def simple_dict_comprehension_before():
x = {y: y for y in whatever if y}
def simple_dict_comprehension_after():
x = {y: y for y in _getiter_(whatever) if y}
def dict_comprehension_attrs_before():
x = {y: y.q for y in whatever.z if y.q}
def dict_comprehension_attrs_after():
x = {y: _getattr_(y, 'q') for y in _getiter_(_getattr_(whatever, 'z')) if _getattr_(y, 'q')}
def simple_set_comprehension_before():
x = {y for y in whatever if y}
def simple_set_comprehension_after():
x = {y for y in _getiter_(whatever) if y}
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
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().
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 from_import_as_bad_name():
from x import y 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
# 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().
def with_as_bad_name():
with x as _leading_underscore:
pass
def relative_import_as_bad_name():
from .x import y as _leading_underscore
def except_as_bad_name():
try:
1/0
except Exception as _leading_underscore:
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().
def dict_comp_bad_name():
{y: y for _restricted_name in x}
def set_comp_bad_name():
{y for _restricted_name in x}
def compound_with_bad_name():
with a as b, c as _restricted_name:
pass
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2006 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
__version__ = '$Revision: 110600 $'[11:-2]
import unittest
from RestrictedPython.RCompile import niceParse
import compiler.ast
class CompileTests(unittest.TestCase):
def testUnicodeSource(self):
# We support unicode sourcecode.
source = u"u'Ä väry nice säntänce with umlauts.'"
parsed = niceParse(source, "test.py", "exec")
self.failUnless(isinstance(parsed, compiler.ast.Module))
parsed = niceParse(source, "test.py", "single")
self.failUnless(isinstance(parsed, compiler.ast.Module))
parsed = niceParse(source, "test.py", "eval")
self.failUnless(isinstance(parsed, compiler.ast.Expression))
def test_suite():
return unittest.makeSuite(CompileTests)
##############################################################################
#
# Copyright (c) 2007 Zope Foundation 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.
#
##############################################################################
"""Run tests in README.txt
"""
import unittest
from doctest import DocFileSuite
__docformat__ = "reStructuredText"
def test_suite():
return unittest.TestSuite([
DocFileSuite('README.txt', package='RestrictedPython'),
])
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2009 Zope Foundation 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.
#
##############################################################################
"""Run tests in README.txt
"""
import unittest
class UtilitiesTests(unittest.TestCase):
def test_string_in_utility_builtins(self):
import string
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['string'] is string)
def test_math_in_utility_builtins(self):
import math
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['math'] is math)
def test_whrandom_in_utility_builtins(self):
import random
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['whrandom'] is random)
def test_random_in_utility_builtins(self):
import random
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['random'] is random)
def test_sets_in_utility_builtins_if_importable(self):
import warnings
from RestrictedPython.Utilities import utility_builtins
_old_filters = warnings.filters[:]
warnings.filterwarnings('ignore', category=DeprecationWarning)
try:
try:
import sets
except ImportError:
sets = None
finally:
warnings.filters[:] = _old_filters
self.failUnless(utility_builtins['sets'] is sets)
def test_DateTime_in_utility_builtins_if_importable(self):
try:
import DateTime
except ImportError:
pass
else:
from RestrictedPython.Utilities import utility_builtins
self.failUnless('DateTime' in utility_builtins)
def test_same_type_in_utility_builtins(self):
from RestrictedPython.Utilities import same_type
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['same_type'] is same_type)
def test_test_in_utility_builtins(self):
from RestrictedPython.Utilities import test
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['test'] is test)
def test_reorder_in_utility_builtins(self):
from RestrictedPython.Utilities import reorder
from RestrictedPython.Utilities import utility_builtins
self.failUnless(utility_builtins['reorder'] is reorder)
def test_sametype_only_one_arg(self):
from RestrictedPython.Utilities import same_type
self.failUnless(same_type(object()))
def test_sametype_only_two_args_same(self):
from RestrictedPython.Utilities import same_type
self.failUnless(same_type(object(), object()))
def test_sametype_only_two_args_different(self):
from RestrictedPython.Utilities import same_type
class Foo(object):
pass
self.failIf(same_type(object(), Foo()))
def test_sametype_only_multiple_args_same(self):
from RestrictedPython.Utilities import same_type
self.failUnless(same_type(object(), object(), object(), object()))
def test_sametype_only_multipe_args_one_different(self):
from RestrictedPython.Utilities import same_type
class Foo(object):
pass
self.failIf(same_type(object(), object(), Foo()))
def test_test_single_value_true(self):
from RestrictedPython.Utilities import test
self.failUnless(test(True))
def test_test_single_value_False(self):
from RestrictedPython.Utilities import test
self.failIf(test(False))
def test_test_even_values_first_true(self):
from RestrictedPython.Utilities import test
self.assertEqual(test(True, 'first', True, 'second'), 'first')
def test_test_even_values_not_first_true(self):
from RestrictedPython.Utilities import test
self.assertEqual(test(False, 'first', True, 'second'), 'second')
def test_test_odd_values_first_true(self):
from RestrictedPython.Utilities import test
self.assertEqual(test(True, 'first', True, 'second', False), 'first')
def test_test_odd_values_not_first_true(self):
from RestrictedPython.Utilities import test
self.assertEqual(test(False, 'first', True, 'second', False), 'second')
def test_test_odd_values_last_true(self):
from RestrictedPython.Utilities import test
self.assertEqual(test(False, 'first', False, 'second', 'third'),
'third')
def test_test_odd_values_last_false(self):
from RestrictedPython.Utilities import test
self.assertEqual(test(False, 'first', False, 'second', False), False)
def test_reorder_with__None(self):
from RestrictedPython.Utilities import reorder
before = ['a', 'b', 'c', 'd', 'e']
without = ['a', 'c', 'e']
after = reorder(before, without=without)
self.assertEqual(after, [('b', 'b'), ('d', 'd')])
def test_reorder_with__not_None(self):
from RestrictedPython.Utilities import reorder
before = ['a', 'b', 'c', 'd', 'e']
with_ = ['a', 'd']
without = ['a', 'c', 'e']
after = reorder(before, with_=with_, without=without)
self.assertEqual(after, [('d', 'd')])
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(UtilitiesTests),
))
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 Foundation and Contributors.
#
# 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)
with_context = (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.
# Another exception is the new-in-Python 2.6 'context
# managers', which do a LOAD_ATTR for __exit__ and
# __enter__.
if op.arg == "__exit__":
with_context = (op, with_context[1])
elif op.arg == "__enter__":
with_context = (with_context[0], op)
elif not ((op.arg == "__enter__" and
window[0].opname == "ROT_TWO" and
window[1].opname == "DUP_TOP") or
(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, code.co_filename, line))
if op.opname in ("WITH_CLEANUP"):
# Here we check if the LOAD_ATTR for __exit__ and
# __enter__ were part of a 'with' statement by checking
# for the 'WITH_CLEANUP' bytecode. If one is seen, we
# clear the with_context variable and let it go. The
# access was safe.
with_context = (None, None)
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]
if not with_context == (None, None):
# An access to __enter__ and __exit__ was performed but not as
# part of a 'with' statement. This is not allowed.
for op in with_context:
if op is not None:
if op.line is not None:
line = op.line
raise ValueError("direct attribute access %s: %s, %s:%d"
% (op.opname, op.arg, code.co_filename, line))
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