Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.mozilla.javascript.IRFactory Maven / Gradle / Ivy
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import org.mozilla.javascript.ast.*;
import java.util.List;
import java.util.ArrayList;
/**
* This class rewrites the parse tree into an IR suitable for codegen.
*
* @see Node
* @author Mike McCabe
* @author Norris Boyd
*/
public final class IRFactory extends Parser
{
private static final int LOOP_DO_WHILE = 0;
private static final int LOOP_WHILE = 1;
private static final int LOOP_FOR = 2;
private static final int ALWAYS_TRUE_BOOLEAN = 1;
private static final int ALWAYS_FALSE_BOOLEAN = -1;
private Decompiler decompiler = new Decompiler();
public IRFactory() {
super();
}
public IRFactory(CompilerEnvirons env) {
this(env, env.getErrorReporter());
}
public IRFactory(CompilerEnvirons env, ErrorReporter errorReporter) {
super(env, errorReporter);
}
/**
* Transforms the tree into a lower-level IR suitable for codegen.
* Optionally generates the encoded source.
*/
public ScriptNode transformTree(AstRoot root) {
currentScriptOrFn = root;
this.inUseStrictDirective = root.isInStrictMode();
int sourceStartOffset = decompiler.getCurrentOffset();
if (Token.printTrees) {
System.out.println("IRFactory.transformTree");
System.out.println(root.debugPrint());
}
ScriptNode script = (ScriptNode)transform(root);
int sourceEndOffset = decompiler.getCurrentOffset();
script.setEncodedSourceBounds(sourceStartOffset,
sourceEndOffset);
if (compilerEnv.isGeneratingSource()) {
script.setEncodedSource(decompiler.getEncodedSource());
}
decompiler = null;
return script;
}
// Might want to convert this to polymorphism - move transform*
// functions into the AstNode subclasses. OTOH that would make
// IR transformation part of the public AST API - desirable?
// Another possibility: create AstTransformer interface and adapter.
public Node transform(AstNode node) {
switch (node.getType()) {
case Token.ARRAYCOMP:
return transformArrayComp((ArrayComprehension)node);
case Token.ARRAYLIT:
return transformArrayLiteral((ArrayLiteral)node);
case Token.BLOCK:
return transformBlock(node);
case Token.BREAK:
return transformBreak((BreakStatement)node);
case Token.CALL:
return transformFunctionCall((FunctionCall)node);
case Token.CONTINUE:
return transformContinue((ContinueStatement)node);
case Token.DO:
return transformDoLoop((DoLoop)node);
case Token.EMPTY:
return node;
case Token.FOR:
if (node instanceof ForInLoop) {
return transformForInLoop((ForInLoop)node);
} else {
return transformForLoop((ForLoop)node);
}
case Token.FUNCTION:
return transformFunction((FunctionNode)node);
case Token.GENEXPR:
return transformGenExpr((GeneratorExpression)node);
case Token.GETELEM:
return transformElementGet((ElementGet)node);
case Token.GETPROP:
return transformPropertyGet((PropertyGet)node);
case Token.HOOK:
return transformCondExpr((ConditionalExpression)node);
case Token.IF:
return transformIf((IfStatement)node);
case Token.TRUE:
case Token.FALSE:
case Token.THIS:
case Token.NULL:
case Token.DEBUGGER:
return transformLiteral(node);
case Token.NAME:
return transformName((Name)node);
case Token.NUMBER:
return transformNumber((NumberLiteral)node);
case Token.NEW:
return transformNewExpr((NewExpression)node);
case Token.OBJECTLIT:
return transformObjectLiteral((ObjectLiteral)node);
case Token.REGEXP:
return transformRegExp((RegExpLiteral)node);
case Token.RETURN:
return transformReturn((ReturnStatement)node);
case Token.SCRIPT:
return transformScript((ScriptNode)node);
case Token.STRING:
return transformString((StringLiteral)node);
case Token.SWITCH:
return transformSwitch((SwitchStatement)node);
case Token.THROW:
return transformThrow((ThrowStatement)node);
case Token.TRY:
return transformTry((TryStatement)node);
case Token.WHILE:
return transformWhileLoop((WhileLoop)node);
case Token.WITH:
return transformWith((WithStatement)node);
case Token.YIELD:
return transformYield((Yield)node);
default:
if (node instanceof ExpressionStatement) {
return transformExprStmt((ExpressionStatement)node);
}
if (node instanceof Assignment) {
return transformAssignment((Assignment)node);
}
if (node instanceof UnaryExpression) {
return transformUnary((UnaryExpression)node);
}
if (node instanceof XmlMemberGet) {
return transformXmlMemberGet((XmlMemberGet)node);
}
if (node instanceof InfixExpression) {
return transformInfix((InfixExpression)node);
}
if (node instanceof VariableDeclaration) {
return transformVariables((VariableDeclaration)node);
}
if (node instanceof ParenthesizedExpression) {
return transformParenExpr((ParenthesizedExpression)node);
}
if (node instanceof LabeledStatement) {
return transformLabeledStatement((LabeledStatement)node);
}
if (node instanceof LetNode) {
return transformLetNode((LetNode)node);
}
if (node instanceof XmlRef) {
return transformXmlRef((XmlRef)node);
}
if (node instanceof XmlLiteral) {
return transformXmlLiteral((XmlLiteral)node);
}
throw new IllegalArgumentException("Can't transform: " + node);
}
}
private Node transformArrayComp(ArrayComprehension node) {
// An array comprehension expression such as
//
// [expr for (x in foo) for each ([y, z] in bar) if (cond)]
//
// is rewritten approximately as
//
// new Scope(ARRAYCOMP) {
// new Node(BLOCK) {
// let tmp1 = new Array;
// for (let x in foo) {
// for each (let tmp2 in bar) {
// if (cond) {
// tmp1.push([y, z] = tmp2, expr);
// }
// }
// }
// }
// createName(tmp1)
// }
int lineno = node.getLineno();
Scope scopeNode = createScopeNode(Token.ARRAYCOMP, lineno);
String arrayName = currentScriptOrFn.getNextTempName();
pushScope(scopeNode);
try {
defineSymbol(Token.LET, arrayName, false);
Node block = new Node(Token.BLOCK, lineno);
Node newArray = createCallOrNew(Token.NEW, createName("Array"));
Node init = new Node(Token.EXPR_VOID,
createAssignment(Token.ASSIGN,
createName(arrayName),
newArray),
lineno);
block.addChildToBack(init);
block.addChildToBack(arrayCompTransformHelper(node, arrayName));
scopeNode.addChildToBack(block);
scopeNode.addChildToBack(createName(arrayName));
return scopeNode;
} finally {
popScope();
}
}
private Node arrayCompTransformHelper(ArrayComprehension node,
String arrayName) {
decompiler.addToken(Token.LB);
int lineno = node.getLineno();
Node expr = transform(node.getResult());
List loops = node.getLoops();
int numLoops = loops.size();
// Walk through loops, collecting and defining their iterator symbols.
Node[] iterators = new Node[numLoops];
Node[] iteratedObjs = new Node[numLoops];
for (int i = 0; i < numLoops; i++) {
ArrayComprehensionLoop acl = loops.get(i);
decompiler.addName(" ");
decompiler.addToken(Token.FOR);
if (acl.isForEach()) {
decompiler.addName("each ");
}
decompiler.addToken(Token.LP);
AstNode iter = acl.getIterator();
String name = null;
if (iter.getType() == Token.NAME) {
name = iter.getString();
decompiler.addName(name);
} else {
// destructuring assignment
decompile(iter);
name = currentScriptOrFn.getNextTempName();
defineSymbol(Token.LP, name, false);
expr = createBinary(Token.COMMA,
createAssignment(Token.ASSIGN,
iter,
createName(name)),
expr);
}
Node init = createName(name);
// Define as a let since we want the scope of the variable to
// be restricted to the array comprehension
defineSymbol(Token.LET, name, false);
iterators[i] = init;
decompiler.addToken(Token.IN);
iteratedObjs[i] = transform(acl.getIteratedObject());
decompiler.addToken(Token.RP);
}
// generate code for tmpArray.push(body)
Node call = createCallOrNew(Token.CALL,
createPropertyGet(createName(arrayName),
null,
"push", 0));
Node body = new Node(Token.EXPR_VOID, call, lineno);
if (node.getFilter() != null) {
decompiler.addName(" ");
decompiler.addToken(Token.IF);
decompiler.addToken(Token.LP);
body = createIf(transform(node.getFilter()), body, null, lineno);
decompiler.addToken(Token.RP);
}
// Now walk loops in reverse to build up the body statement.
int pushed = 0;
try {
for (int i = numLoops-1; i >= 0; i--) {
ArrayComprehensionLoop acl = loops.get(i);
Scope loop = createLoopNode(null, // no label
acl.getLineno());
pushScope(loop);
pushed++;
body = createForIn(Token.LET,
loop,
iterators[i],
iteratedObjs[i],
body,
acl.isForEach());
}
} finally {
for (int i = 0; i < pushed; i++) {
popScope();
}
}
decompiler.addToken(Token.RB);
// Now that we've accumulated any destructuring forms,
// add expr to the call node; it's pushed on each iteration.
call.addChildToBack(expr);
return body;
}
private Node transformArrayLiteral(ArrayLiteral node) {
if (node.isDestructuring()) {
return node;
}
decompiler.addToken(Token.LB);
List elems = node.getElements();
Node array = new Node(Token.ARRAYLIT);
List skipIndexes = null;
for (int i = 0; i < elems.size(); ++i) {
AstNode elem = elems.get(i);
if (elem.getType() != Token.EMPTY) {
array.addChildToBack(transform(elem));
} else {
if (skipIndexes == null) {
skipIndexes = new ArrayList();
}
skipIndexes.add(i);
}
if (i < elems.size() - 1)
decompiler.addToken(Token.COMMA);
}
decompiler.addToken(Token.RB);
array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH,
node.getDestructuringLength());
if (skipIndexes != null) {
int[] skips = new int[skipIndexes.size()];
for (int i = 0; i < skipIndexes.size(); i++)
skips[i] = skipIndexes.get(i);
array.putProp(Node.SKIP_INDEXES_PROP, skips);
}
return array;
}
private Node transformAssignment(Assignment node) {
AstNode left = removeParens(node.getLeft());
Node target = null;
if (isDestructuring(left)) {
decompile(left);
target = left;
} else {
target = transform(left);
}
decompiler.addToken(node.getType());
return createAssignment(node.getType(),
target,
transform(node.getRight()));
}
private Node transformBlock(AstNode node) {
if (node instanceof Scope) {
pushScope((Scope)node);
}
try {
List kids = new ArrayList();
for (Node kid : node) {
kids.add(transform((AstNode)kid));
}
node.removeChildren();
for (Node kid : kids) {
node.addChildToBack(kid);
}
return node;
} finally {
if (node instanceof Scope) {
popScope();
}
}
}
private Node transformBreak(BreakStatement node) {
decompiler.addToken(Token.BREAK);
if (node.getBreakLabel() != null) {
decompiler.addName(node.getBreakLabel().getIdentifier());
}
decompiler.addEOL(Token.SEMI);
return node;
}
private Node transformCondExpr(ConditionalExpression node) {
Node test = transform(node.getTestExpression());
decompiler.addToken(Token.HOOK);
Node ifTrue = transform(node.getTrueExpression());
decompiler.addToken(Token.COLON);
Node ifFalse = transform(node.getFalseExpression());
return createCondExpr(test, ifTrue, ifFalse);
}
private Node transformContinue(ContinueStatement node) {
decompiler.addToken(Token.CONTINUE);
if (node.getLabel() != null) {
decompiler.addName(node.getLabel().getIdentifier());
}
decompiler.addEOL(Token.SEMI);
return node;
}
private Node transformDoLoop(DoLoop loop) {
loop.setType(Token.LOOP);
pushScope(loop);
try {
decompiler.addToken(Token.DO);
decompiler.addEOL(Token.LC);
Node body = transform(loop.getBody());
decompiler.addToken(Token.RC);
decompiler.addToken(Token.WHILE);
decompiler.addToken(Token.LP);
Node cond = transform(loop.getCondition());
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.SEMI);
return createLoop(loop, LOOP_DO_WHILE,
body, cond, null, null);
} finally {
popScope();
}
}
private Node transformElementGet(ElementGet node) {
// OPT: could optimize to createPropertyGet
// iff elem is string that can not be number
Node target = transform(node.getTarget());
decompiler.addToken(Token.LB);
Node element = transform(node.getElement());
decompiler.addToken(Token.RB);
return new Node(Token.GETELEM, target, element);
}
private Node transformExprStmt(ExpressionStatement node) {
Node expr = transform(node.getExpression());
decompiler.addEOL(Token.SEMI);
return new Node(node.getType(), expr, node.getLineno());
}
private Node transformForInLoop(ForInLoop loop) {
decompiler.addToken(Token.FOR);
if (loop.isForEach())
decompiler.addName("each ");
decompiler.addToken(Token.LP);
loop.setType(Token.LOOP);
pushScope(loop);
try {
int declType = -1;
AstNode iter = loop.getIterator();
if (iter instanceof VariableDeclaration) {
declType = ((VariableDeclaration)iter).getType();
}
Node lhs = transform(iter);
decompiler.addToken(Token.IN);
Node obj = transform(loop.getIteratedObject());
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);
Node body = transform(loop.getBody());
decompiler.addEOL(Token.RC);
return createForIn(declType, loop, lhs, obj, body,
loop.isForEach());
} finally {
popScope();
}
}
private Node transformForLoop(ForLoop loop) {
decompiler.addToken(Token.FOR);
decompiler.addToken(Token.LP);
loop.setType(Token.LOOP);
// XXX: Can't use pushScope/popScope here since 'createFor' may split
// the scope
Scope savedScope = currentScope;
currentScope = loop;
try {
Node init = transform(loop.getInitializer());
decompiler.addToken(Token.SEMI);
Node test = transform(loop.getCondition());
decompiler.addToken(Token.SEMI);
Node incr = transform(loop.getIncrement());
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);
Node body = transform(loop.getBody());
decompiler.addEOL(Token.RC);
return createFor(loop, init, test, incr, body);
} finally {
currentScope = savedScope;
}
}
private Node transformFunction(FunctionNode fn) {
int functionType = fn.getFunctionType();
int start = decompiler.markFunctionStart(functionType);
Node mexpr = decompileFunctionHeader(fn);
int index = currentScriptOrFn.addFunction(fn);
PerFunctionVariables savedVars = new PerFunctionVariables(fn);
try {
// If we start needing to record much more codegen metadata during
// function parsing, we should lump it all into a helper class.
Node destructuring = (Node)fn.getProp(Node.DESTRUCTURING_PARAMS);
fn.removeProp(Node.DESTRUCTURING_PARAMS);
int lineno = fn.getBody().getLineno();
++nestingOfFunction; // only for body, not params
Node body = transform(fn.getBody());
if (!fn.isExpressionClosure()) {
decompiler.addToken(Token.RC);
}
fn.setEncodedSourceBounds(start, decompiler.markFunctionEnd(start));
if (functionType != FunctionNode.FUNCTION_EXPRESSION && !fn.isExpressionClosure()) {
// Add EOL only if function is not part of expression
// since it gets SEMI + EOL from Statement in that case
decompiler.addToken(Token.EOL);
}
if (destructuring != null) {
body.addChildToFront(new Node(Token.EXPR_VOID,
destructuring, lineno));
}
int syntheticType = fn.getFunctionType();
Node pn = initFunction(fn, index, body, syntheticType);
if (mexpr != null) {
pn = createAssignment(Token.ASSIGN, mexpr, pn);
if (syntheticType != FunctionNode.FUNCTION_EXPRESSION) {
pn = createExprStatementNoReturn(pn, fn.getLineno());
}
}
return pn;
} finally {
--nestingOfFunction;
savedVars.restore();
}
}
private Node transformFunctionCall(FunctionCall node) {
Node call = createCallOrNew(Token.CALL, transform(node.getTarget()));
call.setLineno(node.getLineno());
decompiler.addToken(Token.LP);
List args = node.getArguments();
for (int i = 0; i < args.size(); i++) {
AstNode arg = args.get(i);
call.addChildToBack(transform(arg));
if (i < args.size() - 1) {
decompiler.addToken(Token.COMMA);
}
}
decompiler.addToken(Token.RP);
return call;
}
private Node transformGenExpr(GeneratorExpression node) {
Node pn;
FunctionNode fn = new FunctionNode();
fn.setSourceName(currentScriptOrFn.getNextTempName());
fn.setIsGenerator();
fn.setFunctionType(FunctionNode.FUNCTION_EXPRESSION);
fn.setRequiresActivation();
int functionType = fn.getFunctionType();
int start = decompiler.markFunctionStart(functionType);
Node mexpr = decompileFunctionHeader(fn);
int index = currentScriptOrFn.addFunction(fn);
PerFunctionVariables savedVars = new PerFunctionVariables(fn);
try {
// If we start needing to record much more codegen metadata during
// function parsing, we should lump it all into a helper class.
Node destructuring = (Node)fn.getProp(Node.DESTRUCTURING_PARAMS);
fn.removeProp(Node.DESTRUCTURING_PARAMS);
int lineno = node.lineno;
++nestingOfFunction; // only for body, not params
Node body = genExprTransformHelper(node);
if (!fn.isExpressionClosure()) {
decompiler.addToken(Token.RC);
}
fn.setEncodedSourceBounds(start, decompiler.markFunctionEnd(start));
if (functionType != FunctionNode.FUNCTION_EXPRESSION && !fn.isExpressionClosure()) {
// Add EOL only if function is not part of expression
// since it gets SEMI + EOL from Statement in that case
decompiler.addToken(Token.EOL);
}
if (destructuring != null) {
body.addChildToFront(new Node(Token.EXPR_VOID,
destructuring, lineno));
}
int syntheticType = fn.getFunctionType();
pn = initFunction(fn, index, body, syntheticType);
if (mexpr != null) {
pn = createAssignment(Token.ASSIGN, mexpr, pn);
if (syntheticType != FunctionNode.FUNCTION_EXPRESSION) {
pn = createExprStatementNoReturn(pn, fn.getLineno());
}
}
} finally {
--nestingOfFunction;
savedVars.restore();
}
Node call = createCallOrNew(Token.CALL, pn);
call.setLineno(node.getLineno());
decompiler.addToken(Token.LP);
decompiler.addToken(Token.RP);
return call;
}
private Node genExprTransformHelper(GeneratorExpression node) {
decompiler.addToken(Token.LP);
int lineno = node.getLineno();
Node expr = transform(node.getResult());
List loops = node.getLoops();
int numLoops = loops.size();
// Walk through loops, collecting and defining their iterator symbols.
Node[] iterators = new Node[numLoops];
Node[] iteratedObjs = new Node[numLoops];
for (int i = 0; i < numLoops; i++) {
GeneratorExpressionLoop acl = loops.get(i);
decompiler.addName(" ");
decompiler.addToken(Token.FOR);
decompiler.addToken(Token.LP);
AstNode iter = acl.getIterator();
String name = null;
if (iter.getType() == Token.NAME) {
name = iter.getString();
decompiler.addName(name);
} else {
// destructuring assignment
decompile(iter);
name = currentScriptOrFn.getNextTempName();
defineSymbol(Token.LP, name, false);
expr = createBinary(Token.COMMA,
createAssignment(Token.ASSIGN,
iter,
createName(name)),
expr);
}
Node init = createName(name);
// Define as a let since we want the scope of the variable to
// be restricted to the array comprehension
defineSymbol(Token.LET, name, false);
iterators[i] = init;
decompiler.addToken(Token.IN);
iteratedObjs[i] = transform(acl.getIteratedObject());
decompiler.addToken(Token.RP);
}
// generate code for tmpArray.push(body)
Node yield = new Node(Token.YIELD, expr, node.getLineno());
Node body = new Node(Token.EXPR_VOID, yield, lineno);
if (node.getFilter() != null) {
decompiler.addName(" ");
decompiler.addToken(Token.IF);
decompiler.addToken(Token.LP);
body = createIf(transform(node.getFilter()), body, null, lineno);
decompiler.addToken(Token.RP);
}
// Now walk loops in reverse to build up the body statement.
int pushed = 0;
try {
for (int i = numLoops-1; i >= 0; i--) {
GeneratorExpressionLoop acl = loops.get(i);
Scope loop = createLoopNode(null, // no label
acl.getLineno());
pushScope(loop);
pushed++;
body = createForIn(Token.LET,
loop,
iterators[i],
iteratedObjs[i],
body,
acl.isForEach());
}
} finally {
for (int i = 0; i < pushed; i++) {
popScope();
}
}
decompiler.addToken(Token.RP);
return body;
}
private Node transformIf(IfStatement n) {
decompiler.addToken(Token.IF);
decompiler.addToken(Token.LP);
Node cond = transform(n.getCondition());
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);
Node ifTrue = transform(n.getThenPart());
Node ifFalse = null;
if (n.getElsePart() != null) {
decompiler.addToken(Token.RC);
decompiler.addToken(Token.ELSE);
decompiler.addEOL(Token.LC);
ifFalse = transform(n.getElsePart());
}
decompiler.addEOL(Token.RC);
return createIf(cond, ifTrue, ifFalse, n.getLineno());
}
private Node transformInfix(InfixExpression node) {
Node left = transform(node.getLeft());
decompiler.addToken(node.getType());
Node right = transform(node.getRight());
if (node instanceof XmlDotQuery) {
decompiler.addToken(Token.RP);
}
return createBinary(node.getType(), left, right);
}
private Node transformLabeledStatement(LabeledStatement ls) {
Label label = ls.getFirstLabel();
List labels = ls.getLabels();
decompiler.addName(label.getName());
if (labels.size() > 1) {
// more than one label
for (Label lb : labels.subList(1, labels.size())) {
decompiler.addEOL(Token.COLON);
decompiler.addName(lb.getName());
}
}
if (ls.getStatement().getType() == Token.BLOCK) {
// reuse OBJECTLIT for ':' workaround, cf. transformObjectLiteral()
decompiler.addToken(Token.OBJECTLIT);
decompiler.addEOL(Token.LC);
} else {
decompiler.addEOL(Token.COLON);
}
Node statement = transform(ls.getStatement());
if (ls.getStatement().getType() == Token.BLOCK) {
decompiler.addEOL(Token.RC);
}
// Make a target and put it _after_ the statement node. Add in the
// LABEL node, so breaks get the right target.
Node breakTarget = Node.newTarget();
Node block = new Node(Token.BLOCK, label, statement, breakTarget);
label.target = breakTarget;
return block;
}
private Node transformLetNode(LetNode node) {
pushScope(node);
try {
decompiler.addToken(Token.LET);
decompiler.addToken(Token.LP);
Node vars = transformVariableInitializers(node.getVariables());
decompiler.addToken(Token.RP);
node.addChildToBack(vars);
boolean letExpr = node.getType() == Token.LETEXPR;
if (node.getBody() != null) {
if (letExpr) {
decompiler.addName(" ");
} else {
decompiler.addEOL(Token.LC);
}
node.addChildToBack(transform(node.getBody()));
if (!letExpr) {
decompiler.addEOL(Token.RC);
}
}
return node;
} finally {
popScope();
}
}
private Node transformLiteral(AstNode node) {
decompiler.addToken(node.getType());
return node;
}
private Node transformName(Name node) {
decompiler.addName(node.getIdentifier());
return node;
}
private Node transformNewExpr(NewExpression node) {
decompiler.addToken(Token.NEW);
Node nx = createCallOrNew(Token.NEW, transform(node.getTarget()));
nx.setLineno(node.getLineno());
List args = node.getArguments();
decompiler.addToken(Token.LP);
for (int i = 0; i < args.size(); i++) {
AstNode arg = args.get(i);
nx.addChildToBack(transform(arg));
if (i < args.size() - 1) {
decompiler.addToken(Token.COMMA);
}
}
decompiler.addToken(Token.RP);
if (node.getInitializer() != null) {
nx.addChildToBack(transformObjectLiteral(node.getInitializer()));
}
return nx;
}
private Node transformNumber(NumberLiteral node) {
decompiler.addNumber(node.getNumber());
return node;
}
private Node transformObjectLiteral(ObjectLiteral node) {
if (node.isDestructuring()) {
return node;
}
// createObjectLiteral rewrites its argument as object
// creation plus object property entries, so later compiler
// stages don't need to know about object literals.
decompiler.addToken(Token.LC);
List elems = node.getElements();
Node object = new Node(Token.OBJECTLIT);
Object[] properties;
if (elems.isEmpty()) {
properties = ScriptRuntime.emptyArgs;
} else {
int size = elems.size(), i = 0;
properties = new Object[size];
for (ObjectProperty prop : elems) {
if (prop.isGetterMethod()) {
decompiler.addToken(Token.GET);
} else if (prop.isSetterMethod()) {
decompiler.addToken(Token.SET);
} else if (prop.isNormalMethod()) {
decompiler.addToken(Token.METHOD);
}
properties[i++] = getPropKey(prop.getLeft());
// OBJECTLIT is used as ':' in object literal for
// decompilation to solve spacing ambiguity.
if (!(prop.isMethod())) {
decompiler.addToken(Token.OBJECTLIT);
}
Node right = transform(prop.getRight());
if (prop.isGetterMethod()) {
right = createUnary(Token.GET, right);
} else if (prop.isSetterMethod()) {
right = createUnary(Token.SET, right);
} else if (prop.isNormalMethod()) {
right = createUnary(Token.METHOD, right);
}
object.addChildToBack(right);
if (i < size) {
decompiler.addToken(Token.COMMA);
}
}
}
decompiler.addToken(Token.RC);
object.putProp(Node.OBJECT_IDS_PROP, properties);
return object;
}
private Object getPropKey(Node id) {
Object key;
if (id instanceof Name) {
String s = ((Name)id).getIdentifier();
decompiler.addName(s);
key = ScriptRuntime.getIndexObject(s);
} else if (id instanceof StringLiteral) {
String s = ((StringLiteral)id).getValue();
decompiler.addString(s);
key = ScriptRuntime.getIndexObject(s);
} else if (id instanceof NumberLiteral) {
double n = ((NumberLiteral)id).getNumber();
decompiler.addNumber(n);
key = ScriptRuntime.getIndexObject(n);
} else {
throw Kit.codeBug();
}
return key;
}
private Node transformParenExpr(ParenthesizedExpression node) {
AstNode expr = node.getExpression();
decompiler.addToken(Token.LP);
int count = 1;
while (expr instanceof ParenthesizedExpression) {
decompiler.addToken(Token.LP);
count++;
expr = ((ParenthesizedExpression)expr).getExpression();
}
Node result = transform(expr);
for (int i = 0; i < count; i++) {
decompiler.addToken(Token.RP);
}
result.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return result;
}
private Node transformPropertyGet(PropertyGet node) {
Node target = transform(node.getTarget());
String name = node.getProperty().getIdentifier();
decompiler.addToken(Token.DOT);
decompiler.addName(name);
return createPropertyGet(target, null, name, 0);
}
private Node transformRegExp(RegExpLiteral node) {
decompiler.addRegexp(node.getValue(), node.getFlags());
currentScriptOrFn.addRegExp(node);
return node;
}
private Node transformReturn(ReturnStatement node) {
boolean expClosure = Boolean.TRUE.equals(node.getProp(Node.EXPRESSION_CLOSURE_PROP));
if (expClosure) {
decompiler.addName(" ");
} else {
decompiler.addToken(Token.RETURN);
}
AstNode rv = node.getReturnValue();
Node value = rv == null ? null : transform(rv);
if (!expClosure) decompiler.addEOL(Token.SEMI);
return rv == null
? new Node(Token.RETURN, node.getLineno())
: new Node(Token.RETURN, value, node.getLineno());
}
private Node transformScript(ScriptNode node) {
decompiler.addToken(Token.SCRIPT);
if (currentScope != null) Kit.codeBug();
currentScope = node;
Node body = new Node(Token.BLOCK);
for (Node kid : node) {
body.addChildToBack(transform((AstNode)kid));
}
node.removeChildren();
Node children = body.getFirstChild();
if (children != null) {
node.addChildrenToBack(children);
}
return node;
}
private Node transformString(StringLiteral node) {
decompiler.addString(node.getValue());
return Node.newString(node.getValue());
}
private Node transformSwitch(SwitchStatement node) {
// The switch will be rewritten from:
//
// switch (expr) {
// case test1: statements1;
// ...
// default: statementsDefault;
// ...
// case testN: statementsN;
// }
//
// to:
//
// {
// switch (expr) {
// case test1: goto label1;
// ...
// case testN: goto labelN;
// }
// goto labelDefault;
// label1:
// statements1;
// ...
// labelDefault:
// statementsDefault;
// ...
// labelN:
// statementsN;
// breakLabel:
// }
//
// where inside switch each "break;" without label will be replaced
// by "goto breakLabel".
//
// If the original switch does not have the default label, then
// after the switch he transformed code would contain this goto:
// goto breakLabel;
// instead of:
// goto labelDefault;
decompiler.addToken(Token.SWITCH);
decompiler.addToken(Token.LP);
Node switchExpr = transform(node.getExpression());
decompiler.addToken(Token.RP);
node.addChildToBack(switchExpr);
Node block = new Node(Token.BLOCK, node, node.getLineno());
decompiler.addEOL(Token.LC);
for (SwitchCase sc : node.getCases()) {
AstNode expr = sc.getExpression();
Node caseExpr = null;
if (expr != null) {
decompiler.addToken(Token.CASE);
caseExpr = transform(expr);
} else {
decompiler.addToken(Token.DEFAULT);
}
decompiler.addEOL(Token.COLON);
List stmts = sc.getStatements();
Node body = new Block();
if (stmts != null) {
for (AstNode kid : stmts) {
body.addChildToBack(transform(kid));
}
}
addSwitchCase(block, caseExpr, body);
}
decompiler.addEOL(Token.RC);
closeSwitch(block);
return block;
}
private Node transformThrow(ThrowStatement node) {
decompiler.addToken(Token.THROW);
Node value = transform(node.getExpression());
decompiler.addEOL(Token.SEMI);
return new Node(Token.THROW, value, node.getLineno());
}
private Node transformTry(TryStatement node) {
decompiler.addToken(Token.TRY);
decompiler.addEOL(Token.LC);
Node tryBlock = transform(node.getTryBlock());
decompiler.addEOL(Token.RC);
Node catchBlocks = new Block();
for (CatchClause cc : node.getCatchClauses()) {
decompiler.addToken(Token.CATCH);
decompiler.addToken(Token.LP);
String varName = cc.getVarName().getIdentifier();
decompiler.addName(varName);
Node catchCond = null;
AstNode ccc = cc.getCatchCondition();
if (ccc != null) {
decompiler.addName(" ");
decompiler.addToken(Token.IF);
catchCond = transform(ccc);
} else {
catchCond = new EmptyExpression();
}
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);
Node body = transform(cc.getBody());
decompiler.addEOL(Token.RC);
catchBlocks.addChildToBack(createCatch(varName, catchCond,
body, cc.getLineno()));
}
Node finallyBlock = null;
if (node.getFinallyBlock() != null) {
decompiler.addToken(Token.FINALLY);
decompiler.addEOL(Token.LC);
finallyBlock = transform(node.getFinallyBlock());
decompiler.addEOL(Token.RC);
}
return createTryCatchFinally(tryBlock, catchBlocks,
finallyBlock, node.getLineno());
}
private Node transformUnary(UnaryExpression node) {
int type = node.getType();
if (type == Token.DEFAULTNAMESPACE) {
return transformDefaultXmlNamepace(node);
}
if (node.isPrefix()) {
decompiler.addToken(type);
}
Node child = transform(node.getOperand());
if (node.isPostfix()) {
decompiler.addToken(type);
}
if (type == Token.INC || type == Token.DEC) {
return createIncDec(type, node.isPostfix(), child);
}
return createUnary(type, child);
}
private Node transformVariables(VariableDeclaration node) {
decompiler.addToken(node.getType());
transformVariableInitializers(node);
// Might be most robust to have parser record whether it was
// a variable declaration statement, possibly as a node property.
AstNode parent = node.getParent();
if (!(parent instanceof Loop)
&& !(parent instanceof LetNode)) {
decompiler.addEOL(Token.SEMI);
}
return node;
}
private Node transformVariableInitializers(VariableDeclaration node) {
List vars = node.getVariables();
int size = vars.size(), i = 0;
for (VariableInitializer var : vars) {
AstNode target = var.getTarget();
AstNode init = var.getInitializer();
Node left = null;
if (var.isDestructuring()) {
decompile(target); // decompile but don't transform
left = target;
} else {
left = transform(target);
}
Node right = null;
if (init != null) {
decompiler.addToken(Token.ASSIGN);
right = transform(init);
}
if (var.isDestructuring()) {
if (right == null) { // TODO: should this ever happen?
node.addChildToBack(left);
} else {
Node d = createDestructuringAssignment(node.getType(),
left, right);
node.addChildToBack(d);
}
} else {
if (right != null) {
left.addChildToBack(right);
}
node.addChildToBack(left);
}
if (i++ < size-1) {
decompiler.addToken(Token.COMMA);
}
}
return node;
}
private Node transformWhileLoop(WhileLoop loop) {
decompiler.addToken(Token.WHILE);
loop.setType(Token.LOOP);
pushScope(loop);
try {
decompiler.addToken(Token.LP);
Node cond = transform(loop.getCondition());
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);
Node body = transform(loop.getBody());
decompiler.addEOL(Token.RC);
return createLoop(loop, LOOP_WHILE, body, cond, null, null);
} finally {
popScope();
}
}
private Node transformWith(WithStatement node) {
decompiler.addToken(Token.WITH);
decompiler.addToken(Token.LP);
Node expr = transform(node.getExpression());
decompiler.addToken(Token.RP);
decompiler.addEOL(Token.LC);
Node stmt = transform(node.getStatement());
decompiler.addEOL(Token.RC);
return createWith(expr, stmt, node.getLineno());
}
private Node transformYield(Yield node) {
decompiler.addToken(Token.YIELD);
Node kid = node.getValue() == null ? null : transform(node.getValue());
if (kid != null)
return new Node(Token.YIELD, kid, node.getLineno());
else
return new Node(Token.YIELD, node.getLineno());
}
private Node transformXmlLiteral(XmlLiteral node) {
// a literal like {bar} is rewritten as
// new XML("" + bar + " ");
Node pnXML = new Node(Token.NEW, node.getLineno());
List frags = node.getFragments();
XmlString first = (XmlString)frags.get(0);
boolean anon = first.getXml().trim().startsWith("<>");
pnXML.addChildToBack(createName(anon ? "XMLList" : "XML"));
Node pn = null;
for (XmlFragment frag : frags) {
if (frag instanceof XmlString) {
String xml = ((XmlString)frag).getXml();
decompiler.addName(xml);
if (pn == null) {
pn = createString(xml);
} else {
pn = createBinary(Token.ADD, pn, createString(xml));
}
} else {
XmlExpression xexpr = (XmlExpression)frag;
boolean isXmlAttr = xexpr.isXmlAttribute();
Node expr;
decompiler.addToken(Token.LC);
if (xexpr.getExpression() instanceof EmptyExpression) {
expr = createString("");
} else {
expr = transform(xexpr.getExpression());
}
decompiler.addToken(Token.RC);
if (isXmlAttr) {
// Need to put the result in double quotes
expr = createUnary(Token.ESCXMLATTR, expr);
Node prepend = createBinary(Token.ADD,
createString("\""),
expr);
expr = createBinary(Token.ADD,
prepend,
createString("\""));
} else {
expr = createUnary(Token.ESCXMLTEXT, expr);
}
pn = createBinary(Token.ADD, pn, expr);
}
}
pnXML.addChildToBack(pn);
return pnXML;
}
private Node transformXmlMemberGet(XmlMemberGet node) {
XmlRef ref = node.getMemberRef();
Node pn = transform(node.getLeft());
int flags = ref.isAttributeAccess() ? Node.ATTRIBUTE_FLAG : 0;
if (node.getType() == Token.DOTDOT) {
flags |= Node.DESCENDANTS_FLAG;
decompiler.addToken(Token.DOTDOT);
} else {
decompiler.addToken(Token.DOT);
}
return transformXmlRef(pn, ref, flags);
}
// We get here if we weren't a child of a . or .. infix node
private Node transformXmlRef(XmlRef node) {
int memberTypeFlags = node.isAttributeAccess()
? Node.ATTRIBUTE_FLAG : 0;
return transformXmlRef(null, node, memberTypeFlags);
}
private Node transformXmlRef(Node pn, XmlRef node, int memberTypeFlags) {
if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) != 0)
decompiler.addToken(Token.XMLATTR);
Name namespace = node.getNamespace();
String ns = namespace != null ? namespace.getIdentifier() : null;
if (ns != null) {
decompiler.addName(ns);
decompiler.addToken(Token.COLONCOLON);
}
if (node instanceof XmlPropRef) {
String name = ((XmlPropRef)node).getPropName().getIdentifier();
decompiler.addName(name);
return createPropertyGet(pn, ns, name, memberTypeFlags);
} else {
decompiler.addToken(Token.LB);
Node expr = transform(((XmlElemRef)node).getExpression());
decompiler.addToken(Token.RB);
return createElementGet(pn, ns, expr, memberTypeFlags);
}
}
private Node transformDefaultXmlNamepace(UnaryExpression node) {
decompiler.addToken(Token.DEFAULT);
decompiler.addName(" xml");
decompiler.addName(" namespace");
decompiler.addToken(Token.ASSIGN);
Node child = transform(node.getOperand());
return createUnary(Token.DEFAULTNAMESPACE, child);
}
/**
* If caseExpression argument is null it indicates a default label.
*/
private void addSwitchCase(Node switchBlock, Node caseExpression,
Node statements)
{
if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
Jump switchNode = (Jump)switchBlock.getFirstChild();
if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();
Node gotoTarget = Node.newTarget();
if (caseExpression != null) {
Jump caseNode = new Jump(Token.CASE, caseExpression);
caseNode.target = gotoTarget;
switchNode.addChildToBack(caseNode);
} else {
switchNode.setDefault(gotoTarget);
}
switchBlock.addChildToBack(gotoTarget);
switchBlock.addChildToBack(statements);
}
private void closeSwitch(Node switchBlock)
{
if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
Jump switchNode = (Jump)switchBlock.getFirstChild();
if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();
Node switchBreakTarget = Node.newTarget();
// switchNode.target is only used by NodeTransformer
// to detect switch end
switchNode.target = switchBreakTarget;
Node defaultTarget = switchNode.getDefault();
if (defaultTarget == null) {
defaultTarget = switchBreakTarget;
}
switchBlock.addChildAfter(makeJump(Token.GOTO, defaultTarget),
switchNode);
switchBlock.addChildToBack(switchBreakTarget);
}
private Node createExprStatementNoReturn(Node expr, int lineno) {
return new Node(Token.EXPR_VOID, expr, lineno);
}
private Node createString(String string) {
return Node.newString(string);
}
/**
* Catch clause of try/catch/finally
* @param varName the name of the variable to bind to the exception
* @param catchCond the condition under which to catch the exception.
* May be null if no condition is given.
* @param stmts the statements in the catch clause
* @param lineno the starting line number of the catch clause
*/
private Node createCatch(String varName, Node catchCond, Node stmts,
int lineno) {
if (catchCond == null) {
catchCond = new Node(Token.EMPTY);
}
return new Node(Token.CATCH, createName(varName),
catchCond, stmts, lineno);
}
private Node initFunction(FunctionNode fnNode, int functionIndex,
Node statements, int functionType) {
fnNode.setFunctionType(functionType);
fnNode.addChildToBack(statements);
int functionCount = fnNode.getFunctionCount();
if (functionCount != 0) {
// Functions containing other functions require activation objects
fnNode.setRequiresActivation();
}
if (functionType == FunctionNode.FUNCTION_EXPRESSION) {
Name name = fnNode.getFunctionName();
if (name != null && name.length() != 0
&& fnNode.getSymbol(name.getIdentifier()) == null) {
// A function expression needs to have its name as a
// variable (if it isn't already allocated as a variable).
// See ECMA Ch. 13. We add code to the beginning of the
// function to initialize a local variable of the
// function's name to the function value, but only if the
// function doesn't already define a formal parameter, var,
// or nested function with the same name.
fnNode.putSymbol(new Symbol(Token.FUNCTION, name.getIdentifier()));
Node setFn = new Node(Token.EXPR_VOID,
new Node(Token.SETNAME,
Node.newString(Token.BINDNAME,
name.getIdentifier()),
new Node(Token.THISFN)));
statements.addChildrenToFront(setFn);
}
}
// Add return to end if needed.
Node lastStmt = statements.getLastChild();
if (lastStmt == null || lastStmt.getType() != Token.RETURN) {
statements.addChildToBack(new Node(Token.RETURN));
}
Node result = Node.newString(Token.FUNCTION, fnNode.getName());
result.putIntProp(Node.FUNCTION_PROP, functionIndex);
return result;
}
/**
* Create loop node. The code generator will later call
* createWhile|createDoWhile|createFor|createForIn
* to finish loop generation.
*/
private Scope createLoopNode(Node loopLabel, int lineno) {
Scope result = createScopeNode(Token.LOOP, lineno);
if (loopLabel != null) {
((Jump)loopLabel).setLoop(result);
}
return result;
}
private Node createFor(Scope loop, Node init,
Node test, Node incr, Node body) {
if (init.getType() == Token.LET) {
// rewrite "for (let i=s; i < N; i++)..." as
// "let (i=s) { for (; i < N; i++)..." so that "s" is evaluated
// outside the scope of the for.
Scope let = Scope.splitScope(loop);
let.setType(Token.LET);
let.addChildrenToBack(init);
let.addChildToBack(createLoop(loop, LOOP_FOR, body, test,
new Node(Token.EMPTY), incr));
return let;
}
return createLoop(loop, LOOP_FOR, body, test, init, incr);
}
private Node createLoop(Jump loop, int loopType, Node body,
Node cond, Node init, Node incr)
{
Node bodyTarget = Node.newTarget();
Node condTarget = Node.newTarget();
if (loopType == LOOP_FOR && cond.getType() == Token.EMPTY) {
cond = new Node(Token.TRUE);
}
Jump IFEQ = new Jump(Token.IFEQ, cond);
IFEQ.target = bodyTarget;
Node breakTarget = Node.newTarget();
loop.addChildToBack(bodyTarget);
loop.addChildrenToBack(body);
if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
// propagate lineno to condition
loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno()));
}
loop.addChildToBack(condTarget);
loop.addChildToBack(IFEQ);
loop.addChildToBack(breakTarget);
loop.target = breakTarget;
Node continueTarget = condTarget;
if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
// Just add a GOTO to the condition in the do..while
loop.addChildToFront(makeJump(Token.GOTO, condTarget));
if (loopType == LOOP_FOR) {
int initType = init.getType();
if (initType != Token.EMPTY) {
if (initType != Token.VAR && initType != Token.LET) {
init = new Node(Token.EXPR_VOID, init);
}
loop.addChildToFront(init);
}
Node incrTarget = Node.newTarget();
loop.addChildAfter(incrTarget, body);
if (incr.getType() != Token.EMPTY) {
incr = new Node(Token.EXPR_VOID, incr);
loop.addChildAfter(incr, incrTarget);
}
continueTarget = incrTarget;
}
}
loop.setContinue(continueTarget);
return loop;
}
/**
* Generate IR for a for..in loop.
*/
private Node createForIn(int declType, Node loop, Node lhs,
Node obj, Node body, boolean isForEach)
{
int destructuring = -1;
int destructuringLen = 0;
Node lvalue;
int type = lhs.getType();
if (type == Token.VAR || type == Token.LET) {
Node kid = lhs.getLastChild();
int kidType = kid.getType();
if (kidType == Token.ARRAYLIT || kidType == Token.OBJECTLIT)
{
type = destructuring = kidType;
lvalue = kid;
destructuringLen = 0;
if (kid instanceof ArrayLiteral)
destructuringLen = ((ArrayLiteral) kid).getDestructuringLength();
} else if (kidType == Token.NAME) {
lvalue = Node.newString(Token.NAME, kid.getString());
} else {
reportError("msg.bad.for.in.lhs");
return null;
}
} else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) {
destructuring = type;
lvalue = lhs;
destructuringLen = 0;
if (lhs instanceof ArrayLiteral)
destructuringLen = ((ArrayLiteral) lhs).getDestructuringLength();
} else {
lvalue = makeReference(lhs);
if (lvalue == null) {
reportError("msg.bad.for.in.lhs");
return null;
}
}
Node localBlock = new Node(Token.LOCAL_BLOCK);
int initType = isForEach ? Token.ENUM_INIT_VALUES
: (destructuring != -1
? Token.ENUM_INIT_ARRAY
: Token.ENUM_INIT_KEYS);
Node init = new Node(initType, obj);
init.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
Node cond = new Node(Token.ENUM_NEXT);
cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
Node id = new Node(Token.ENUM_ID);
id.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
Node newBody = new Node(Token.BLOCK);
Node assign;
if (destructuring != -1) {
assign = createDestructuringAssignment(declType, lvalue, id);
if (!isForEach && (destructuring == Token.OBJECTLIT ||
destructuringLen != 2))
{
// destructuring assignment is only allowed in for..each or
// with an array type of length 2 (to hold key and value)
reportError("msg.bad.for.in.destruct");
}
} else {
assign = simpleAssignment(lvalue, id);
}
newBody.addChildToBack(new Node(Token.EXPR_VOID, assign));
newBody.addChildToBack(body);
loop = createLoop((Jump)loop, LOOP_WHILE, newBody, cond, null, null);
loop.addChildToFront(init);
if (type == Token.VAR || type == Token.LET)
loop.addChildToFront(lhs);
localBlock.addChildToBack(loop);
return localBlock;
}
/**
* Try/Catch/Finally
*
* The IRFactory tries to express as much as possible in the tree;
* the responsibilities remaining for Codegen are to add the Java
* handlers: (Either (but not both) of TARGET and FINALLY might not
* be defined)
*
* - a catch handler for javascript exceptions that unwraps the
* exception onto the stack and GOTOes to the catch target
*
* - a finally handler
*
* ... and a goto to GOTO around these handlers.
*/
private Node createTryCatchFinally(Node tryBlock, Node catchBlocks,
Node finallyBlock, int lineno)
{
boolean hasFinally = (finallyBlock != null)
&& (finallyBlock.getType() != Token.BLOCK
|| finallyBlock.hasChildren());
// short circuit
if (tryBlock.getType() == Token.BLOCK && !tryBlock.hasChildren()
&& !hasFinally)
{
return tryBlock;
}
boolean hasCatch = catchBlocks.hasChildren();
// short circuit
if (!hasFinally && !hasCatch) {
// bc finally might be an empty block...
return tryBlock;
}
Node handlerBlock = new Node(Token.LOCAL_BLOCK);
Jump pn = new Jump(Token.TRY, tryBlock, lineno);
pn.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
if (hasCatch) {
// jump around catch code
Node endCatch = Node.newTarget();
pn.addChildToBack(makeJump(Token.GOTO, endCatch));
// make a TARGET for the catch that the tcf node knows about
Node catchTarget = Node.newTarget();
pn.target = catchTarget;
// mark it
pn.addChildToBack(catchTarget);
//
// Given
//
// try {
// tryBlock;
// } catch (e if condition1) {
// something1;
// ...
//
// } catch (e if conditionN) {
// somethingN;
// } catch (e) {
// somethingDefault;
// }
//
// rewrite as
//
// try {
// tryBlock;
// goto after_catch:
// } catch (x) {
// with (newCatchScope(e, x)) {
// if (condition1) {
// something1;
// goto after_catch;
// }
// }
// ...
// with (newCatchScope(e, x)) {
// if (conditionN) {
// somethingN;
// goto after_catch;
// }
// }
// with (newCatchScope(e, x)) {
// somethingDefault;
// goto after_catch;
// }
// }
// after_catch:
//
// If there is no default catch, then the last with block
// arround "somethingDefault;" is replaced by "rethrow;"
// It is assumed that catch handler generation will store
// exeception object in handlerBlock register
// Block with local for exception scope objects
Node catchScopeBlock = new Node(Token.LOCAL_BLOCK);
// expects catchblocks children to be (cond block) pairs.
Node cb = catchBlocks.getFirstChild();
boolean hasDefault = false;
int scopeIndex = 0;
while (cb != null) {
int catchLineNo = cb.getLineno();
Node name = cb.getFirstChild();
Node cond = name.getNext();
Node catchStatement = cond.getNext();
cb.removeChild(name);
cb.removeChild(cond);
cb.removeChild(catchStatement);
// Add goto to the catch statement to jump out of catch
// but prefix it with LEAVEWITH since try..catch produces
// "with"code in order to limit the scope of the exception
// object.
catchStatement.addChildToBack(new Node(Token.LEAVEWITH));
catchStatement.addChildToBack(makeJump(Token.GOTO, endCatch));
// Create condition "if" when present
Node condStmt;
if (cond.getType() == Token.EMPTY) {
condStmt = catchStatement;
hasDefault = true;
} else {
condStmt = createIf(cond, catchStatement, null,
catchLineNo);
}
// Generate code to create the scope object and store
// it in catchScopeBlock register
Node catchScope = new Node(Token.CATCH_SCOPE, name,
createUseLocal(handlerBlock));
catchScope.putProp(Node.LOCAL_BLOCK_PROP, catchScopeBlock);
catchScope.putIntProp(Node.CATCH_SCOPE_PROP, scopeIndex);
catchScopeBlock.addChildToBack(catchScope);
// Add with statement based on catch scope object
catchScopeBlock.addChildToBack(
createWith(createUseLocal(catchScopeBlock), condStmt,
catchLineNo));
// move to next cb
cb = cb.getNext();
++scopeIndex;
}
pn.addChildToBack(catchScopeBlock);
if (!hasDefault) {
// Generate code to rethrow if no catch clause was executed
Node rethrow = new Node(Token.RETHROW);
rethrow.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
pn.addChildToBack(rethrow);
}
pn.addChildToBack(endCatch);
}
if (hasFinally) {
Node finallyTarget = Node.newTarget();
pn.setFinally(finallyTarget);
// add jsr finally to the try block
pn.addChildToBack(makeJump(Token.JSR, finallyTarget));
// jump around finally code
Node finallyEnd = Node.newTarget();
pn.addChildToBack(makeJump(Token.GOTO, finallyEnd));
pn.addChildToBack(finallyTarget);
Node fBlock = new Node(Token.FINALLY, finallyBlock);
fBlock.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
pn.addChildToBack(fBlock);
pn.addChildToBack(finallyEnd);
}
handlerBlock.addChildToBack(pn);
return handlerBlock;
}
private Node createWith(Node obj, Node body, int lineno) {
setRequiresActivation();
Node result = new Node(Token.BLOCK, lineno);
result.addChildToBack(new Node(Token.ENTERWITH, obj));
Node bodyNode = new Node(Token.WITH, body, lineno);
result.addChildrenToBack(bodyNode);
result.addChildToBack(new Node(Token.LEAVEWITH));
return result;
}
private Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno)
{
int condStatus = isAlwaysDefinedBoolean(cond);
if (condStatus == ALWAYS_TRUE_BOOLEAN) {
return ifTrue;
} else if (condStatus == ALWAYS_FALSE_BOOLEAN) {
if (ifFalse != null) {
return ifFalse;
}
// Replace if (false) xxx by empty block
return new Node(Token.BLOCK, lineno);
}
Node result = new Node(Token.BLOCK, lineno);
Node ifNotTarget = Node.newTarget();
Jump IFNE = new Jump(Token.IFNE, cond);
IFNE.target = ifNotTarget;
result.addChildToBack(IFNE);
result.addChildrenToBack(ifTrue);
if (ifFalse != null) {
Node endTarget = Node.newTarget();
result.addChildToBack(makeJump(Token.GOTO, endTarget));
result.addChildToBack(ifNotTarget);
result.addChildrenToBack(ifFalse);
result.addChildToBack(endTarget);
} else {
result.addChildToBack(ifNotTarget);
}
return result;
}
private Node createCondExpr(Node cond, Node ifTrue, Node ifFalse) {
int condStatus = isAlwaysDefinedBoolean(cond);
if (condStatus == ALWAYS_TRUE_BOOLEAN) {
return ifTrue;
} else if (condStatus == ALWAYS_FALSE_BOOLEAN) {
return ifFalse;
}
return new Node(Token.HOOK, cond, ifTrue, ifFalse);
}
private Node createUnary(int nodeType, Node child)
{
int childType = child.getType();
switch (nodeType) {
case Token.DELPROP: {
Node n;
if (childType == Token.NAME) {
// Transform Delete(Name "a")
// to Delete(Bind("a"), String("a"))
child.setType(Token.BINDNAME);
Node left = child;
Node right = Node.newString(child.getString());
n = new Node(nodeType, left, right);
} else if (childType == Token.GETPROP ||
childType == Token.GETELEM)
{
Node left = child.getFirstChild();
Node right = child.getLastChild();
child.removeChild(left);
child.removeChild(right);
n = new Node(nodeType, left, right);
} else if (childType == Token.GET_REF) {
Node ref = child.getFirstChild();
child.removeChild(ref);
n = new Node(Token.DEL_REF, ref);
} else {
// Always evaluate delete operand, see ES5 11.4.1 & bug #726121
n = new Node(nodeType, new Node(Token.TRUE), child);
}
return n;
}
case Token.TYPEOF:
if (childType == Token.NAME) {
child.setType(Token.TYPEOFNAME);
return child;
}
break;
case Token.BITNOT:
if (childType == Token.NUMBER) {
int value = ScriptRuntime.toInt32(child.getDouble());
child.setDouble(~value);
return child;
}
break;
case Token.NEG:
if (childType == Token.NUMBER) {
child.setDouble(-child.getDouble());
return child;
}
break;
case Token.NOT: {
int status = isAlwaysDefinedBoolean(child);
if (status != 0) {
int type;
if (status == ALWAYS_TRUE_BOOLEAN) {
type = Token.FALSE;
} else {
type = Token.TRUE;
}
if (childType == Token.TRUE || childType == Token.FALSE) {
child.setType(type);
return child;
}
return new Node(type);
}
break;
}
}
return new Node(nodeType, child);
}
private Node createCallOrNew(int nodeType, Node child) {
int type = Node.NON_SPECIALCALL;
if (child.getType() == Token.NAME) {
String name = child.getString();
if (name.equals("eval")) {
type = Node.SPECIALCALL_EVAL;
} else if (name.equals("With")) {
type = Node.SPECIALCALL_WITH;
}
} else if (child.getType() == Token.GETPROP) {
String name = child.getLastChild().getString();
if (name.equals("eval")) {
type = Node.SPECIALCALL_EVAL;
}
}
Node node = new Node(nodeType, child);
if (type != Node.NON_SPECIALCALL) {
// Calls to these functions require activation objects.
setRequiresActivation();
node.putIntProp(Node.SPECIALCALL_PROP, type);
}
return node;
}
private Node createIncDec(int nodeType, boolean post, Node child)
{
child = makeReference(child);
int childType = child.getType();
switch (childType) {
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
case Token.GET_REF: {
Node n = new Node(nodeType, child);
int incrDecrMask = 0;
if (nodeType == Token.DEC) {
incrDecrMask |= Node.DECR_FLAG;
}
if (post) {
incrDecrMask |= Node.POST_FLAG;
}
n.putIntProp(Node.INCRDECR_PROP, incrDecrMask);
return n;
}
}
throw Kit.codeBug();
}
private Node createPropertyGet(Node target, String namespace, String name,
int memberTypeFlags)
{
if (namespace == null && memberTypeFlags == 0) {
if (target == null) {
return createName(name);
}
checkActivationName(name, Token.GETPROP);
if (ScriptRuntime.isSpecialProperty(name)) {
Node ref = new Node(Token.REF_SPECIAL, target);
ref.putProp(Node.NAME_PROP, name);
return new Node(Token.GET_REF, ref);
}
return new Node(Token.GETPROP, target, Node.newString(name));
}
Node elem = Node.newString(name);
memberTypeFlags |= Node.PROPERTY_FLAG;
return createMemberRefGet(target, namespace, elem, memberTypeFlags);
}
/**
* @param target the node before the LB
* @param namespace optional namespace
* @param elem the node in the brackets
* @param memberTypeFlags E4X flags
*/
private Node createElementGet(Node target, String namespace, Node elem,
int memberTypeFlags)
{
// OPT: could optimize to createPropertyGet
// iff elem is string that can not be number
if (namespace == null && memberTypeFlags == 0) {
// stand-alone [aaa] as primary expression is array literal
// declaration and should not come here!
if (target == null) throw Kit.codeBug();
return new Node(Token.GETELEM, target, elem);
}
return createMemberRefGet(target, namespace, elem, memberTypeFlags);
}
private Node createMemberRefGet(Node target, String namespace, Node elem,
int memberTypeFlags)
{
Node nsNode = null;
if (namespace != null) {
// See 11.1.2 in ECMA 357
if (namespace.equals("*")) {
nsNode = new Node(Token.NULL);
} else {
nsNode = createName(namespace);
}
}
Node ref;
if (target == null) {
if (namespace == null) {
ref = new Node(Token.REF_NAME, elem);
} else {
ref = new Node(Token.REF_NS_NAME, nsNode, elem);
}
} else {
if (namespace == null) {
ref = new Node(Token.REF_MEMBER, target, elem);
} else {
ref = new Node(Token.REF_NS_MEMBER, target, nsNode, elem);
}
}
if (memberTypeFlags != 0) {
ref.putIntProp(Node.MEMBER_TYPE_PROP, memberTypeFlags);
}
return new Node(Token.GET_REF, ref);
}
private Node createBinary(int nodeType, Node left, Node right) {
switch (nodeType) {
case Token.ADD:
// numerical addition and string concatenation
if (left.type == Token.STRING) {
String s2;
if (right.type == Token.STRING) {
s2 = right.getString();
} else if (right.type == Token.NUMBER) {
s2 = ScriptRuntime.numberToString(right.getDouble(), 10);
} else {
break;
}
String s1 = left.getString();
left.setString(s1.concat(s2));
return left;
} else if (left.type == Token.NUMBER) {
if (right.type == Token.NUMBER) {
left.setDouble(left.getDouble() + right.getDouble());
return left;
} else if (right.type == Token.STRING) {
String s1, s2;
s1 = ScriptRuntime.numberToString(left.getDouble(), 10);
s2 = right.getString();
right.setString(s1.concat(s2));
return right;
}
}
// can't do anything if we don't know both types - since
// 0 + object is supposed to call toString on the object and do
// string concantenation rather than addition
break;
case Token.SUB:
// numerical subtraction
if (left.type == Token.NUMBER) {
double ld = left.getDouble();
if (right.type == Token.NUMBER) {
//both numbers
left.setDouble(ld - right.getDouble());
return left;
} else if (ld == 0.0) {
// first 0: 0-x -> -x
return new Node(Token.NEG, right);
}
} else if (right.type == Token.NUMBER) {
if (right.getDouble() == 0.0) {
//second 0: x - 0 -> +x
// can not make simply x because x - 0 must be number
return new Node(Token.POS, left);
}
}
break;
case Token.MUL:
// numerical multiplication
if (left.type == Token.NUMBER) {
double ld = left.getDouble();
if (right.type == Token.NUMBER) {
//both numbers
left.setDouble(ld * right.getDouble());
return left;
} else if (ld == 1.0) {
// first 1: 1 * x -> +x
return new Node(Token.POS, right);
}
} else if (right.type == Token.NUMBER) {
if (right.getDouble() == 1.0) {
//second 1: x * 1 -> +x
// can not make simply x because x - 0 must be number
return new Node(Token.POS, left);
}
}
// can't do x*0: Infinity * 0 gives NaN, not 0
break;
case Token.DIV:
// number division
if (right.type == Token.NUMBER) {
double rd = right.getDouble();
if (left.type == Token.NUMBER) {
// both constants -- just divide, trust Java to handle x/0
left.setDouble(left.getDouble() / rd);
return left;
} else if (rd == 1.0) {
// second 1: x/1 -> +x
// not simply x to force number convertion
return new Node(Token.POS, left);
}
}
break;
case Token.AND: {
// Since x && y gives x, not false, when Boolean(x) is false,
// and y, not Boolean(y), when Boolean(x) is true, x && y
// can only be simplified if x is defined. See bug 309957.
int leftStatus = isAlwaysDefinedBoolean(left);
if (leftStatus == ALWAYS_FALSE_BOOLEAN) {
// if the first one is false, just return it
return left;
} else if (leftStatus == ALWAYS_TRUE_BOOLEAN) {
// if first is true, set to second
return right;
}
break;
}
case Token.OR: {
// Since x || y gives x, not true, when Boolean(x) is true,
// and y, not Boolean(y), when Boolean(x) is false, x || y
// can only be simplified if x is defined. See bug 309957.
int leftStatus = isAlwaysDefinedBoolean(left);
if (leftStatus == ALWAYS_TRUE_BOOLEAN) {
// if the first one is true, just return it
return left;
} else if (leftStatus == ALWAYS_FALSE_BOOLEAN) {
// if first is false, set to second
return right;
}
break;
}
}
return new Node(nodeType, left, right);
}
private Node createAssignment(int assignType, Node left, Node right)
{
Node ref = makeReference(left);
if (ref == null) {
if (left.getType() == Token.ARRAYLIT ||
left.getType() == Token.OBJECTLIT)
{
if (assignType != Token.ASSIGN) {
reportError("msg.bad.destruct.op");
return right;
}
return createDestructuringAssignment(-1, left, right);
}
reportError("msg.bad.assign.left");
return right;
}
left = ref;
int assignOp;
switch (assignType) {
case Token.ASSIGN:
return simpleAssignment(left, right);
case Token.ASSIGN_BITOR: assignOp = Token.BITOR; break;
case Token.ASSIGN_BITXOR: assignOp = Token.BITXOR; break;
case Token.ASSIGN_BITAND: assignOp = Token.BITAND; break;
case Token.ASSIGN_LSH: assignOp = Token.LSH; break;
case Token.ASSIGN_RSH: assignOp = Token.RSH; break;
case Token.ASSIGN_URSH: assignOp = Token.URSH; break;
case Token.ASSIGN_ADD: assignOp = Token.ADD; break;
case Token.ASSIGN_SUB: assignOp = Token.SUB; break;
case Token.ASSIGN_MUL: assignOp = Token.MUL; break;
case Token.ASSIGN_DIV: assignOp = Token.DIV; break;
case Token.ASSIGN_MOD: assignOp = Token.MOD; break;
default: throw Kit.codeBug();
}
int nodeType = left.getType();
switch (nodeType) {
case Token.NAME: {
Node op = new Node(assignOp, left, right);
Node lvalueLeft = Node.newString(Token.BINDNAME, left.getString());
return new Node(Token.SETNAME, lvalueLeft, op);
}
case Token.GETPROP:
case Token.GETELEM: {
Node obj = left.getFirstChild();
Node id = left.getLastChild();
int type = nodeType == Token.GETPROP
? Token.SETPROP_OP
: Token.SETELEM_OP;
Node opLeft = new Node(Token.USE_STACK);
Node op = new Node(assignOp, opLeft, right);
return new Node(type, obj, id, op);
}
case Token.GET_REF: {
ref = left.getFirstChild();
checkMutableReference(ref);
Node opLeft = new Node(Token.USE_STACK);
Node op = new Node(assignOp, opLeft, right);
return new Node(Token.SET_REF_OP, ref, op);
}
}
throw Kit.codeBug();
}
private Node createUseLocal(Node localBlock) {
if (Token.LOCAL_BLOCK != localBlock.getType()) throw Kit.codeBug();
Node result = new Node(Token.LOCAL_LOAD);
result.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
return result;
}
private Jump makeJump(int type, Node target) {
Jump n = new Jump(type);
n.target = target;
return n;
}
private Node makeReference(Node node) {
int type = node.getType();
switch (type) {
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
case Token.GET_REF:
return node;
case Token.CALL:
node.setType(Token.REF_CALL);
return new Node(Token.GET_REF, node);
}
// Signal caller to report error
return null;
}
// Check if Node always mean true or false in boolean context
private static int isAlwaysDefinedBoolean(Node node) {
switch (node.getType()) {
case Token.FALSE:
case Token.NULL:
return ALWAYS_FALSE_BOOLEAN;
case Token.TRUE:
return ALWAYS_TRUE_BOOLEAN;
case Token.NUMBER: {
double num = node.getDouble();
if (num == num && num != 0.0) {
return ALWAYS_TRUE_BOOLEAN;
} else {
return ALWAYS_FALSE_BOOLEAN;
}
}
}
return 0;
}
// Check if node is the target of a destructuring bind.
boolean isDestructuring(Node n) {
return n instanceof DestructuringForm
&& ((DestructuringForm)n).isDestructuring();
}
Node decompileFunctionHeader(FunctionNode fn) {
Node mexpr = null;
if (fn.getFunctionName() != null) {
decompiler.addName(fn.getName());
} else if (fn.getMemberExprNode() != null) {
mexpr = transform(fn.getMemberExprNode());
}
decompiler.addToken(Token.LP);
List params = fn.getParams();
for (int i = 0; i < params.size(); i++) {
decompile(params.get(i));
if (i < params.size() - 1) {
decompiler.addToken(Token.COMMA);
}
}
decompiler.addToken(Token.RP);
if (!fn.isExpressionClosure()) {
decompiler.addEOL(Token.LC);
}
return mexpr;
}
void decompile(AstNode node) {
switch (node.getType()) {
case Token.ARRAYLIT:
decompileArrayLiteral((ArrayLiteral)node);
break;
case Token.OBJECTLIT:
decompileObjectLiteral((ObjectLiteral)node);
break;
case Token.STRING:
decompiler.addString(((StringLiteral)node).getValue());
break;
case Token.NAME:
decompiler.addName(((Name)node).getIdentifier());
break;
case Token.NUMBER:
decompiler.addNumber(((NumberLiteral)node).getNumber());
break;
case Token.GETPROP:
decompilePropertyGet((PropertyGet)node);
break;
case Token.EMPTY:
break;
case Token.GETELEM:
decompileElementGet((ElementGet) node);
break;
case Token.THIS:
decompiler.addToken(node.getType());
break;
default:
Kit.codeBug("unexpected token: "
+ Token.typeToName(node.getType()));
}
}
// used for destructuring forms, since we don't transform() them
void decompileArrayLiteral(ArrayLiteral node) {
decompiler.addToken(Token.LB);
List elems = node.getElements();
int size = elems.size();
for (int i = 0; i < size; i++) {
AstNode elem = elems.get(i);
decompile(elem);
if (i < size - 1) {
decompiler.addToken(Token.COMMA);
}
}
decompiler.addToken(Token.RB);
}
// only used for destructuring forms
void decompileObjectLiteral(ObjectLiteral node) {
decompiler.addToken(Token.LC);
List props = node.getElements();
int size = props.size();
for (int i = 0; i < size; i++) {
ObjectProperty prop = props.get(i);
boolean destructuringShorthand =
Boolean.TRUE.equals(prop.getProp(Node.DESTRUCTURING_SHORTHAND));
decompile(prop.getLeft());
if (!destructuringShorthand) {
decompiler.addToken(Token.COLON);
decompile(prop.getRight());
}
if (i < size - 1) {
decompiler.addToken(Token.COMMA);
}
}
decompiler.addToken(Token.RC);
}
// only used for destructuring forms
void decompilePropertyGet(PropertyGet node) {
decompile(node.getTarget());
decompiler.addToken(Token.DOT);
decompile(node.getProperty());
}
// only used for destructuring forms
void decompileElementGet(ElementGet node) {
decompile(node.getTarget());
decompiler.addToken(Token.LB);
decompile(node.getElement());
decompiler.addToken(Token.RB);
}
}