/****************************************************************************** * Copyright (c) 2002 - 2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *****************************************************************************/ package com.ibm.wala.cast.js.translator; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.mozilla.javascript.CompilerEnvirons; import org.mozilla.javascript.ErrorReporter; import org.mozilla.javascript.EvaluatorException; import org.mozilla.javascript.Node; import org.mozilla.javascript.Parser; import org.mozilla.javascript.Token; import org.mozilla.javascript.ast.ArrayComprehension; import org.mozilla.javascript.ast.ArrayComprehensionLoop; import org.mozilla.javascript.ast.ArrayLiteral; import org.mozilla.javascript.ast.Assignment; import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstRoot; import org.mozilla.javascript.ast.Block; import org.mozilla.javascript.ast.BreakStatement; import org.mozilla.javascript.ast.CatchClause; import org.mozilla.javascript.ast.Comment; import org.mozilla.javascript.ast.ConditionalExpression; import org.mozilla.javascript.ast.ContinueStatement; import org.mozilla.javascript.ast.DoLoop; import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.EmptyExpression; import org.mozilla.javascript.ast.EmptyStatement; import org.mozilla.javascript.ast.ErrorNode; import org.mozilla.javascript.ast.ExpressionStatement; import org.mozilla.javascript.ast.ForInLoop; import org.mozilla.javascript.ast.ForLoop; import org.mozilla.javascript.ast.FunctionCall; import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.ast.IfStatement; import org.mozilla.javascript.ast.InfixExpression; import org.mozilla.javascript.ast.Jump; import org.mozilla.javascript.ast.KeywordLiteral; import org.mozilla.javascript.ast.Label; import org.mozilla.javascript.ast.LabeledStatement; import org.mozilla.javascript.ast.LetNode; import org.mozilla.javascript.ast.Name; import org.mozilla.javascript.ast.NewExpression; import org.mozilla.javascript.ast.NumberLiteral; import org.mozilla.javascript.ast.ObjectLiteral; import org.mozilla.javascript.ast.ObjectProperty; import org.mozilla.javascript.ast.ParenthesizedExpression; import org.mozilla.javascript.ast.PropertyGet; import org.mozilla.javascript.ast.RegExpLiteral; import org.mozilla.javascript.ast.ReturnStatement; import org.mozilla.javascript.ast.Scope; import org.mozilla.javascript.ast.ScriptNode; import org.mozilla.javascript.ast.StringLiteral; import org.mozilla.javascript.ast.SwitchCase; import org.mozilla.javascript.ast.SwitchStatement; import org.mozilla.javascript.ast.Symbol; import org.mozilla.javascript.ast.ThrowStatement; import org.mozilla.javascript.ast.TryStatement; import org.mozilla.javascript.ast.UnaryExpression; import org.mozilla.javascript.ast.VariableDeclaration; import org.mozilla.javascript.ast.VariableInitializer; import org.mozilla.javascript.ast.WhileLoop; import org.mozilla.javascript.ast.WithStatement; import org.mozilla.javascript.ast.XmlDotQuery; import org.mozilla.javascript.ast.XmlElemRef; import org.mozilla.javascript.ast.XmlExpression; import org.mozilla.javascript.ast.XmlFragment; import org.mozilla.javascript.ast.XmlLiteral; import org.mozilla.javascript.ast.XmlMemberGet; import org.mozilla.javascript.ast.XmlPropRef; import org.mozilla.javascript.ast.XmlRef; import org.mozilla.javascript.ast.XmlString; import org.mozilla.javascript.ast.Yield; import com.ibm.wala.cast.ir.translator.TranslatorToCAst; import com.ibm.wala.cast.js.html.MappedSourceModule; import com.ibm.wala.cast.js.ipa.callgraph.JSSSAPropagationCallGraphBuilder; import com.ibm.wala.cast.js.loader.JavaScriptLoader; import com.ibm.wala.cast.js.types.JavaScriptTypes; import com.ibm.wala.cast.tree.CAst; import com.ibm.wala.cast.tree.CAstAnnotation; import com.ibm.wala.cast.tree.CAstControlFlowMap; import com.ibm.wala.cast.tree.CAstEntity; import com.ibm.wala.cast.tree.CAstNode; import com.ibm.wala.cast.tree.CAstNodeTypeMap; import com.ibm.wala.cast.tree.CAstQualifier; import com.ibm.wala.cast.tree.CAstSourcePositionMap; import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.cast.tree.CAstType; import com.ibm.wala.cast.tree.impl.CAstOperator; import com.ibm.wala.cast.tree.impl.CAstSymbolImpl; import com.ibm.wala.cast.tree.impl.RangePosition; import com.ibm.wala.cast.tree.rewrite.CAstRewriter.CopyKey; import com.ibm.wala.cast.tree.rewrite.CAstRewriter.RewriteContext; import com.ibm.wala.cast.tree.rewrite.CAstRewriterFactory; import com.ibm.wala.cast.tree.visit.CAstVisitor; import com.ibm.wala.cast.util.CAstPattern; import com.ibm.wala.classLoader.ModuleEntry; import com.ibm.wala.classLoader.SourceModule; import com.ibm.wala.util.collections.EmptyIterator; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.debug.Assertions; import com.ibm.wala.util.warnings.Warning; public class RhinoToAstTranslator implements TranslatorToCAst { /** * a dummy name to use for standard function calls, only used to distinguish * them from constructor calls */ public static final String STANDARD_CALL_FN_NAME = "do"; /** * name used for constructor calls, used to distinguish them from standard * function calls */ public static final String CTOR_CALL_FN_NAME = "ctor"; private final boolean DEBUG = false; /** * shared interface for all objects storing contextual information during the * Rhino AST traversal * */ private interface WalkContext extends JavaScriptTranslatorToCAst.WalkContext { } /** * default implementation of WalkContext; methods do nothing / return null * */ private static class RootContext extends JavaScriptTranslatorToCAst.RootContext implements WalkContext { } /** * context used for function / script declarations */ private static class FunctionContext extends JavaScriptTranslatorToCAst.FunctionContext implements WalkContext { FunctionContext(WalkContext parent, Node s) { super(parent, s); } } /** * context used for top-level script declarations */ private static class ScriptContext extends FunctionContext { private final String script; ScriptContext(WalkContext parent, ScriptNode s, String script) { super(parent, s); this.script = script; } @Override public String script() { return script; } } private static class MemberDestructuringContext extends JavaScriptTranslatorToCAst.MemberDestructuringContext implements WalkContext { protected MemberDestructuringContext(WalkContext parent, Node initialBaseFor, int operationIndex) { super(parent, initialBaseFor, operationIndex); } } private static class BreakContext extends JavaScriptTranslatorToCAst.BreakContext implements WalkContext { @Override public WalkContext getParent() { return (WalkContext) super.getParent(); } BreakContext(WalkContext parent, Node breakTo, String label) { super(parent, breakTo, label); } } private static class LoopContext extends TranslatorToCAst.LoopContext implements WalkContext { LoopContext(WalkContext parent, Node breakTo, Node continueTo, String label) { super(parent, breakTo, continueTo, label); } @Override public WalkContext getParent() { return (WalkContext) super.getParent(); } } private static class TryCatchContext extends TranslatorToCAst.TryCatchContext implements WalkContext { TryCatchContext(WalkContext parent, CAstNode catchNode) { super(parent, catchNode); } @Override public WalkContext getParent() { return (WalkContext) super.getParent(); } } private static String operationReceiverName(int operationIndex) { return "$$destructure$rcvr" + operationIndex; } private CAstNode operationReceiverVar(int operationIndex) { return Ast.makeNode(CAstNode.VAR, Ast.makeConstant(operationReceiverName(operationIndex))); } private static String operationElementName(int operationIndex) { return "$$destructure$elt" + operationIndex; } private CAstNode operationElementVar(int operationIndex) { return Ast.makeNode(CAstNode.VAR, Ast.makeConstant(operationElementName(operationIndex))); } private static CAstNode translateOpcode(int nodeType) { switch (nodeType) { case Token.POS: case Token.ADD: case Token.ASSIGN_ADD: return CAstOperator.OP_ADD; case Token.DIV: case Token.ASSIGN_DIV: return CAstOperator.OP_DIV; case Token.ASSIGN_LSH: case Token.LSH: return CAstOperator.OP_LSH; case Token.MOD: case Token.ASSIGN_MOD: return CAstOperator.OP_MOD; case Token.MUL: case Token.ASSIGN_MUL: return CAstOperator.OP_MUL; case Token.RSH: case Token.ASSIGN_RSH: return CAstOperator.OP_RSH; case Token.SUB: case Token.NEG: case Token.ASSIGN_SUB: return CAstOperator.OP_SUB; case Token.URSH: case Token.ASSIGN_URSH: return CAstOperator.OP_URSH; case Token.BITAND: case Token.ASSIGN_BITAND: return CAstOperator.OP_BIT_AND; case Token.BITOR: case Token.ASSIGN_BITOR: return CAstOperator.OP_BIT_OR; case Token.BITXOR: case Token.ASSIGN_BITXOR: return CAstOperator.OP_BIT_XOR; case Token.EQ: return CAstOperator.OP_EQ; case Token.SHEQ: return CAstOperator.OP_STRICT_EQ; case Token.IFEQ: return CAstOperator.OP_EQ; case Token.GE: return CAstOperator.OP_GE; case Token.GT: return CAstOperator.OP_GT; case Token.LE: return CAstOperator.OP_LE; case Token.LT: return CAstOperator.OP_LT; case Token.NE: return CAstOperator.OP_NE; case Token.SHNE: return CAstOperator.OP_STRICT_NE; case Token.IFNE: return CAstOperator.OP_NE; case Token.BITNOT: return CAstOperator.OP_BITNOT; case Token.NOT: return CAstOperator.OP_NOT; default: Assertions.UNREACHABLE(); return null; } } private CAstNode makeBuiltinNew(String typeName) { return Ast.makeNode(CAstNode.NEW, Ast.makeConstant(typeName)); } private CAstNode handleNew(WalkContext context, String globalName, CAstNode arguments[]) { return handleNew(context, readName(context, null, globalName), arguments); } private CAstNode handleNew(WalkContext context, CAstNode value, CAstNode arguments[]) { return makeCtorCall(value, arguments, context); } private static boolean isPrologueScript(WalkContext context) { return JavaScriptLoader.bootstrapFileNames.contains(context.script()); } private static Node getCallTarget(FunctionCall n) { return n.getTarget(); } /** * is n a call to "primitive" within our synthetic modeling code? */ private static boolean isPrimitiveCall(WalkContext context, FunctionCall n) { return isPrologueScript(context) && n.getType() == Token.CALL && getCallTarget(n).getType() == Token.NAME && getCallTarget(n).getString().equals("primitive"); } private static Node getNewTarget(NewExpression n) { return n.getTarget(); } private static boolean isPrimitiveCreation(WalkContext context, NewExpression n) { Node target = getNewTarget(n); return isPrologueScript(context) && n.getType() == Token.NEW && target.getType() == Token.NAME && target.getString().equals("Primitives"); } private CAstNode makeCall(CAstNode fun, CAstNode thisptr, CAstNode args[], WalkContext context) { return makeCall(fun, thisptr, args, context, STANDARD_CALL_FN_NAME); } private CAstNode makeCtorCall(CAstNode thisptr, CAstNode args[], WalkContext context) { return makeCall(thisptr, null, args, context, CTOR_CALL_FN_NAME); } private CAstNode makeCall(CAstNode fun, CAstNode thisptr, CAstNode args[], WalkContext context, String callee) { int children = (args == null)? 0 : args.length; // children of CAst CALL node are the expression that evaluates to the // function, followed by a name (either STANDARD_CALL_FN_NAME or // CTOR_CALL_FN_NAME), followed by the actual // parameters int nargs = (thisptr == null) ? children + 2 : children + 3; int i = 0; CAstNode arguments[] = new CAstNode[nargs]; arguments[i++] = fun; // assert callee.equals(STANDARD_CALL_FN_NAME) || callee.equals(CTOR_CALL_FN_NAME); arguments[i++] = Ast.makeConstant(callee); if (thisptr != null) arguments[i++] = thisptr; if (args != null) { for (CAstNode arg : args) { arguments[i++] = arg; } } CAstNode call = Ast.makeNode(CAstNode.CALL, arguments); context.cfg().map(call, call); if (context.getCatchTarget() != null) { context.cfg().add(call, context.getCatchTarget(), null); } return call; } /** * Used to represent a script or function in the CAst; see walkEntity(). * */ private class ScriptOrFnEntity implements CAstEntity { private final String[] arguments; private final String name; private final int kind; private final Map> subs; private final CAstNode ast; private final CAstControlFlowMap map; private final CAstSourcePositionMap pos; private final Position entityPosition; private final Position namePosition; private final Position[] paramPositions; private ScriptOrFnEntity(AstNode n, Map> subs, CAstNode ast, CAstControlFlowMap map, CAstSourcePositionMap pos, String name) { this.name = name; this.entityPosition = pos.getPosition(ast); if (n instanceof FunctionNode) { FunctionNode f = (FunctionNode) n; namePosition = makePosition(f.getFunctionName()); f.flattenSymbolTable(false); int i = 0; arguments = new String[f.getParamCount() + 2]; arguments[i++] = name; arguments[i++] = "this"; for (int j = 0; j < f.getParamCount(); j++) { arguments[i++] = f.getParamOrVarName(j); } List params = f.getParams(); paramPositions = new Position[ params.size() ]; for(int pi = 0; pi < params.size(); pi++) { paramPositions[pi] = makePosition(params.get(pi)); } } else { paramPositions = new Position[0]; arguments = new String[0]; namePosition = null; } kind = (n instanceof FunctionNode) ? CAstEntity.FUNCTION_ENTITY : CAstEntity.SCRIPT_ENTITY; this.subs = subs; this.ast = ast; this.map = map; this.pos = pos; } @Override public String toString() { return ""; } @Override public String getName() { return name; } @Override public String getSignature() { Assertions.UNREACHABLE(); return null; } @Override public int getKind() { return kind; } @Override public String[] getArgumentNames() { return arguments; } @Override public CAstNode[] getArgumentDefaults() { return new CAstNode[0]; } @Override public int getArgumentCount() { return arguments.length; } @Override public Map> getAllScopedEntities() { return Collections.unmodifiableMap(subs); } @Override public Iterator getScopedEntities(CAstNode construct) { if (subs.containsKey(construct)) return subs.get(construct).iterator(); else return EmptyIterator.instance(); } @Override public CAstNode getAST() { return ast; } @Override public CAstControlFlowMap getControlFlow() { return map; } @Override public CAstSourcePositionMap getSourceMap() { return pos; } @Override public CAstSourcePositionMap.Position getPosition() { return entityPosition; } @Override public CAstNodeTypeMap getNodeTypeMap() { return null; } @Override public Collection getAnnotations() { return null; } @Override public Collection getQualifiers() { Assertions.UNREACHABLE("JuliansUnnamedCAstEntity$2.getQualifiers()"); return null; } @Override public CAstType getType() { return JSAstTranslator.Any; } @Override public Position getPosition(int arg) { return paramPositions[arg]; } @Override public Position getNamePosition() { return namePosition; } } private CAstEntity walkEntity(final AstNode n, List body, String name, WalkContext child) { CAstNode[] stmts = body.toArray(new CAstNode[body.size()]); // add variable / constant / function declarations, if any if (!child.getNameDecls().isEmpty()) { // new first statement will be a block declaring all names. CAstNode[] newStmts = new CAstNode[stmts.length + 1]; if (child.getNameDecls().size() == 1) { newStmts[0] = child.getNameDecls().iterator().next(); } else { newStmts[0] = Ast.makeNode(CAstNode.BLOCK_STMT, child.getNameDecls().toArray(new CAstNode[child.getNameDecls().size()])); } System.arraycopy(stmts, 0, newStmts, 1, stmts.length); stmts = newStmts; } final CAstNode ast = noteSourcePosition(child, Ast.makeNode(CAstNode.BLOCK_STMT, stmts), n); final CAstControlFlowMap map = child.cfg(); final CAstSourcePositionMap pos = child.pos(); // not sure if we need this copy --MS final Map> subs = HashMapFactory.make(child.getScopedEntities()); return new ScriptOrFnEntity(n, subs, ast, map, pos, name); } private Position makePosition(AstNode n) { if (n != null) { URL url = ((SourceModule)sourceModule).getURL(); int line = n.getLineno(); Position pos = new RangePosition(url, line, n.getAbsolutePosition(), n.getAbsolutePosition()+n.getLength()); if (sourceModule instanceof MappedSourceModule) { Position np = ((MappedSourceModule) sourceModule).getMapping().getIncludedPosition(pos); if (np != null) { return np; } } return pos; } else { return null; } } protected CAstNode noteSourcePosition(WalkContext context, CAstNode n, AstNode p) { if (p.getLineno() != -1 && context.pos().getPosition(n) == null) { pushSourcePosition(context, n, makePosition(p)); } return n; } private CAstNode readName(WalkContext context, AstNode node, String name) { CAstNode cn = makeVarRef(name); if (node != null) { context.cfg().map(node, cn); } else { context.cfg().map(cn, cn); } CAstNode target = context.getCatchTarget(); if (target != null) { context.cfg().add(cn, target, JavaScriptTypes.ReferenceError); } else { context.cfg().add(cn, CAstControlFlowMap.EXCEPTION_TO_EXIT, JavaScriptTypes.ReferenceError); } return cn; } private static List