Commit 2ebcf9c5 authored by Boxiang Sun's avatar Boxiang Sun

Let RestrictedPython become stubs.

parent 1e456686
...@@ -13,62 +13,62 @@ ...@@ -13,62 +13,62 @@
__version__='$Revision: 1.6 $'[11:-2] __version__='$Revision: 1.6 $'[11:-2]
from SelectCompiler import ast # from SelectCompiler import ast
ListType = type([]) ListType = type([])
TupleType = type(()) TupleType = type(())
SequenceTypes = (ListType, TupleType) SequenceTypes = (ListType, TupleType)
class MutatingWalker: # class MutatingWalker:
#
def __init__(self, visitor): # def __init__(self, visitor):
self.visitor = visitor # self.visitor = visitor
self._cache = {} # self._cache = {}
#
def defaultVisitNode(self, node, walker=None, exclude=None): # def defaultVisitNode(self, node, walker=None, exclude=None):
for name, child in node.__dict__.items(): # for name, child in node.__dict__.items():
if exclude is not None and name in exclude: # if exclude is not None and name in exclude:
continue # continue
v = self.dispatchObject(child) # v = self.dispatchObject(child)
if v is not child: # if v is not child:
# Replace the node. # # Replace the node.
node.__dict__[name] = v # node.__dict__[name] = v
return node # return node
#
def visitSequence(self, seq): # def visitSequence(self, seq):
res = seq # res = seq
for idx in range(len(seq)): # for idx in range(len(seq)):
child = seq[idx] # child = seq[idx]
v = self.dispatchObject(child) # v = self.dispatchObject(child)
if v is not child: # if v is not child:
# Change the sequence. # # Change the sequence.
if type(res) is ListType: # if type(res) is ListType:
res[idx : idx + 1] = [v] # res[idx : idx + 1] = [v]
else: # else:
res = res[:idx] + (v,) + res[idx + 1:] # res = res[:idx] + (v,) + res[idx + 1:]
return res # return res
#
def dispatchObject(self, ob): # def dispatchObject(self, ob):
''' # '''
Expected to return either ob or something that will take # Expected to return either ob or something that will take
its place. # its place.
''' # '''
if isinstance(ob, ast.Node): # if isinstance(ob, ast.Node):
return self.dispatchNode(ob) # return self.dispatchNode(ob)
elif type(ob) in SequenceTypes: # elif type(ob) in SequenceTypes:
return self.visitSequence(ob) # return self.visitSequence(ob)
else: # else:
return ob # return ob
#
def dispatchNode(self, node): # def dispatchNode(self, node):
klass = node.__class__ # klass = node.__class__
meth = self._cache.get(klass, None) # meth = self._cache.get(klass, None)
if meth is None: # if meth is None:
className = klass.__name__ # className = klass.__name__
meth = getattr(self.visitor, 'visit' + className, # meth = getattr(self.visitor, 'visit' + className,
self.defaultVisitNode) # self.defaultVisitNode)
self._cache[klass] = meth # self._cache[klass] = meth
return meth(node, self) # return meth(node, self)
#
def walk(tree, visitor): # def walk(tree, visitor):
return MutatingWalker(visitor).dispatchNode(tree) # return MutatingWalker(visitor).dispatchNode(tree)
...@@ -16,65 +16,65 @@ Python standard library. ...@@ -16,65 +16,65 @@ Python standard library.
__version__='$Revision: 1.6 $'[11:-2] __version__='$Revision: 1.6 $'[11:-2]
from compiler import ast, parse, misc, syntax, pycodegen # from compiler import ast, parse, misc, syntax, pycodegen
from compiler.pycodegen import AbstractCompileMode, Expression, \ # from compiler.pycodegen import AbstractCompileMode, Expression, \
Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp # Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp
#
import MutatingWalker # import MutatingWalker
from RestrictionMutator import RestrictionMutator # from RestrictionMutator import RestrictionMutator
#
#
def niceParse(source, filename, mode): # def niceParse(source, filename, mode):
if isinstance(source, unicode): # if isinstance(source, unicode):
# Use the utf-8-sig BOM so the compiler # # Use the utf-8-sig BOM so the compiler
# detects this as a UTF-8 encoded string. # # detects this as a UTF-8 encoded string.
source = '\xef\xbb\xbf' + source.encode('utf-8') # source = '\xef\xbb\xbf' + source.encode('utf-8')
try: # try:
return parse(source, mode) # return parse(source, mode)
except: # except:
# Try to make a clean error message using # # Try to make a clean error message using
# the builtin Python compiler. # # the builtin Python compiler.
try: # try:
compile(source, filename, mode) # compile(source, filename, mode)
except SyntaxError: # except SyntaxError:
raise # raise
# Some other error occurred. # # Some other error occurred.
raise # raise
#
class RestrictedCompileMode(AbstractCompileMode): # class RestrictedCompileMode(AbstractCompileMode):
"""Abstract base class for hooking up custom CodeGenerator.""" # """Abstract base class for hooking up custom CodeGenerator."""
# See concrete subclasses below. # # See concrete subclasses below.
#
def __init__(self, source, filename): # def __init__(self, source, filename):
if source: # if source:
source = '\n'.join(source.splitlines()) + '\n' # source = '\n'.join(source.splitlines()) + '\n'
self.rm = RestrictionMutator() # self.rm = RestrictionMutator()
AbstractCompileMode.__init__(self, source, filename) # AbstractCompileMode.__init__(self, source, filename)
#
def parse(self): # def parse(self):
return niceParse(self.source, self.filename, self.mode) # return niceParse(self.source, self.filename, self.mode)
#
def _get_tree(self): # def _get_tree(self):
tree = self.parse() # tree = self.parse()
MutatingWalker.walk(tree, self.rm) # MutatingWalker.walk(tree, self.rm)
if self.rm.errors: # if self.rm.errors:
raise SyntaxError, self.rm.errors[0] # raise SyntaxError, self.rm.errors[0]
misc.set_filename(self.filename, tree) # misc.set_filename(self.filename, tree)
syntax.check(tree) # syntax.check(tree)
return tree # return tree
#
def compile(self): # def compile(self):
tree = self._get_tree() # tree = self._get_tree()
gen = self.CodeGeneratorClass(tree) # gen = self.CodeGeneratorClass(tree)
self.code = gen.getCode() # self.code = gen.getCode()
#
#
def compileAndTuplize(gen): # def compileAndTuplize(gen):
try: # try:
gen.compile() # gen.compile()
except SyntaxError, v: # except SyntaxError, v:
return None, (str(v),), gen.rm.warnings, gen.rm.used_names # return None, (str(v),), gen.rm.warnings, gen.rm.used_names
return gen.getCode(), (), 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): def compile_restricted_function(p, body, name, filename, globalize=None):
"""Compiles a restricted code object for a function. """Compiles a restricted code object for a function.
...@@ -87,13 +87,15 @@ def compile_restricted_function(p, body, name, filename, globalize=None): ...@@ -87,13 +87,15 @@ def compile_restricted_function(p, body, name, filename, globalize=None):
treated as globals (code is generated as if each name in the list treated as globals (code is generated as if each name in the list
appeared in a global statement at the top of the function). appeared in a global statement at the top of the function).
""" """
gen = RFunction(p, body, name, filename, globalize) # gen = RFunction(p, body, name, filename, globalize)
return compileAndTuplize(gen) # return compileAndTuplize(gen)
return None
def compile_restricted_exec(s, filename='<string>'): def compile_restricted_exec(s, filename='<string>'):
"""Compiles a restricted code suite.""" """Compiles a restricted code suite."""
gen = RModule(s, filename) # gen = RModule(s, filename)
return compileAndTuplize(gen) # return compileAndTuplize(gen)
return None
def compile_restricted_eval(s, filename='<string>'): def compile_restricted_eval(s, filename='<string>'):
"""Compiles a restricted expression.""" """Compiles a restricted expression."""
...@@ -102,143 +104,144 @@ def compile_restricted_eval(s, filename='<string>'): ...@@ -102,143 +104,144 @@ def compile_restricted_eval(s, filename='<string>'):
def compile_restricted(source, filename, mode): def compile_restricted(source, filename, mode):
"""Replacement for the builtin compile() function.""" """Replacement for the builtin compile() function."""
if mode == "single": # if mode == "single":
gen = RInteractive(source, filename) # gen = RInteractive(source, filename)
elif mode == "exec": # elif mode == "exec":
gen = RModule(source, filename) # gen = RModule(source, filename)
elif mode == "eval": # elif mode == "eval":
gen = RExpression(source, filename) # gen = RExpression(source, filename)
else: # else:
raise ValueError("compile_restricted() 3rd arg must be 'exec' or " # raise ValueError("compile_restricted() 3rd arg must be 'exec' or "
"'eval' or 'single'") # "'eval' or 'single'")
gen.compile() # gen.compile()
return gen.getCode() # return gen.getCode()
return None
class RestrictedCodeGenerator:
"""Mixin for CodeGenerator to replace UNPACK_SEQUENCE bytecodes. # 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 # The UNPACK_SEQUENCE opcode is not safe because it extracts
making __getitem__ checks. # 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 # This code generator replaces use of UNPACK_SEQUENCE with calls to
security checks, and returns a simple list. # 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. # # 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 # def _gen_safe_unpack_sequence(self, num):
# unpack num items. That's a security hole, since it exposes # # We're at a place where UNPACK_SEQUENCE should be generated, to
# individual items from an arbitrary iterable. We don't remove # # unpack num items. That's a security hole, since it exposes
# the UNPACK_SEQUENCE, but instead insert a call to our _getiter_() # # individual items from an arbitrary iterable. We don't remove
# wrapper first. That applies security checks to each item as # # the UNPACK_SEQUENCE, but instead insert a call to our _getiter_()
# it's delivered. codegen is (just) a bit messy because the # # wrapper first. That applies security checks to each item as
# iterable is already on the stack, so we have to do a stack swap # # it's delivered. codegen is (just) a bit messy because the
# to get things in the right order. # # iterable is already on the stack, so we have to do a stack swap
self.emit('LOAD_GLOBAL', '_getiter_') # # to get things in the right order.
self.emit('ROT_TWO') # self.emit('LOAD_GLOBAL', '_getiter_')
self.emit('CALL_FUNCTION', 1) # self.emit('ROT_TWO')
self.emit('UNPACK_SEQUENCE', num) # self.emit('CALL_FUNCTION', 1)
# self.emit('UNPACK_SEQUENCE', num)
def _visitAssSequence(self, node): #
if findOp(node) != 'OP_DELETE': # def _visitAssSequence(self, node):
self._gen_safe_unpack_sequence(len(node.nodes)) # if findOp(node) != 'OP_DELETE':
for child in node.nodes: # self._gen_safe_unpack_sequence(len(node.nodes))
self.visit(child) # for child in node.nodes:
# self.visit(child)
visitAssTuple = _visitAssSequence #
visitAssList = _visitAssSequence # visitAssTuple = _visitAssSequence
# visitAssList = _visitAssSequence
# Call to generate code for unpacking nested tuple arguments #
# in function calls. # # Call to generate code for unpacking nested tuple arguments
# # in function calls.
def unpackSequence(self, tup): #
self._gen_safe_unpack_sequence(len(tup)) # def unpackSequence(self, tup):
for elt in tup: # self._gen_safe_unpack_sequence(len(tup))
if isinstance(elt, tuple): # for elt in tup:
self.unpackSequence(elt) # if isinstance(elt, tuple):
else: # self.unpackSequence(elt)
self._nameOp('STORE', 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 # # A collection of code generators that adds the restricted mixin to
# are defined here (at the end) so that can refer to RestrictedCodeGenerator. # # 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): # class RestrictedFunctionCodeGenerator(RestrictedCodeGenerator,
pass # pycodegen.FunctionCodeGenerator):
# pass
class RestrictedExpressionCodeGenerator(RestrictedCodeGenerator, #
pycodegen.ExpressionCodeGenerator): # class RestrictedExpressionCodeGenerator(RestrictedCodeGenerator,
pass # pycodegen.ExpressionCodeGenerator):
# pass
class RestrictedInteractiveCodeGenerator(RestrictedCodeGenerator, #
pycodegen.InteractiveCodeGenerator): # class RestrictedInteractiveCodeGenerator(RestrictedCodeGenerator,
pass # pycodegen.InteractiveCodeGenerator):
# pass
class RestrictedModuleCodeGenerator(RestrictedCodeGenerator, #
pycodegen.ModuleCodeGenerator): # class RestrictedModuleCodeGenerator(RestrictedCodeGenerator,
# pycodegen.ModuleCodeGenerator):
def initClass(self): #
ModuleCodeGenerator.initClass(self) # def initClass(self):
self.__class__.FunctionGen = RestrictedFunctionCodeGenerator # 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 # # These subclasses work around the definition of stub compile and mode
# didn't define new attributes, then the stub code inherited via # # attributes in the common base class AbstractCompileMode. If it
# RestrictedCompileMode would override the real definitions in # # didn't define new attributes, then the stub code inherited via
# Expression. # # RestrictedCompileMode would override the real definitions in
# # Expression.
class RExpression(RestrictedCompileMode, Expression): #
mode = "eval" # class RExpression(RestrictedCompileMode, Expression):
CodeGeneratorClass = RestrictedExpressionCodeGenerator # mode = "eval"
# CodeGeneratorClass = RestrictedExpressionCodeGenerator
class RInteractive(RestrictedCompileMode, Interactive): #
mode = "single" # class RInteractive(RestrictedCompileMode, Interactive):
CodeGeneratorClass = RestrictedInteractiveCodeGenerator # mode = "single"
# CodeGeneratorClass = RestrictedInteractiveCodeGenerator
class RModule(RestrictedCompileMode, Module): #
mode = "exec" # class RModule(RestrictedCompileMode, Module):
CodeGeneratorClass = RestrictedModuleCodeGenerator # mode = "exec"
# CodeGeneratorClass = RestrictedModuleCodeGenerator
class RFunction(RModule): #
"""A restricted Python function built from parts.""" # class RFunction(RModule):
# """A restricted Python function built from parts."""
CodeGeneratorClass = RestrictedModuleCodeGenerator #
# CodeGeneratorClass = RestrictedModuleCodeGenerator
def __init__(self, p, body, name, filename, globals): #
self.params = p # def __init__(self, p, body, name, filename, globals):
if body: # self.params = p
body = '\n'.join(body.splitlines()) + '\n' # if body:
self.body = body # body = '\n'.join(body.splitlines()) + '\n'
self.name = name # self.body = body
self.globals = globals or [] # self.name = name
RModule.__init__(self, None, filename) # self.globals = globals or []
# RModule.__init__(self, None, filename)
def parse(self): #
# Parse the parameters and body, then combine them. # def parse(self):
firstline = 'def f(%s): pass' % self.params # # Parse the parameters and body, then combine them.
tree = niceParse(firstline, '<function parameters>', 'exec') # firstline = 'def f(%s): pass' % self.params
f = tree.node.nodes[0] # tree = niceParse(firstline, '<function parameters>', 'exec')
body_code = niceParse(self.body, self.filename, 'exec') # f = tree.node.nodes[0]
# Stitch the body code into the function. # body_code = niceParse(self.body, self.filename, 'exec')
f.code.nodes = body_code.node.nodes # # Stitch the body code into the function.
f.name = self.name # f.code.nodes = body_code.node.nodes
# Look for a docstring, if there are any nodes at all # f.name = self.name
if len(f.code.nodes) > 0: # # Look for a docstring, if there are any nodes at all
stmt1 = f.code.nodes[0] # if len(f.code.nodes) > 0:
if (isinstance(stmt1, ast.Discard) and # stmt1 = f.code.nodes[0]
isinstance(stmt1.expr, ast.Const) and # if (isinstance(stmt1, ast.Discard) and
isinstance(stmt1.expr.value, str)): # isinstance(stmt1.expr, ast.Const) and
f.doc = stmt1.expr.value # isinstance(stmt1.expr.value, str)):
# The caller may specify that certain variables are globals # f.doc = stmt1.expr.value
# so that they can be referenced before a local assignment. # # The caller may specify that certain variables are globals
# The only known example is the variables context, container, # # so that they can be referenced before a local assignment.
# script, traverse_subpath in PythonScripts. # # The only known example is the variables context, container,
if self.globals: # # script, traverse_subpath in PythonScripts.
f.code.nodes.insert(0, ast.Global(self.globals)) # if self.globals:
return tree # f.code.nodes.insert(0, ast.Global(self.globals))
# return tree
...@@ -14,10 +14,10 @@ ...@@ -14,10 +14,10 @@
""" """
# Use the compiler from the standard library. # Use the compiler from the standard library.
import compiler # import compiler
from compiler import ast # from compiler import ast
from compiler.transformer import parse # from compiler.transformer import parse
from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY # from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
from RCompile import \ from RCompile import \
compile_restricted, \ compile_restricted, \
......
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