Commit 5976a7da authored by Boxiang Sun's avatar Boxiang Sun

switch from compiler package to ast package

parent 1e456686
##############################################################################
#
# 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)
......@@ -16,12 +16,13 @@ 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
# The AbstractCompileMode
# The compiler.pycodegen.Expression is just a subclass of AbstractCompileMode.
import ast
import MutatingWalker
from RestrictionMutator import RestrictionMutator
from RestrictionMutator import RestrictionTransformer
from ast import parse
def niceParse(source, filename, mode):
......@@ -30,7 +31,7 @@ def niceParse(source, filename, mode):
# detects this as a UTF-8 encoded string.
source = '\xef\xbb\xbf' + source.encode('utf-8')
try:
return parse(source, mode)
return parse(source, mode=mode)
except:
# Try to make a clean error message using
# the builtin Python compiler.
......@@ -41,40 +42,46 @@ def niceParse(source, filename, mode):
# Some other error occurred.
raise
class RestrictedCompileMode(AbstractCompileMode):
"""Abstract base class for hooking up custom CodeGenerator."""
# See concrete subclasses below.
# Note: the AbstractCompileMode is in Python/Lib/compiler/pycodegen
# it is just a same class like RestructedCompileMode, nothing special to inheritate.
def __init__(self, source, filename):
# class RestrictedCompileMode(AbstractCompileMode):
class RestrictedCompileMode(object):
# """Abstract base class for hooking up custom CodeGenerator."""
# # See concrete subclasses below.
#
def __init__(self, source, filename, mode='exec'):
if source:
source = '\n'.join(source.splitlines()) + '\n'
self.rm = RestrictionMutator()
AbstractCompileMode.__init__(self, source, filename)
self.rt = RestrictionTransformer()
self.source = source
self.filename = filename
self.code = None
self.mode = mode
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)
self.rt.visit(tree)
if self.rt.errors:
raise SyntaxError, self.rt.errors[0]
return tree
def compile(self):
tree = self._get_tree()
gen = self.CodeGeneratorClass(tree)
self.code = gen.getCode()
self.code = compile(tree, self.filename, self.mode)
def getCode(self):
return self.code
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
return None, (str(v),), gen.rt.warnings, gen.rt.used_names
return gen.getCode(), (), gen.rt.warnings, gen.rt.used_names
def compile_restricted_function(p, body, name, filename, globalize=None):
"""Compiles a restricted code object for a function.
......@@ -90,102 +97,25 @@ def compile_restricted_function(p, body, name, filename, globalize=None):
gen = RFunction(p, body, name, filename, globalize)
return compileAndTuplize(gen)
def compile_restricted_exec(s, filename='<string>'):
def compile_restricted_exec(source, filename='<string>'):
"""Compiles a restricted code suite."""
gen = RModule(s, filename)
gen = RestrictedCompileMode(source, filename ,'exec')
return compileAndTuplize(gen)
def compile_restricted_eval(s, filename='<string>'):
def compile_restricted_eval(source, filename='<string>'):
"""Compiles a restricted expression."""
gen = RExpression(s, filename)
gen = RestrictedCompileMode(source, filename ,'eval')
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:
if mode not in ('single', 'exec', 'eval'):
raise ValueError("compile_restricted() 3rd arg must be 'exec' or "
"'eval' or 'single'")
gen = RestrictedCompileMode(source, filename ,mode)
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
......@@ -193,23 +123,19 @@ class RestrictedModuleCodeGenerator(RestrictedCodeGenerator,
# RestrictedCompileMode would override the real definitions in
# Expression.
class RExpression(RestrictedCompileMode, Expression):
class RExpression(RestrictedCompileMode):
mode = "eval"
CodeGeneratorClass = RestrictedExpressionCodeGenerator
class RInteractive(RestrictedCompileMode, Interactive):
class RInteractive(RestrictedCompileMode):
mode = "single"
CodeGeneratorClass = RestrictedInteractiveCodeGenerator
class RModule(RestrictedCompileMode, Module):
mode = "exec"
CodeGeneratorClass = RestrictedModuleCodeGenerator
class RModule(RestrictedCompileMode):
def __init__(self, source, filename):
super(RModule, self).__init__(source, filename, 'exec')
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:
......@@ -223,22 +149,23 @@ class RFunction(RModule):
# 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]
f = tree.body[0]
body_code = niceParse(self.body, self.filename, 'exec')
# Stitch the body code into the function.
f.code.nodes = body_code.node.nodes
f.body = body_code.body
# 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
# if len(f.code.nodes) > 0:
if len(f.body) > 0:
stmt1 = f.body[0]
if (isinstance(stmt1, ast.Expr) and
isinstance(stmt1.value, ast.Str)):
f.__doc__ = stmt1.value.s
# 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))
f.body.insert(0, ast.Global(self.globals))
return tree
......@@ -13,12 +13,6 @@
"""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, \
......
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