All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST Maven / Gradle / Ivy

There is a newer version: 2.11.0
Show newest version
/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.jjs.impl;

import static com.google.gwt.dev.js.JsUtils.createAssignment;
import static com.google.gwt.dev.js.JsUtils.createInvocationOrPropertyAccess;
import static com.google.gwt.dev.js.JsUtils.createQualifiedNameRef;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.MinimalRebuildCache;
import com.google.gwt.dev.PrecompileTaskOptions;
import com.google.gwt.dev.cfg.PermutationProperties;
import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.javac.JsInteropUtil;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.HasEnclosingType;
import com.google.gwt.dev.jjs.ast.HasJsInfo.JsMemberType;
import com.google.gwt.dev.jjs.ast.HasName;
import com.google.gwt.dev.jjs.ast.JAbstractMethodBody;
import com.google.gwt.dev.jjs.ast.JArrayLength;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JBlock;
import com.google.gwt.dev.jjs.ast.JBreakStatement;
import com.google.gwt.dev.jjs.ast.JCaseStatement;
import com.google.gwt.dev.jjs.ast.JCastMap;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConditional;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JContinueStatement;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JDoStatement;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JExpressionStatement;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JForStatement;
import com.google.gwt.dev.jjs.ast.JIfStatement;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JLabel;
import com.google.gwt.dev.jjs.ast.JLabeledStatement;
import com.google.gwt.dev.jjs.ast.JLiteral;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JMember;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNameOf;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JNode;
import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JNumericEntry;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPermutationDependentValue;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JRunAsync;
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JSwitchStatement;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JThrowStatement;
import com.google.gwt.dev.jjs.ast.JTransformer;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.JWhileStatement;
import com.google.gwt.dev.jjs.ast.RuntimeConstants;
import com.google.gwt.dev.jjs.ast.js.JDebuggerStatement;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral;
import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
import com.google.gwt.dev.jjs.ast.js.JsonArray;
import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper;
import com.google.gwt.dev.js.JsStackEmulator;
import com.google.gwt.dev.js.JsUtils;
import com.google.gwt.dev.js.JsUtils.InvocationStyle;
import com.google.gwt.dev.js.ast.JsArrayAccess;
import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsBreak;
import com.google.gwt.dev.js.ast.JsCase;
import com.google.gwt.dev.js.ast.JsCatch;
import com.google.gwt.dev.js.ast.JsConditional;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsContinue;
import com.google.gwt.dev.js.ast.JsDebugger;
import com.google.gwt.dev.js.ast.JsDefault;
import com.google.gwt.dev.js.ast.JsDoWhile;
import com.google.gwt.dev.js.ast.JsEmpty;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFor;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsIf;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsLabel;
import com.google.gwt.dev.js.ast.JsLiteral;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameOf;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNew;
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsNormalScope;
import com.google.gwt.dev.js.ast.JsNullLiteral;
import com.google.gwt.dev.js.ast.JsNumberLiteral;
import com.google.gwt.dev.js.ast.JsNumericEntry;
import com.google.gwt.dev.js.ast.JsObjectLiteral;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.js.ast.JsPositionMarker;
import com.google.gwt.dev.js.ast.JsPositionMarker.Type;
import com.google.gwt.dev.js.ast.JsPostfixOperation;
import com.google.gwt.dev.js.ast.JsPrefixOperation;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsReturn;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsScope;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.dev.js.ast.JsSwitch;
import com.google.gwt.dev.js.ast.JsSwitchMember;
import com.google.gwt.dev.js.ast.JsThisRef;
import com.google.gwt.dev.js.ast.JsThrow;
import com.google.gwt.dev.js.ast.JsTry;
import com.google.gwt.dev.js.ast.JsUnaryOperator;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVars.JsVar;
import com.google.gwt.dev.js.ast.JsWhile;
import com.google.gwt.dev.util.Pair;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.arg.OptionMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.OptionOptimize;
import com.google.gwt.dev.util.collect.Stack;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedSet;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;

/**
 * Creates a JavaScript AST from a JProgram node.
 */
public class GenerateJavaScriptAST {

  /**
   * Finds the nodes that are targets of JNameOf so that a name is assigned to them.
   */
  private class FindNameOfTargets extends JVisitor {
    @Override
    public void endVisit(JNameOf x, Context ctx) {
      nameOfTargets.add(x.getNode());
    }
  }

  private class CreateNamesAndScopesVisitor extends JVisitor {

    /**
     * Cache of computed Java source file names to URI strings for symbol
     * export. By using a cache we also ensure the miminum number of String
     * instances are serialized.
     */
    private final Map fileNameToUriString = Maps.newHashMap();

    private final Stack scopeStack = new Stack();

    private JMethod currentMethod;

    @Override
    public boolean visit(JProgram x, Context ctx) {
      // Scopes and name objects need to be calculated within all types, even reference-only ones.
      // This information is used to be able to detect and avoid name collisions during pretty or
      // obfuscated JS variable name generation.
      x.visitAllTypes(this);
      return false;
    }

    @Override
    public void endVisit(JArrayType x, Context ctx) {
      JsName name = topScope.declareName(x.getName());
      names.put(x, name);
      recordSymbol(x, name);
    }

    @Override
    public void endVisit(JClassType x, Context ctx) {
      scopeStack.pop();
    }

    @Override
    public void endVisit(JField x, Context ctx) {
      JsName jsName;
      if (x.isStatic()) {
        jsName = topScope.declareName(mangleName(x), x.getName());
      } else {
        jsName = JjsUtils.requiresJsName(x)
                ? scopeStack.peek().declareUnobfuscatableName(x.getJsName())
                : scopeStack.peek().declareName(mangleName(x), x.getName());
      }
      names.put(x, jsName);
      recordSymbol(x, jsName);
    }

    @Override
    public void endVisit(JInterfaceType x, Context ctx) {
      scopeStack.pop();
    }

    @Override
    public void endVisit(JLabel x, Context ctx) {
      if (names.get(x) != null) {
        return;
      }
      names.put(x, scopeStack.peek().declareName(x.getName()));
    }

    @Override
    public void endVisit(JLocal x, Context ctx) {
      // locals can conflict, that's okay just reuse the same variable
      JsScope scope = scopeStack.peek();
      JsName jsName = scope.declareName(x.getName());
      names.put(x, jsName);
    }

    @Override
    public void endVisit(JMethod x, Context ctx) {
      if (doesNotHaveConcreteImplementation(x)) {
        return;
      }
      scopeStack.pop();
    }

    @Override
    public void endVisit(JParameter x, Context ctx) {
      if (x.isVarargs() && currentMethod.isJsMethodVarargs()) {
        names.put(x, scopeStack.peek().declareUnobfuscatableName("arguments"));
        return;
      }
      names.put(x, scopeStack.peek().declareName(x.getName()));
    }

    @Override
    public void endVisit(JProgram x, Context ctx) {
      /*
       * put the null method and field into objectScope since they can be
       * referenced as instance on null-types (as determined by type flow)
       */
      JMethod nullMethod = x.getNullMethod();
      polymorphicNames.put(nullMethod, objectScope.declareName("$_nullMethod"));
      JField nullField = x.getNullField();
      JsName nullFieldName = objectScope.declareName("$_nullField");
      names.put(nullField, nullFieldName);

      /*
       * Create names for instantiable array types since JProgram.traverse()
       * doesn't iterate over them.
       */
      for (JArrayType arrayType : program.getAllArrayTypes()) {
        if (program.typeOracle.isInstantiatedType(arrayType)) {
          accept(arrayType);
        }
      }
    }

    @Override
    public boolean visit(JClassType x, Context ctx) {
      // have I already been visited as a super type?
      JsScope myScope = classScopes.get(x);
      if (myScope != null) {
        scopeStack.push(myScope);
        return false;
      }

      // My seed function name
      JsName jsName = topScope.declareName(JjsUtils.mangledNameString(x), x.getShortName());
      names.put(x, jsName);
      recordSymbol(x, jsName);

      // My class scope
      if (x.getSuperClass() == null) {
        myScope = objectScope;
      } else {
        JsScope parentScope = classScopes.get(x.getSuperClass());
        // Run my superclass first!
        if (parentScope == null) {
          accept(x.getSuperClass());
        }
        parentScope = classScopes.get(x.getSuperClass());
        assert (parentScope != null);
        /*
         * WEIRD: we wedge the global interface scope in between object and all
         * of its subclasses; this ensures that interface method names trump all
         * (except Object method names)
         */
        if (parentScope == objectScope) {
          parentScope = interfaceScope;
        }
        myScope = new JsNormalScope(parentScope, "class " + x.getShortName());
      }
      classScopes.put(x, myScope);

      scopeStack.push(myScope);
      return true;
    }

    @Override
    public boolean visit(JInterfaceType x, Context ctx) {
      // interfaces have no name at run time
      scopeStack.push(interfaceScope);
      return true;
    }

    @Override
    public boolean visit(JMethod x, Context ctx) {
      currentMethod = x;
      // my polymorphic name
      String name = x.getName();
      if (x.needsDynamicDispatch()) {
        if (polymorphicNames.get(x) == null) {
          JsName polyName = JjsUtils.requiresJsName(x)
                  ? interfaceScope.declareUnobfuscatableName(x.getJsName())
                  : interfaceScope.declareName(mangleNameForPoly(x), name);
          polymorphicNames.put(x, polyName);
        }
      }

      if (doesNotHaveConcreteImplementation(x)) {
        return false;
      }

      // my global name
      JsName globalName = null;
      assert x.getEnclosingType() != null;
      String mangleName = mangleNameForGlobal(x);

      if (JProgram.isClinit(x)) {
        name = name + "_" + x.getEnclosingType().getShortName();
      }

      /*
       * Only allocate a name for a function if it is native, not polymorphic,
       * is a JNameOf target or stack-stripping is disabled.
       */
      if (!stripStack || !polymorphicNames.containsKey(x) || x.isJsniMethod()
          || nameOfTargets.contains(x)) {
        globalName = topScope.declareName(mangleName, name);
        names.put(x, globalName);
        recordSymbol(x, globalName);
      }
      JsFunction function;
      if (x.isJsniMethod()) {
        // set the global name of the JSNI peer
        JsniMethodBody body = (JsniMethodBody) x.getBody();
        function = body.getFunc();
        function.setName(globalName);
      } else {
        /*
         * It would be more correct here to check for an inline assignment, such
         * as var foo = function blah() {} and introduce a separate scope for
         * the function's name according to EcmaScript-262, but this would mess
         * up stack traces by allowing two inner scope function names to
         * obfuscate to the same identifier, making function names no longer a
         * 1:1 mapping to obfuscated symbols. Leaving them in global scope
         * causes no harm.
         */
        function = new JsFunction(x.getSourceInfo(), topScope, globalName, !x.isJsNative());
      }

      jsFunctionsByJavaMethodBody.put(x.getBody(), function);
      scopeStack.push(function.getScope());

      // Don't traverse the method body of methods in referenceOnly types since those method bodies
      // only exist in JS output of other modules it is their responsibility to handle their naming.
      return !program.isReferenceOnly(x.getEnclosingType());
    }

    @Override
    public boolean visit(JTryStatement x, Context ctx) {
      accept(x.getTryBlock());
      for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
        JLocalRef arg = clause.getArg();
        JBlock catchBlock = clause.getBlock();
        JsCatch jsCatch =
            new JsCatch(x.getSourceInfo(), scopeStack.peek(), arg.getTarget().getName());
        JsParameter jsParam = jsCatch.getParameter();
        names.put(arg.getTarget(), jsParam.getName());
        catchMap.put(catchBlock, jsCatch);
        catchParamIdentifiers.add(jsParam.getName());

        scopeStack.push(jsCatch.getScope());
        accept(catchBlock);
        scopeStack.pop();
      }

      // TODO: normalize this so it's never null?
      if (x.getFinallyBlock() != null) {
        accept(x.getFinallyBlock());
      }
      return false;
    }

    /**
     * Generate a file name URI string for a source info, for symbol data
     * export.
     */
    private String makeUriString(HasSourceInfo x) {
      String fileName = x.getSourceInfo().getFileName();
      if (fileName == null) {
        return null;
      }
      String uriString = fileNameToUriString.get(fileName);
      if (uriString == null) {
        uriString = StandardSymbolData.toUriString(fileName);
        fileNameToUriString.put(fileName, uriString);
      }
      return uriString;
    }

    private void recordSymbol(JReferenceType type, JsName jsName) {
      if (getRuntimeTypeReference(type) == null || !program.typeOracle.isInstantiatedType(type)) {
        return;
      }

      String typeId = getRuntimeTypeReference(type).toSource();
      StandardSymbolData symbolData =
          StandardSymbolData.forClass(type.getName(), type.getSourceInfo().getFileName(),
              type.getSourceInfo().getStartLine(), typeId);
      assert !symbolTable.containsKey(symbolData);
      symbolTable.put(symbolData, jsName);
    }

    private  void recordSymbol(T member,
        JsName jsName) {
      /*
       * NB: The use of member.getName() can produce confusion in cases where a type
       * has both polymorphic and static dispatch for a method, because you
       * might see HashSet::$add() and HashSet::add(). Logically, these methods
       * should be treated equally, however they will be implemented with
       * separate global functions and must be recorded independently.
       *
       * Automated systems that process the symbol information can easily map
       * the statically-dispatched function by looking for method names that
       * begin with a dollar-sign and whose first parameter is the enclosing
       * type.
       */

      String methodSignature = null;
      if (member instanceof JMethod) {
        JMethod method = ((JMethod) member);
        methodSignature =
            StringInterner.get().intern(method.getSignature().substring(method.getName().length()));
      }

      StandardSymbolData symbolData =
          StandardSymbolData.forMember(member.getEnclosingType().getName(), member.getName(),
              methodSignature,  makeUriString(member), member.getSourceInfo().getStartLine());
      assert !symbolTable.containsKey(symbolData) : "Duplicate symbol recorded " + jsName.getIdent()
          + " for " + member.getName() + " and key " + symbolData.getJsniIdent();
      symbolTable.put(symbolData, jsName);
    }
  }

  private class GenerateJavaScriptTransformer extends JTransformer {

    public static final String GOOG_ABSTRACT_METHOD = "goog.abstractMethod";
    public static final String GOOG_INHERITS = "goog.inherits";
    public static final String GOOG_OBJECT_CREATE_SET = "goog.object.createSet";

    private final Set alreadyRan = Sets.newLinkedHashSet();

    private final Map clinitFunctionForType = Maps.newHashMap();

    private JMethod currentMethod = null;

    private final JsName arrayLength = objectScope.declareUnobfuscatableName("length");
    private final JsName globalTemp = topScope.declareUnobfuscatableName("_");
    private final JsName prototype = objectScope.declareUnobfuscatableName("prototype");
    private final JsName wnd = topScope.declareUnobfuscatableName("$wnd");
    private final JsName goog = topScope.declareUnobfuscatableName("goog");
    private final JsName global = topScope.declareUnobfuscatableName("global");

    @Override
    public JsExpression transformArrayLength(JArrayLength expression) {
      assert expression.getInstance() != null : "Can't access the length of a null array";
      return arrayLength.makeQualifiedRef(expression.getSourceInfo(),
          transform(expression.getInstance()));
    }

    @Override
    public JsExpression transformArrayRef(JArrayRef arrayRef) {
      JsArrayAccess jsArrayAccess = new JsArrayAccess(arrayRef.getSourceInfo());
      jsArrayAccess.setIndexExpr(transform(arrayRef.getIndexExpr()));
      jsArrayAccess.setArrayExpr(transform(arrayRef.getInstance()));
      return jsArrayAccess;
    }

    @Override
    public JsExpression transformBinaryOperation(JBinaryOperation binaryOperation) {
      JsExpression lhs = transform(binaryOperation.getLhs());
      JsExpression rhs = transform(binaryOperation.getRhs());
      JsBinaryOperator op = JavaToJsOperatorMap.get(binaryOperation.getOp());

      /*
       * Use === and !== on reference types, or else you can get wrong answers
       * when Object.toString() == 'some string'.
       */
      if (binaryOperation.getLhs().getType() instanceof JReferenceType
          && binaryOperation.getRhs().getType() instanceof JReferenceType) {
        switch (op) {
          case EQ:
            op = JsBinaryOperator.REF_EQ;
            break;
          case NEQ:
            op = JsBinaryOperator.REF_NEQ;
            break;
        }
      }
      return new JsBinaryOperation(binaryOperation.getSourceInfo(), op, lhs, rhs);
    }

    @Override
    public JsStatement transformBlock(JBlock block) {
      JsBlock jsBlock = new JsBlock(block.getSourceInfo());
      List stmts = jsBlock.getStatements();

      transformIntoExcludingNulls(block.getStatements(), stmts);
      Iterables.removeIf(stmts, Predicates.instanceOf(JsEmpty.class));
      return jsBlock;
    }

    @Override
    public JsNode transformBreakStatement(JBreakStatement breakStatement) {
      SourceInfo info = breakStatement.getSourceInfo();
      return new JsBreak(info, transformIntoLabelReference(info, breakStatement.getLabel()));
    }

    @Override
    public JsNode transformCaseStatement(JCaseStatement caseStatement) {
      if (caseStatement.getExpr() == null) {
        return new JsDefault(caseStatement.getSourceInfo());
      } else {
        JsCase jsCase = new JsCase(caseStatement.getSourceInfo());
        jsCase.setCaseExpr(transform(caseStatement.getExpr()));
        return jsCase;
      }
    }

    @Override
    public JsNode transformCastOperation(JCastOperation castOperation) {
      // These are left in when cast checking is disabled.
      return transform(castOperation.getExpr());
    }

    @Override
    public JsNode transformClassLiteral(JClassLiteral classLiteral) {
      JsName classLit = names.get(classLiteral.getField());
      return classLit.makeRef(classLiteral.getSourceInfo());
    }

    @Override
    public JsNode transformDeclaredType(JDeclaredType type) {
      // Don't generate JS for types not in current module if separate compilation is on.
      if (program.isReferenceOnly(type)) {
        return null;
      }

      if (alreadyRan.contains(type)) {
        return null;
      }

      alreadyRan.add(type);

      if (type.isJsNative()) {
        // Emit JsOverlay static methods for native JsTypes.
        emitStaticMethods(type);
        // Emit JsOverlay (static) fields for native JsTypes.
        emitFields(type);
        return null;
      }

      checkForDuplicateMethods(type);

      assert program.getTypeClassLiteralHolder() != type;
      assert !program.immortalCodeGenTypes.contains(type);
      // Super classes should be emitted before the actual class.
      assert type.getSuperClass() == null || program.isReferenceOnly(type.getSuperClass()) ||
          alreadyRan.contains(type.getSuperClass());

      emitStaticMethods(type);

      generateTypeSetup(type);

      emitFields(type);
      return null;
    }

    @Override
    public JsNode transformConditional(JConditional conditional) {
      JsExpression ifTest = transform(conditional.getIfTest());
      JsExpression thenExpr = transform(conditional.getThenExpr());
      JsExpression elseExpr = transform(conditional.getElseExpr());
      return new JsConditional(conditional.getSourceInfo(), ifTest, thenExpr, elseExpr);
    }

    @Override
    public JsNode transformContinueStatement(JContinueStatement continueStatement) {
      SourceInfo info = continueStatement.getSourceInfo();
      return new JsContinue(info, transformIntoLabelReference(info, continueStatement.getLabel()));
    }

    @Override
    public JsNode transformDebuggerStatement(JDebuggerStatement debuggerStatement) {
      return new JsDebugger(debuggerStatement.getSourceInfo());
    }

    @Override
    public JsNode transformDeclarationStatement(JDeclarationStatement declarationStatement) {
      if (declarationStatement.getInitializer() == null) {
        return null;
      }

      JVariable target = declarationStatement.getVariableRef().getTarget();
      if (target instanceof JField && initializeAtTopScope((JField) target)) {
        // Will initialize at top scope; no need to double-initialize.
        return null;
      }

      JsExpression initializer = transform(declarationStatement.getInitializer());
      JsNameRef localRef = transform(declarationStatement.getVariableRef());

      SourceInfo info = declarationStatement.getSourceInfo();
      return JsUtils.createAssignment(info, localRef, initializer).makeStmt();
    }

    @Override
    public JsNode transformDoStatement(JDoStatement doStatement) {
      JsDoWhile stmt = new JsDoWhile(doStatement.getSourceInfo());
      stmt.setCondition(transform(doStatement.getTestExpr()));
      stmt.setBody(jsEmptyIfNull(doStatement.getSourceInfo(), transform(doStatement.getBody())));
      return stmt;
    }

    @Override
    public JsNode transformExpressionStatement(JExpressionStatement statement) {
      return transform(statement.getExpr()).makeStmt();
    }

    @Override
    public JsNode transformFieldRef(JFieldRef fieldRef) {
      JsExpression qualifier = transform(fieldRef.getInstance());
      boolean isStatic = fieldRef.getField().isStatic();
      return isStatic ? dispatchToStaticField(fieldRef, qualifier)
          : dispatchToInstanceField(fieldRef, qualifier);
    }

    private JsExpression dispatchToStaticField(
        JFieldRef fieldRef, JsExpression unnecessaryQualifier) {
      /*
       * Note: the comma expressions here would cause an illegal tree state if
       * the result expression ended up on the lhs of an assignment.
       * {@link JsNormalizer} will fix this situation.
       */

      JsExpression result = createStaticReference(fieldRef.getField(), fieldRef.getSourceInfo());

      return JsUtils.createCommaExpression(
          unnecessaryQualifier, maybeCreateClinitCall(fieldRef.getField()), result);
    }

    private JsExpression dispatchToInstanceField(JFieldRef x, JsExpression instance) {
      return names.get(x.getField()).makeQualifiedRef(x.getSourceInfo(), instance);
    }

    @Override
    public JsNode transformForStatement(JForStatement forStatement) {
      JsFor result = new JsFor(forStatement.getSourceInfo());

      JsExpression initExpr = null;
      List initStmts = transform(forStatement.getInitializers());
      for (int i = 0; i < initStmts.size(); ++i) {
        JsExprStmt initStmt = initStmts.get(i);
        if (initStmt != null) {
          initExpr = JsUtils.createCommaExpression(initExpr, initStmt.getExpression());
        }
      }
      result.setInitExpr(initExpr);
      result.setCondition(transform(forStatement.getCondition()));
      result.setIncrExpr(transform(forStatement.getIncrements()));
      result.setBody(jsEmptyIfNull(forStatement.getSourceInfo(), transform(forStatement.getBody())));

      return result;
    }

    @Override
    public JsNode transformIfStatement(JIfStatement ifStatement) {
      JsIf result = new JsIf(ifStatement.getSourceInfo());

      result.setIfExpr(transform(ifStatement.getIfExpr()));
      result.setThenStmt(jsEmptyIfNull(ifStatement.getSourceInfo(),
          transform(ifStatement.getThenStmt())));
      result.setElseStmt(transform(ifStatement.getElseStmt()));

      return result;
    }

    @Override
    public JsLabel transformLabel(JLabel label) {
      return new JsLabel(label.getSourceInfo(), names.get(label));
    }

    @Override
    public JsStatement transformLabeledStatement(JLabeledStatement labeledStatement) {
      JsLabel label = transform(labeledStatement.getLabel());
      label.setStmt(transform(labeledStatement.getBody()));
      return label;
    }

    @Override
    public JsLiteral transformLiteral(JLiteral literal) {
      return JjsUtils.translateLiteral(literal);
    }

    @Override
    public JsNode transformLocalRef(JLocalRef localRef) {
      return names.get(localRef.getTarget()).makeRef(localRef.getSourceInfo());
    }

    @Override
    public JsNode transformMethod(JMethod method) {
      if (method.isAbstract()) {
        return generateAbstractMethodDefinition(method);
      } else if (doesNotHaveConcreteImplementation(method)) {
        return null;
      }
      currentMethod = method;

      JsFunction function = transform(method.getBody());
      function.setInliningMode(method.getInliningMode());

      if (!method.isJsniMethod()) {
        // Setup params on the generated function. A native method already got
        // its jsParams set when parsed from JSNI.
        List parameterList = method.getParams();
        if (method.isJsMethodVarargs()) {
          parameterList = parameterList.subList(0, parameterList.size() - 1);
        }
        transformInto(parameterList, function.getParameters());
      }

      JsInvocation jsInvocation = maybeCreateClinitCall(method);
      if (jsInvocation != null) {
        function.getBody().getStatements().add(0, jsInvocation.makeStmt());
      }

      if (JProgram.isClinit(method)) {
        function.markAsClinit();
      }

      currentMethod = null;
      return function;
    }

    @Override
    public JsNode transformMethodBody(JMethodBody methodBody) {

      JsBlock body = transform(methodBody.getBlock());

      JsFunction function = jsFunctionsByJavaMethodBody.get(methodBody);
      function.setBody(body);

      /*
       * Emit a statement to declare the method's complete set of local
       * variables. JavaScript doesn't have the same concept of lexical scoping
       * as Java, so it's okay to just predeclare all local vars at the top of
       * the function, which saves us having to use the "var" keyword over and
       * over.
       *
       * Note: it's fine to use the same JS ident to represent two different
       * Java locals of the same name since they could never conflict with each
       * other in Java. We use the alreadySeen set to make sure we don't declare
       * the same-named local var twice.
       */
      JsVars vars = new JsVars(methodBody.getSourceInfo());
      Set alreadySeen = Sets.newHashSet();
      for (JLocal local : methodBody.getLocals()) {
        JsName name = names.get(local);
        String ident = name.getIdent();
        if (!alreadySeen.contains(ident)
            // Catch block params don't need var declarations
            && !catchParamIdentifiers.contains(name)) {
          alreadySeen.add(ident);
          vars.add(new JsVar(methodBody.getSourceInfo(), name));
        }
      }

      if (!vars.isEmpty()) {
        function.getBody().getStatements().add(0, vars);
      }

      return function;
    }

    @Override
    public JsNode transformMethodCall(JMethodCall methodCall) {
      JMethod method = methodCall.getTarget();
      if (JProgram.isClinit(method)) {
        /*
         * It is possible for clinits to be referenced here that have actually
         * been retargeted (see {@link
         * JTypeOracle.recomputeAfterOptimizations}). Most of the time, these
         * will get cleaned up by other optimization passes prior to this point,
         * but it's not guaranteed. In this case we need to replace the method
         * call with the replaced clinit, unless the replacement is null, in
         * which case we generate a JsNullLiteral as a place-holder expression.
         */
        JDeclaredType type = method.getEnclosingType();
        JDeclaredType clinitTarget = type.getClinitTarget();
        if (clinitTarget == null) {
          // generate a null expression, which will get optimized out
          return JsNullLiteral.INSTANCE;
        }
        method = clinitTarget.getClinitMethod();
      }

      JsExpression qualifier = transform(methodCall.getInstance());
      List args = transform(methodCall.getArgs());
      SourceInfo sourceInfo = methodCall.getSourceInfo();
      if (method.isStatic()) {
        return dispatchToStatic(qualifier, method, args, sourceInfo);
      } else if (methodCall.isStaticDispatchOnly()) {
        return dispatchToSuper(qualifier, method, args, sourceInfo);
      } else if (method.isOrOverridesJsFunctionMethod()) {
        return dispatchToJsFunction(qualifier, method, args, sourceInfo);
      } else {
        return dispatchToInstanceMethod(qualifier, method, args, sourceInfo);
      }
    }

    private JsExpression dispatchToStatic(JsExpression unnecessaryQualifier, JMethod method,
        List args, SourceInfo sourceInfo) {
      JsNameRef methodName = createStaticReference(method, sourceInfo);
      return JsUtils.createCommaExpression(
          unnecessaryQualifier,
          createInvocationOrPropertyAccess(
              InvocationStyle.NORMAL, sourceInfo, method, null, methodName, args));
    }

    private JsExpression dispatchToSuper(
        JsExpression instance, JMethod method, List args, SourceInfo sourceInfo) {
      JsNameRef methodNameRef;
      if (method.isJsNative()) {
        // Construct Constructor.prototype.jsname or Constructor.
        methodNameRef = createGlobalQualifier(method.getQualifiedJsName(), sourceInfo);
      } else if (method.isConstructor()) {
        /*
         * Constructor calls through {@code this} and {@code super} are always dispatched statically
         * using the constructor function name (constructors are always defined as top level
         * functions).
         *
         * Because constructors are modeled like instance methods they have an implicit {@code this}
         * parameter, hence they are invoked like: "constructor.call(this, ...)".
         */
        methodNameRef = names.get(method).makeRef(sourceInfo);
      } else {
        // These are regular super method call. These calls are always dispatched statically and
        // optimizations will devirtualize them (except in a few cases, like being target of
        // {@link Impl.getNameOf} or calls to the native classes.

        JDeclaredType superClass = method.getEnclosingType();
        JsExpression protoRef = getPrototypeQualifierViaLookup(superClass, sourceInfo);
        methodNameRef = polymorphicNames.get(method).makeQualifiedRef(sourceInfo, protoRef);
      }

      return JsUtils.createInvocationOrPropertyAccess(InvocationStyle.SUPER,
          sourceInfo, method, instance, methodNameRef, args);
    }

    private JsExpression getPrototypeQualifierViaLookup(JDeclaredType type, SourceInfo sourceInfo) {
      if (closureCompilerFormatEnabled) {
        return getPrototypeQualifierOf(type, type.getSourceInfo());
      } else {
        // Construct JCHSU.getPrototypeFor(type).polyname
        // TODO(rluble): Ideally we would want to construct the inheritance chain the JS way and
        // then we could do Type.prototype.polyname.call(this, ...). Currently prototypes do not
        // have global names instead they are stuck into the prototypesByTypeId array.
        return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_GET_CLASS_PROTOTYPE,
            (JsExpression) transform(getRuntimeTypeReference(type)));
      }
    }

    private JsExpression dispatchToJsFunction(JsExpression instance, JMethod method,
        List args, SourceInfo sourceInfo) {
      return createInvocationOrPropertyAccess(
          InvocationStyle.FUNCTION, sourceInfo, method, instance, null, args);
    }

    private JsExpression dispatchToInstanceMethod(JsExpression instance, JMethod method,
        List args, SourceInfo sourceInfo) {
      JsNameRef reference = polymorphicNames.get(method).makeQualifiedRef(sourceInfo, instance);
      return createInvocationOrPropertyAccess(
          InvocationStyle.NORMAL, sourceInfo, method, instance, reference, args);
    }

    @Override
    public JsNode transformMultiExpression(JMultiExpression multiExpression) {
      if (multiExpression.isEmpty()) {
        // the multi-expression was empty; use undefined
        return JsRootScope.INSTANCE.getUndefined().makeRef(multiExpression.getSourceInfo());
      }

      List exprs = transform(multiExpression.getExpressions());
      JsExpression cur = null;
      for (int i = 0; i < exprs.size(); ++i) {
        JsExpression next = exprs.get(i);
        cur = JsUtils.createCommaExpression(cur, next);
      }
      return cur;
    }

    @Override
    public JsNode transformNameOf(JNameOf nameof) {
      JsName name = names.get(nameof.getNode());
      if (name == null) {
        return JsRootScope.INSTANCE.getUndefined().makeRef(nameof.getSourceInfo());
      }
      return new JsNameOf(nameof.getSourceInfo(), name);
    }

    @Override
    public JsNode transformNewInstance(JNewInstance newInstance) {
      SourceInfo sourceInfo = newInstance.getSourceInfo();
      JConstructor ctor = newInstance.getTarget();
      JsName ctorName = names.get(ctor);
      JsNameRef  reference = ctor.isJsNative()
          ? createGlobalQualifier(ctor.getQualifiedJsName(), sourceInfo)
          : ctorName.makeRef(sourceInfo);
      List arguments = transform(newInstance.getArgs());

      if (newInstance.getClassType().isJsFunctionImplementation()) {
        // Synthesize makeLambdaFunction(samMethodReference, constructorReference, ctorArguments)
        // which will create the function instance and run the constructor on it.
        // TODO(rluble): optimize the constructor call away if it is empty.
        return constructJsFunctionObject(
            sourceInfo,
            newInstance.getClassType(),
            ctorName,
            reference,
            new JsArrayLiteral(sourceInfo, arguments));
      }

      return JsUtils.createInvocationOrPropertyAccess(
          InvocationStyle.NEWINSTANCE, sourceInfo, ctor, null, reference, arguments);
    }

    private JsExpression constructJsFunctionObject(SourceInfo sourceInfo, JClassType type,
        JsName ctorName, JsNameRef ctorReference, JsExpression ctorArguments) {
      // Foo.prototype.functionMethodName
      JMethod jsFunctionMethod = getJsFunctionMethod(type);
      JsNameRef funcNameRef = JsUtils.createQualifiedNameRef(sourceInfo,
          ctorName, prototype, polymorphicNames.get(jsFunctionMethod));

      // makeLambdaFunction(Foo.prototype.functionMethodName, new Foo(...))
      return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_MAKE_LAMBDA_FUNCTION,
          funcNameRef, ctorReference, ctorArguments);
    }

    private JMethod getJsFunctionMethod(JClassType type) {
      for (JMethod method : type.getMethods()) {
        if (method.isOrOverridesJsFunctionMethod()) {
          return method;
        }
      }
      throw new AssertionError("Should never reach here.");
    }

    @Override
    public JsNode transformNumericEntry(JNumericEntry entry) {
      return new JsNumericEntry(entry.getSourceInfo(), entry.getKey(), entry.getValue());
    }

    @Override
    public JsNode transformParameter(JParameter parameter) {
      assert !(currentMethod.isJsMethodVarargs() && parameter.isVarargs());
      return new JsParameter(parameter.getSourceInfo(), names.get(parameter));
    }

    @Override
    public JsNode transformParameterRef(JParameterRef parameterRef) {
      return names.get(parameterRef.getTarget()).makeRef(parameterRef.getSourceInfo());
    }

    @Override
    public JsNode transformPermutationDependentValue(JPermutationDependentValue dependentValue) {
      throw new AssertionError("AST should not contain permutation dependent values at " +
          "this point but contains " + dependentValue);
    }

    @Override
    public JsNode transformPostfixOperation(JPostfixOperation expression) {
      return new JsPostfixOperation(expression.getSourceInfo(), JavaToJsOperatorMap.get(expression.getOp()),
          transform(expression.getArg()));
    }

    @Override
    public JsNode transformPrefixOperation(JPrefixOperation expression) {
      return new JsPrefixOperation(expression.getSourceInfo(),
          JavaToJsOperatorMap.get(expression.getOp()), transform(expression.getArg()));
    }

    /**
     * Embeds properties into permProps for easy access from JavaScript.
     */
    private void embedBindingProperties() {
      SourceInfo sourceInfo = SourceOrigin.UNKNOWN;

      // Generates a list of lists of pairs: [[["key", "value"], ...], ...]
      // The outermost list is indexed by soft permutation id. Each item represents
      // a map from binding properties to their values, but is stored as a list of pairs
      // for easy iteration.
      JsArrayLiteral permutationProperties = new JsArrayLiteral(sourceInfo);
      for (Map propertyValueByPropertyName :
          properties.findEmbeddedProperties(TreeLogger.NULL)) {
        JsArrayLiteral entryList = new JsArrayLiteral(sourceInfo);
        for (Entry entry : propertyValueByPropertyName.entrySet()) {
          JsArrayLiteral pair = new JsArrayLiteral(sourceInfo,
              new JsStringLiteral(sourceInfo, entry.getKey()),
              new JsStringLiteral(sourceInfo, entry.getValue()));
          entryList.getExpressions().add(pair);
        }
        permutationProperties.getExpressions().add(entryList);
      }

      getGlobalStatements().add(
          constructInvocation(sourceInfo, "ModuleUtils.setGwtProperty",
              new JsStringLiteral(sourceInfo, "permProps"), permutationProperties).makeStmt());
    }

    @Override
    public JsNode transformReturnStatement(JReturnStatement returnStatement) {
      return new JsReturn(returnStatement.getSourceInfo(), transform(returnStatement.getExpr()));
    }

    @Override
    public JsNode transformRunAsync(JRunAsync runAsync) {
      return transform(runAsync.getRunAsyncCall());
    }

    @Override
    public JsNode transformCastMap(JCastMap castMap) {
      SourceInfo sourceInfo = castMap.getSourceInfo();
      List jsCastToTypes = transform(castMap.getCanCastToTypes());
      return buildJsCastMapLiteral(jsCastToTypes, sourceInfo);
    }

    @Override
    public JsNameRef transformJsniMethodRef(JsniMethodRef jsniMethodRef) {
      JMethod method = jsniMethodRef.getTarget();
      if (method.isJsNative()) {
        // Construct Constructor.prototype.jsname or Constructor.
        return createGlobalQualifier(method.getQualifiedJsName(), jsniMethodRef.getSourceInfo());
      }
      return names.get(method).makeRef(jsniMethodRef.getSourceInfo());
    }

    @Override
    public JsArrayLiteral transformJsonArray(JsonArray jsonArray) {
      JsArrayLiteral jsArrayLiteral = new JsArrayLiteral(jsonArray.getSourceInfo());
      transformInto(jsonArray.getExpressions(), jsArrayLiteral.getExpressions());
      return jsArrayLiteral;
    }

    @Override
    public JsNode transformThisRef(JThisRef thisRef) {
      return new JsThisRef(thisRef.getSourceInfo());
    }

    @Override
    public JsNode transformThrowStatement(JThrowStatement throwStatement) {
      return new JsThrow(throwStatement.getSourceInfo(), transform(throwStatement.getExpr()));
    }

    @Override
    public JsNode transformTryStatement(JTryStatement tryStatement) {
      JsTry jsTry = new JsTry(tryStatement.getSourceInfo());

      jsTry.setTryBlock(transform(tryStatement.getTryBlock()));

      int size = tryStatement.getCatchClauses().size();
      assert (size < 2);
      if (size == 1) {
        JBlock block = tryStatement.getCatchClauses().get(0).getBlock();
        JsCatch jsCatch = catchMap.get(block);
        jsCatch.setBody(transform(block));
        jsTry.getCatches().add(jsCatch);
      }

      JsBlock finallyBlock = transform(tryStatement.getFinallyBlock());
      if (finallyBlock != null && finallyBlock.getStatements().size() > 0) {
        jsTry.setFinallyBlock(finallyBlock);
      }

      return jsTry;
    }

    @Override
    public JsNode transformUnsafeTypeCoercion(JUnsafeTypeCoercion unsafeTypeCoercion) {
      return transform(unsafeTypeCoercion.getExpression());
    }

    @Override
    public JsNode transformWhileStatement(JWhileStatement whileStatement) {
      SourceInfo info = whileStatement.getSourceInfo();
      JsWhile stmt = new JsWhile(info);
      stmt.setCondition(transform(whileStatement.getTestExpr()));
      stmt.setBody(jsEmptyIfNull(info, transform(whileStatement.getBody())));
      return stmt;
    }

    public JsStatement jsEmptyIfNull(SourceInfo info, JsStatement statement) {
      return statement != null ? statement : new JsEmpty(info);
    }

    private void insertInTopologicalOrder(JDeclaredType type,
        Set topologicallySortedSet) {
      if (type == null || topologicallySortedSet.contains(type) || program.isReferenceOnly(type)) {
        return;
      }
      insertInTopologicalOrder(type.getSuperClass(), topologicallySortedSet);

      for (JInterfaceType intf : type.getImplements()) {
        if (program.typeOracle.isInstantiatedType(type)) {
          insertInTopologicalOrder(intf, topologicallySortedSet);
        }
      }

      topologicallySortedSet.add(type);
    }

    @Override
    public JsNode transformProgram(JProgram program) {
      // Handle the visiting here as we need to slightly change the order.
      // 1.1 (preamble) Immortal code gentypes.
      // 1.2 (preamble) Classes in the preamble, i.e. all the classes that are needed
      //                to support creation of class literals (reachable through Class.createFor* ).
      // 1.3 (preamble) Class literals for classes in the preamble.
      // 2.  (body)     Normal classes, each with its corresponding class literal (if live).
      // 3.  (epilogue) Code to start the execution of the program (gwtOnLoad, etc).

      Set preambleTypes = generatePreamble(program);

      if (incremental) {
        // Record the names of preamble types so that it's possible to invalidate caches when the
        // preamble types are known to have become stale.
        if (!minimalRebuildCache.hasPreambleTypeNames()) {
          Set preambleTypeNames =  Sets.newHashSet();
          for (JDeclaredType preambleType : preambleTypes) {
            preambleTypeNames.add(preambleType.getName());
          }
          minimalRebuildCache.setPreambleTypeNames(logger, preambleTypeNames);
        }
      }

      // Sort normal types according to superclass relationship.
      Set topologicallySortedBodyTypes = Sets.newLinkedHashSet();
      for (JDeclaredType type : program.getModuleDeclaredTypes()) {
        insertInTopologicalOrder(type, topologicallySortedBodyTypes);
      }
      // Remove all preamble types that might have been inserted here.
      topologicallySortedBodyTypes.removeAll(preambleTypes);

      // Iterate over each type in the right order.
      markPosition("Program", Type.PROGRAM_START);
      for (JDeclaredType type : topologicallySortedBodyTypes) {
        markPosition(type.getName(), Type.CLASS_START);
        transform(type);
        maybeGenerateClassLiteral(type);
        installClassLiterals(Arrays.asList(type));
        markPosition(type.getName(), Type.CLASS_END);
      }
      markPosition("Program", Type.PROGRAM_END);

      generateEpilogue();

      // All done, do not visit children.
      return null;
    }

    private Set generatePreamble(JProgram program) {
      SourceInfo programSourceInfo = jsProgram.getSourceInfo();

      // Reserve the "_" identifier.
      JsVars vars = new JsVars(programSourceInfo, new JsVar(programSourceInfo, globalTemp));
      addVarsIfNotEmpty(vars);

      // Generate immortal types in the preamble.
      generateImmortalTypes(vars);

      // Generate the assignment to goog.global.
      generateGoogGlobalInitialization(programSourceInfo);

      //  Perform necessary polyfills.
      addTypeDefinitionStatement(
          program.getIndexedType(RuntimeConstants.RUNTIME),
          constructInvocation(program.getSourceInfo(), RuntimeConstants.RUNTIME_BOOTSTRAP)
              .makeStmt());

      Set alreadyProcessed =
          Sets.newLinkedHashSet(program.immortalCodeGenTypes);
      alreadyProcessed.add(program.getTypeClassLiteralHolder());
      alreadyRan.addAll(alreadyProcessed);

      List classLiteralSupportClasses =
          computeClassLiteralsSupportClasses(program, alreadyProcessed);

      // Make sure immortal classes are not doubly processed.
      classLiteralSupportClasses.removeAll(alreadyProcessed);
      for (JDeclaredType type : classLiteralSupportClasses) {
        transform(type);
      }
      generateClassLiterals(classLiteralSupportClasses);
      installClassLiterals(classLiteralSupportClasses);

      Set preambleTypes = Sets.newLinkedHashSet(alreadyProcessed);
      preambleTypes.addAll(classLiteralSupportClasses);
      return preambleTypes;
    }

    private void generateGoogGlobalInitialization(SourceInfo programSourceInfo) {
      // $wnd.goog = $wnd.goog || {}
      JsNameRef wndGoog  = goog.makeQualifiedRef(programSourceInfo, wnd.makeRef(programSourceInfo));
      generatePropertyInitialization(
          programSourceInfo, wndGoog, JsObjectLiteral.builder(programSourceInfo).build());

      // $wnd.goog.global = $wnd.goog.global || $wnd
      JsNameRef wndGoogGlobal  = global.makeQualifiedRef(programSourceInfo, wndGoog);
      generatePropertyInitialization(
          programSourceInfo, wndGoogGlobal, wnd.makeRef(programSourceInfo));
    }

    private void generatePropertyInitialization(
        SourceInfo sourceInfo, JsNameRef qualifiedNameRef, JsExpression initializer) {
      getGlobalStatements().add(
          new JsBinaryOperation(sourceInfo, JsBinaryOperator.ASG, qualifiedNameRef,
              new JsBinaryOperation(sourceInfo, JsBinaryOperator.OR,
                  qualifiedNameRef, initializer)).makeStmt());
    }

    private JsNameRef transformIntoLabelReference(SourceInfo info, JLabel label) {
      if (label == null) {
        return null;
      }

      return  ((JsLabel) transform(label)).getName().makeRef(info);
    }

    private void installClassLiterals(List classLiteralTypesToInstall) {
      if (!closureCompilerFormatEnabled) {
        // let createForClass() install them until a follow on CL
        // TODO(cromwellian) remove after approval from rluble in follow up CL
        return;
      }

      for (JDeclaredType type : classLiteralTypesToInstall) {
        if (shouldNotEmitTypeDefinition(type)) {
          continue;
        }

        JsNameRef classLiteralRef = createClassLiteralReference(type);
        if (classLiteralRef == null) {
          continue;
        }

        SourceInfo sourceInfo = type.getSourceInfo();
        JsExpression protoRef = getPrototypeQualifierOf(type, sourceInfo);
        JsNameRef clazzField =
            getIndexedFieldJsName(RuntimeConstants.OBJECT_CLAZZ).makeRef(sourceInfo);
        clazzField.setQualifier(protoRef);
        JsExprStmt stmt = createAssignment(clazzField, classLiteralRef).makeStmt();
        addTypeDefinitionStatement(type, stmt);
      }
    }

    private boolean shouldNotEmitTypeDefinition(JDeclaredType type) {
      // Interfaces, Unboxed Types, JSOs, Native Types, JsFunction, and uninstantiated types
      // Do not have vtables/prototype setup
      return type instanceof JInterfaceType && !closureCompilerFormatEnabled
          || program.isRepresentedAsNativeJsPrimitive(type)
          || !program.typeOracle.isInstantiatedType(type)
          || type.isJsoType()
          || type.isJsNative()
          || type.isJsFunction();
    }

    private List computeClassLiteralsSupportClasses(JProgram program,
        Set alreadyProcessedTypes) {
      if (program.isReferenceOnly(program.getIndexedType("Class"))) {
        return Collections.emptyList();
      }
      // Include in the preamble all classes that are reachable for Class.createForClass,
      // Class.createForInterface
      SortedSet reachableClasses =
          computeReachableTypes(METHODS_PROVIDED_BY_PREAMBLE);

      assert !incremental || checkCoreModulePreambleComplete(program,
          program.getTypeClassLiteralHolder().getClinitMethod());

      Set orderedPreambleClasses = Sets.newLinkedHashSet();
      for (JDeclaredType type : reachableClasses) {
        if (alreadyProcessedTypes.contains(type)) {
          continue;
        }
        insertInTopologicalOrder(type, orderedPreambleClasses);
      }

      // TODO(rluble): The set of preamble types might be overly large, in particular will include
      // JSOs that need clinit. This is due to {@link ControlFlowAnalyzer} making all JSOs live if
      // there is a cast to that type anywhere in the program. See the use of
      // {@link JTypeOracle.getInstantiatedJsoTypesViaCast} in the constructor.
      return Lists.newArrayList(orderedPreambleClasses);
    }

    /**
     * Check that in modular compiles the preamble is complete.
     * 

* In modular compiles the preamble has to include code for creating all 4 types of class * literals. */ private boolean checkCoreModulePreambleComplete(JProgram program, JMethod classLiteralInitMethod) { final Set calledMethods = Sets.newHashSet(); new JVisitor() { @Override public void endVisit(JMethodCall x, Context ctx) { calledMethods.add(x.getTarget()); } }.accept(classLiteralInitMethod); for (String createForMethodName : METHODS_PROVIDED_BY_PREAMBLE) { if (!calledMethods.contains(program.getIndexedMethod(createForMethodName))) { return false; } } return true; } /** * Computes the set of types whose methods or fields are reachable from {@code methods}. */ private SortedSet computeReachableTypes(Iterable methodNames) { ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(program); for (String methodName : methodNames) { JMethod method = program.getIndexedMethodOrNull(methodName); // Only traverse it if it has not been pruned. if (method != null) { cfa.traverseFrom(method); } } // Get the list of enclosing classes that were not excluded. SortedSet reachableTypes = ImmutableSortedSet.copyOf(HasName.BY_NAME_COMPARATOR, Iterables.filter( Iterables.transform(cfa.getLiveFieldsAndMethods(), new Function() { @Override public JDeclaredType apply(JNode member) { if (member instanceof JMethod) { return ((JMethod) member).getEnclosingType(); } else if (member instanceof JField) { return ((JField) member).getEnclosingType(); } else { assert member instanceof JParameter || member instanceof JLocal; // Discard locals and parameters, only need the enclosing instances of reachable // fields and methods. return null; } } }), Predicates.notNull())); return reachableTypes; } private JsExpression generateAbstractMethodDefinition(JMethod method) { return (closureCompilerFormatEnabled) ? JsUtils.createQualifiedNameRef(GOOG_ABSTRACT_METHOD, method.getSourceInfo()) : null; } private void generateEpilogue() { generateRemainingClassLiterals(); // add all @JsExport assignments generateExports(); // Generate entry methods. Needs to be after class literal insertion since class literal will // be referenced by runtime rebind and property provider bootstrapping. setupGwtOnLoad(); embedBindingProperties(); } private void generateRemainingClassLiterals() { if (!incremental) { // Emit classliterals that are references but whose classes are not live. generateClassLiterals(Iterables.filter(classLiteralDeclarationsByType.keySet(), Predicates.not(Predicates.in(alreadyRan)))); return; } // In incremental, class literal references to class literals that were not generated // as part of the current compile have to be from reference only classes. assert FluentIterable.from(classLiteralDeclarationsByType.keySet()) .filter(Predicates.instanceOf(JDeclaredType.class)) .filter(Predicates.not(Predicates.in(alreadyRan))) .filter( new Predicate() { @Override public boolean apply(JType type) { return !program.isReferenceOnly((JDeclaredType) type); } }) .isEmpty(); // In incremental only the class literals for the primitive types should be part of the // epilogue. generateClassLiterals(JPrimitiveType.types); } private void generateClassLiterals(Iterable orderedTypes) { for (JType type : orderedTypes) { maybeGenerateClassLiteral(type); } } private void generateExports() { Map exportedMembersByExportName = new TreeMap(); Set hoistedClinits = Sets.newHashSet(); JsInteropExportsGenerator exportGenerator = closureCompilerFormatEnabled ? new ClosureJsInteropExportsGenerator(getGlobalStatements(), names) : new DefaultJsInteropExportsGenerator(getGlobalStatements(), globalTemp, getIndexedMethodJsName(RuntimeConstants.RUNTIME_PROVIDE)); // Gather exported things in JsNamespace order. for (JDeclaredType type : program.getDeclaredTypes()) { if (type.isJsNative()) { // JsNative types have no implementation and so shouldn't export anything. continue; } if (type.isJsType()) { exportedMembersByExportName.put(type.getQualifiedJsName(), type); } for (JMember member : type.getMembers()) { if (member.isJsInteropEntryPoint()) { if (member.getJsMemberType() == JsMemberType.PROPERTY && !member.isFinal()) { // TODO(goktug): Remove the warning when we export via Object.defineProperty logger.log( TreeLogger.Type.WARN, "Exporting effectively non-final field " + member.getQualifiedName() + ". Due to the way exporting works, the value of the" + " exported field will not be reflected across Java/JavaScript border."); } exportedMembersByExportName.put(member.getQualifiedJsName(), member); } } } // Output the exports. for (Object exportedEntity : exportedMembersByExportName.values()) { if (exportedEntity instanceof JDeclaredType) { exportGenerator.exportType((JDeclaredType) exportedEntity); } else { JMember member = (JMember) exportedEntity; maybeHoistClinit(hoistedClinits, member); exportGenerator.exportMember(member, names.get(member).makeRef(member.getSourceInfo())); } } } private void maybeHoistClinit(Set hoistedClinits, JMember member) { JDeclaredType enclosingType = member.getEnclosingType(); if (hoistedClinits.contains(enclosingType)) { return; } JsInvocation clinitCall = member instanceof JMethod ? maybeCreateClinitCall((JMethod) member) : maybeCreateClinitCall((JField) member); if (clinitCall != null) { hoistedClinits.add(enclosingType); getGlobalStatements().add(clinitCall.makeStmt()); } } @Override public JsFunction transformJsniMethodBody(JsniMethodBody jsniMethodBody) { final Map nodeByJsniReference = Maps.newHashMap(); for (JsniClassLiteral ref : jsniMethodBody.getClassRefs()) { nodeByJsniReference.put(ref.getIdent(), ref.getField()); } for (JsniFieldRef ref : jsniMethodBody.getJsniFieldRefs()) { nodeByJsniReference.put(ref.getIdent(), ref.getField()); } for (JsniMethodRef ref : jsniMethodBody.getJsniMethodRefs()) { nodeByJsniReference.put(ref.getIdent(), ref.getTarget()); } final JsFunction function = jsniMethodBody.getFunc(); // replace all JSNI idents with a real JsName now that we know it new JsModVisitor() { /** * Marks a ctor that is a direct child of an invocation. Instead of * replacing the ctor with a tear-off, we replace the invocation with a * new operation. */ private JsNameRef dontReplaceCtor; @Override public void endVisit(JsInvocation x, JsContext ctx) { // TODO(rluble): this fixup should be done during the initial JSNI processing in // GwtAstBuilder.JsniReferenceCollector. if (!(x.getQualifier() instanceof JsNameRef)) { // If the invocation does not have a name as a qualifier (it might be an expression). return; } JsNameRef ref = (JsNameRef) x.getQualifier(); if (!ref.isJsniReference()) { // The invocation is not to a JSNI method. return; } // Only constructors reach this point, all other JSNI references in the method body // would have already been replaced at endVisit(JsNameRef). // Replace invocation to ctor with a new op. String ident = ref.getIdent(); assert ref.getQualifier() == null; JConstructor constructor = (JConstructor) nodeByJsniReference.get(ident); JsNameRef constructorJsName = createStaticReference(constructor, x.getSourceInfo()); ctx.replaceMe(new JsNew(x.getSourceInfo(), constructorJsName, x.getArguments())); } @Override public void endVisit(JsNameRef x, JsContext ctx) { if (!x.isJsniReference()) { return; } String ident = x.getIdent(); JNode node = nodeByJsniReference.get(ident); assert (node != null); if (node instanceof JField) { JField field = (JField) node; if (field.isStatic() && field.isJsNative()) { ctx.replaceMe(createQualifiedNameRef(field.getQualifiedJsName(), x.getSourceInfo())); return; } JsName jsName = names.get(field); assert (jsName != null); x.resolve(jsName); // See if we need to add a clinit call to a static field ref JsInvocation clinitCall = maybeCreateClinitCall(field); if (clinitCall != null) { JsExpression commaExpr = JsUtils.createCommaExpression(clinitCall, x); ctx.replaceMe(commaExpr); } } else if (node instanceof JConstructor) { if (x == dontReplaceCtor) { // Do nothing, parent will handle. } else { // Replace with a local closure function. // function(a,b,c){return new Obj(a,b,c);} JConstructor constructor = (JConstructor) node; SourceInfo info = x.getSourceInfo(); JsNameRef constructorJsNameRef = createStaticReference(constructor, info); JsFunction anonymousFunction = new JsFunction(info, function.getScope()); for (JParameter p : constructor.getParams()) { JsName name = anonymousFunction.getScope().declareName(p.getName()); anonymousFunction.getParameters().add(new JsParameter(info, name)); } JsNew jsNew = new JsNew(info, constructorJsNameRef); for (JsParameter p : anonymousFunction.getParameters()) { jsNew.getArguments().add(p.getName().makeRef(info)); } anonymousFunction.setBody(new JsBlock(info)); anonymousFunction.getBody().getStatements().add(new JsReturn(info, jsNew)); ctx.replaceMe(anonymousFunction); } } else { JMethod method = (JMethod) node; if (!method.needsDynamicDispatch() && method.isJsNative()) { ctx.replaceMe(createGlobalQualifier(method.getQualifiedJsName(), x.getSourceInfo())); return; } if (x.getQualifier() == null) { JsName jsName = names.get(method); assert (jsName != null); x.resolve(jsName); } else { JsName jsName = polymorphicNames.get(method); if (jsName == null) { // this can occur when JSNI references an instance method on a // type that was never actually instantiated. jsName = getIndexedMethodJsName(RuntimeConstants.RUNTIME_EMPTY_METHOD); } x.resolve(jsName); } } } @Override public boolean visit(JsInvocation x, JsContext ctx) { if (x.getQualifier() instanceof JsNameRef) { dontReplaceCtor = (JsNameRef) x.getQualifier(); } return true; } }.accept(function); return function; } @Override public JsStatement transformSwitchStatement(JSwitchStatement switchStatement) { /* * What a pain.. JSwitchStatement and JsSwitch are modeled completely * differently. Here we try to resolve those differences. */ JsSwitch jsSwitch = new JsSwitch(switchStatement.getSourceInfo()); jsSwitch.setExpr(transform(switchStatement.getExpr())); List bodyStmts = switchStatement.getBody().getStatements(); List curStatements = null; for (JStatement stmt : bodyStmts) { if (stmt instanceof JCaseStatement) { // create a new switch member JsSwitchMember switchMember = transform((JNode) stmt); jsSwitch.getCases().add(switchMember); curStatements = switchMember.getStmts(); } else { // add to statements for current case assert (curStatements != null); JsStatement newStmt = transform(stmt); if (newStmt != null) { // Empty JDeclarationStatement produces a null curStatements.add(newStmt); } } } return jsSwitch; } private JsExpression buildJsCastMapLiteral(List runtimeTypeIdLiterals, SourceInfo sourceInfo) { if (JjsUtils.closureStyleLiteralsNeeded(incremental, closureCompilerFormatEnabled)) { return buildClosureStyleCastMapFromArrayLiteral(runtimeTypeIdLiterals, sourceInfo); } JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1); JsObjectLiteral.Builder objectLiteralBuilder = JsObjectLiteral.builder(sourceInfo) .setInternable(); for (JsExpression runtimeTypeIdLiteral : runtimeTypeIdLiterals) { objectLiteralBuilder.add(runtimeTypeIdLiteral, one); } return objectLiteralBuilder.build(); } private JsExpression buildClosureStyleCastMapFromArrayLiteral( List runtimeTypeIdLiterals, SourceInfo sourceInfo) { /* * goog.object.createSet('foo', 'bar', 'baz') is optimized by closure compiler into * {'foo': !0, 'bar': !0, baz: !0} */ JsNameRef createSet = new JsNameRef(sourceInfo, GOOG_OBJECT_CREATE_SET); JsInvocation jsInvocation = new JsInvocation(sourceInfo, createSet); for (JsExpression expr : runtimeTypeIdLiterals) { jsInvocation.getArguments().add(expr); } return jsInvocation; } private void checkForDuplicateMethods(JDeclaredType type) { // Sanity check to see that all methods are uniquely named. List methods = type.getMethods(); Set methodSignatures = Sets.newHashSet(); for (JMethod method : methods) { String sig = method.getSignature(); if (methodSignatures.contains(sig)) { throw new InternalCompilerException("Signature collision in Type " + type.getName() + " for method " + sig); } methodSignatures.add(sig); } } private JsNameRef createStaticReference(JMember member, SourceInfo sourceInfo) { assert !member.needsDynamicDispatch(); return member.isJsNative() ? createGlobalQualifier(member.getQualifiedJsName(), sourceInfo) : names.get(member).makeRef(sourceInfo); } private void emitFields(JDeclaredType type) { JsVars vars = new JsVars(type.getSourceInfo()); for (JField field : type.getFields()) { if (field.isJsNative()) { // Nothing to output for native fields. continue; } JsExpression initializer = null; // if we need an initial value, create an assignment if (initializeAtTopScope(field)) { // setup the constant value initializer = transform(field.getLiteralInitializer()); } else if (field.getType().getDefaultValue() == JNullLiteral.INSTANCE) { // Fields whose default value is null are left uninitialized and will // have a JS value of undefined. } else { // setup the default value, see Issue 380 initializer = transform(field.getType().getDefaultValue()); } JsName name = names.get(field); if (field.isStatic()) { // setup a var for the static JsVar var = new JsVar(type.getSourceInfo(), name); var.setInitExpr(initializer); vars.add(var); } else if (initializer != null) { // Instance field initilized at top. JsNameRef fieldRef = name.makeQualifiedRef(field.getSourceInfo(), getPrototypeQualifierOf(field)); addTypeDefinitionStatement(type, createAssignment(fieldRef, initializer).makeStmt()); } } addVarsIfNotEmpty(vars); } private void emitStaticMethods(JDeclaredType type) { // declare all methods into the global scope for (JMethod method : type.getMethods()) { if (method.needsDynamicDispatch()) { continue; } JsFunction function = transform(method); if (function == null) { continue; } if (JProgram.isClinit(method)) { handleClinit(type, function); } emitMethodImplementation(method, function.getName().makeRef(function.getSourceInfo()), function.makeStmt()); } } private JsExpression generateCastableTypeMap(JDeclaredType type) { JCastMap castMap = program.getCastMap(type); JsName castableTypeMapName = getIndexedFieldJsName(RuntimeConstants.OBJECT_CASTABLE_TYPE_MAP); if (castMap != null && castableTypeMapName != null) { return transform(castMap); } return JsObjectLiteral.EMPTY; } private JField getClassLiteralField(JType type) { JDeclarationStatement decl = classLiteralDeclarationsByType.get(type); if (decl == null) { return null; } return (JField) decl.getVariableRef().getTarget(); } private void maybeGenerateClassLiteral(JType type) { JField field = getClassLiteralField(type); if (field == null) { return; } // TODO(rluble): refactor so that all output related to a class is decided together. if (type != null && type instanceof JDeclaredType && program.isReferenceOnly((JDeclaredType) type)) { // Only generate class literals for classes in the current module. // TODO(rluble): In separate compilation some class literals will be duplicated, which if // not done with care might violate java semantics of getClass(). There are class literals // for primitives and arrays. Currently, because they will be assigned to the same field // the one defined later will be the one used and Java semantics are preserved. return; } JsVars vars = new JsVars(jsProgram.getSourceInfo()); JsName jsName = names.get(field); JsExpression classLiteralObject = transform(field.getInitializer()); JsVar var = new JsVar(field.getSourceInfo(), jsName); var.setInitExpr(classLiteralObject); vars.add(var); addVarsIfNotEmpty(vars); } private JsNameRef createClassLiteralReference(JType type) { JField field = getClassLiteralField(type); if (field == null) { return null; } JsName jsName = names.get(field); return jsName.makeRef(type.getSourceInfo()); } private void generateTypeSetup(JDeclaredType type) { if (program.isRepresentedAsNativeJsPrimitive(type) && program.typeOracle.isInstantiatedType(type)) { setupCastMapForUnboxedType(type, program.getRepresentedAsNativeTypesDispatchMap().get(type).getCastMapField()); return; } if (shouldNotEmitTypeDefinition(type)) { return; } generateClassDefinition(type); generatePrototypeDefinitions(type); maybeGenerateObjectMethodsAliases(type); } private void markPosition(String name, Type type) { getGlobalStatements().add(new JsPositionMarker(SourceOrigin.UNKNOWN, name, type)); } /** * Sets up gwtOnLoad bootstrapping code. Unusually, the created code is executed as part of * source loading and runs in the global scope (not inside of any function scope). */ private void setupGwtOnLoad() { /** *

       * var $entry = Impl.registerEntry();
       * var gwtOnLoad = ModuleUtils.gwtOnLoad();
       * ModuleUtils.addInitFunctions(init1, init2,...)
       * 
*/ final SourceInfo sourceInfo = SourceOrigin.UNKNOWN; // var $entry = ModuleUtils.registerEntry(); JsStatement entryVars = constructFunctionCallStatement( topScope.declareName("$entry"), "ModuleUtils.registerEntry"); getGlobalStatements().add(entryVars); // var gwtOnLoad = ModuleUtils.gwtOnLoad; JsName gwtOnLoad = topScope.findExistingUnobfuscatableName("gwtOnLoad"); JsVar varGwtOnLoad = new JsVar(sourceInfo, gwtOnLoad); varGwtOnLoad.setInitExpr(createAssignment(gwtOnLoad.makeRef(sourceInfo), getIndexedMethodJsName(RuntimeConstants.MODULE_UTILS_GWT_ON_LOAD).makeRef(sourceInfo))); getGlobalStatements().add(new JsVars(sourceInfo, varGwtOnLoad)); // ModuleUtils.addInitFunctions(init1, init2,...) List arguments = Lists.newArrayList(); for (JMethod entryPointMethod : program.getEntryMethods()) { JsFunction entryFunction = getJsFunctionFor(entryPointMethod); arguments.add(entryFunction.getName().makeRef(sourceInfo)); } JsStatement createGwtOnLoadFunctionCall = constructInvocation("ModuleUtils.addInitFunctions", arguments).makeStmt(); getGlobalStatements().add(createGwtOnLoadFunctionCall); } /** * Creates a (var) assignment a statement for a function call to an indexed function. */ private JsStatement constructFunctionCallStatement(JsName assignToVariableName, String indexedFunctionName, JsExpression... args) { return constructFunctionCallStatement(assignToVariableName, indexedFunctionName, Arrays.asList(args)); } /** * Creates a (var) assignment a statement for a function call to an indexed function. */ private JsStatement constructFunctionCallStatement(JsName assignToVariableName, String indexedFunctionName, List args) { SourceInfo sourceInfo = SourceOrigin.UNKNOWN; JsInvocation invocation = constructInvocation(indexedFunctionName, args); JsVar var = new JsVar(sourceInfo, assignToVariableName); var.setInitExpr(invocation); JsVars entryVars = new JsVars(sourceInfo); entryVars.add(var); return entryVars; } /** * Constructs an invocation for an indexed function. */ private JsInvocation constructInvocation(SourceInfo sourceInfo, String indexedFunctionName, JsExpression... args) { return constructInvocation(sourceInfo, indexedFunctionName, Arrays.asList(args)); } /** * Constructs an invocation for an indexed function. */ private JsInvocation constructInvocation(String indexedFunctionName, List args) { SourceInfo sourceInfo = SourceOrigin.UNKNOWN; return constructInvocation(sourceInfo, indexedFunctionName, args); } /** * Constructs an invocation for an indexed function. */ private JsInvocation constructInvocation(SourceInfo sourceInfo, String indexedFunctionName, List args) { JsName functionToInvoke = getIndexedMethodJsName(indexedFunctionName); return new JsInvocation(sourceInfo, functionToInvoke.makeRef(sourceInfo), args); } private void generateImmortalTypes(JsVars globals) { List immortalTypesReversed = Lists.reverse(program.immortalCodeGenTypes); // visit in reverse order since insertions start at head for (JClassType x : immortalTypesReversed) { // Don't generate JS for referenceOnly types. if (program.isReferenceOnly(x)) { continue; } // should not be pruned assert x.getMethods().size() > 0; // insert all static methods for (JMethod method : x.getMethods()) { /* * Skip virtual methods and constructors. Even in cases where there is no constructor * defined, the compiler will synthesize a default constructor which invokes * a synthesized $init() method. We must skip both of these inserted methods. */ if (method.needsDynamicDispatch() || method instanceof JConstructor || doesNotHaveConcreteImplementation(method)) { continue; } // add after var declaration, but before everything else JsFunction function = transform(method); assert function.getName() != null; addMethodDefinitionStatement(1, method, function.makeStmt()); } // insert fields into global var declaration for (JField field : x.getFields()) { assert field.isStatic() : "'" + field.getName() + "' is not static. Only static fields are allowed on immortal types"; assert field.getInitializer() == field.getLiteralInitializer() : "'" + field.getName() + "' is not initilialized to a literal." + " Only literal initializers are allowed on immortal types"; JsVar var = new JsVar(x.getSourceInfo(), names.get(field)); var.setInitExpr(transform(field.getLiteralInitializer())); globals.add(var); } } } private void generateCallToDefineClass(JClassType type, List constructorArgs) { JClassType superClass = type.getSuperClass(); JExpression superTypeId = (superClass == null) ? JNullLiteral.INSTANCE : getRuntimeTypeReference(superClass); String jsPrototype = getSuperPrototype(type); List defineClassArguments = Lists.newArrayList(); defineClassArguments.add(transform(getRuntimeTypeReference(type))); defineClassArguments.add(jsPrototype == null ? transform(superTypeId) : createGlobalQualifier(jsPrototype, type.getSourceInfo())); defineClassArguments.add(generateCastableTypeMap(type)); defineClassArguments.addAll(constructorArgs); // Runtime.defineClass(typeId, superTypeId, castableMap, constructors) JsStatement defineClassStatement = constructInvocation(type.getSourceInfo(), RuntimeConstants.RUNTIME_DEFINE_CLASS, defineClassArguments).makeStmt(); addTypeDefinitionStatement(type, defineClassStatement); maybeCopyJavaLangObjectProperties( type, getPrototypeQualifierViaLookup(program.getTypeJavaLangObject(), type.getSourceInfo()), globalTemp.makeRef(type.getSourceInfo())); } private void maybeCopyJavaLangObjectProperties( JDeclaredType type, JsExpression javaLangObjectPrototype, JsExpression toPrototype) { if (getSuperPrototype(type) != null && !type.isJsFunctionImplementation()) { JsStatement statement = constructInvocation( type.getSourceInfo(), RuntimeConstants.RUNTIME_COPY_OBJECT_PROPERTIES, javaLangObjectPrototype, toPrototype ).makeStmt(); addTypeDefinitionStatement(type, statement); } } private String getSuperPrototype(JDeclaredType type) { if (type.isJsFunctionImplementation()) { return "Function"; } JClassType superClass = type.getSuperClass(); if (superClass != null && superClass.isJsNative()) { return superClass.getQualifiedJsName(); } return null; } private void generateClassDefinition(JDeclaredType type) { assert !program.isRepresentedAsNativeJsPrimitive(type); if (closureCompilerFormatEnabled) { generateClosureTypeDefinition(type); } else { generateJsClassDefinition((JClassType) type); } } /* * Class definition for regular output looks like: * * defineClass(id, superId, castableTypeMap, ctor1, ctor2, ctor3); * _.method1 = function() { ... } * _.method2 = function() { ... } */ private void generateJsClassDefinition(JClassType classType) { // Add constructors as varargs to define class. List constructorArgs = Lists.newArrayList(); for (JMethod method : getPotentiallyAliveConstructors(classType)) { constructorArgs.add(names.get(method).makeRef(classType.getSourceInfo())); } // defineClass(..., Ctor1, Ctor2, ...) generateCallToDefineClass(classType, constructorArgs); } /* * Class definition for closure output looks like: * * function ClassName() {} * ClassName.prototype.method1 = function() { ... }; * ClassName.prototype.method2 = function() { ... }; * ClassName.prototype.castableTypeMap = {...} * ClassName.prototype.___clazz = classLit; * function Ctor1() {} * function Ctor2() {} * * goog$inherits(Ctor1, ClassName); * goog$inherits(Ctor2, ClassName); * * The primary change is to make the prototype assignment look like regular closure code to help * the compiler disambiguate which methods belong to which type. Elimination of defineClass() * makes the setup more transparent and eliminates a global table holding a reference to * every prototype. */ private void generateClosureTypeDefinition(JDeclaredType type) { // function ClassName(){} JsName classVar = declareSynthesizedClosureConstructor(type); generateInlinedDefineClass(type, classVar); /* * Closure style prefers 1 single ctor per type. To model this without radical changes, * we simply model each concrete ctor as a subtype. This works because GWT doesn't use the * native instanceof operator. So for example, class A() { A(int type){}, A(String s){} } * becomes (pseudo code): * * function A() {} * A.prototype.method = ... * * function A_int(x) {} * function A_String(s) {} * goog$inherits(A_int, A); * goog$inherits(A_string, A); * */ for (JMethod method : getPotentiallyAliveConstructors(type)) { SourceInfo typeSourceInfo = type.getSourceInfo(); JsNameRef googInherits = JsUtils.createQualifiedNameRef(GOOG_INHERITS, typeSourceInfo); SourceInfo methodSourceInfo = method.getSourceInfo(); JsExprStmt callGoogInherits = new JsInvocation(typeSourceInfo, googInherits, names.get(method).makeRef(methodSourceInfo), names.get(method.getEnclosingType()).makeRef(methodSourceInfo)).makeStmt(); addMethodDefinitionStatement(method, callGoogInherits); } } /** * Does everything JCHSU.defineClass does, but inlined into global statements. Roughly * parallels argument order of generateCallToDefineClass. */ private void generateInlinedDefineClass(JDeclaredType type, JsName classVar) { if (type instanceof JInterfaceType) { return; } JClassType superClass = type.getSuperClass(); // check if there's an overriding prototype String jsPrototype = getSuperPrototype(type); SourceInfo info = type.getSourceInfo(); JsNameRef parentCtor = jsPrototype != null ? createGlobalQualifier(jsPrototype, info) : superClass != null ? names.get(superClass).makeRef(info) : null; if (parentCtor != null) { JsNameRef googInherits = JsUtils.createQualifiedNameRef(GOOG_INHERITS, info); // Use goog$inherits(ChildCtor, ParentCtor) to setup inheritance JsExprStmt callGoogInherits = new JsInvocation(info, googInherits, classVar.makeRef(info), parentCtor).makeStmt(); addTypeDefinitionStatement(type, callGoogInherits); } if (type == program.getTypeJavaLangObject()) { setupTypeMarkerOnJavaLangObjectPrototype(type); } // inline assignment of castableTypeMap field instead of using defineClass() setupCastMapOnPrototype(type); maybeCopyJavaLangObjectProperties( type, getPrototypeQualifierOf(program.getTypeJavaLangObject(), info), getPrototypeQualifierOf(type, info)); } private void setupCastMapOnPrototype(JDeclaredType type) { JsExpression castMap = generateCastableTypeMap(type); generatePrototypeAssignmentForJavaField(type, "Object.castableTypeMap", castMap); } private void setupTypeMarkerOnJavaLangObjectPrototype(JDeclaredType type) { JsName typeMarkerMethod = getIndexedMethodJsName(RuntimeConstants.RUNTIME_TYPE_MARKER_FN); generatePrototypeAssignmentForJavaField(type, RuntimeConstants.OBJECT_TYPEMARKER, typeMarkerMethod.makeRef(type.getSourceInfo())); } private void generatePrototypeAssignmentForJavaField(JDeclaredType type, String javaField, JsExpression rhs) { SourceInfo sourceInfo = type.getSourceInfo(); JsNameRef protoRef = getPrototypeQualifierOf(type, sourceInfo); JsNameRef fieldRef = getIndexedFieldJsName(javaField).makeQualifiedRef(sourceInfo, protoRef); addTypeDefinitionStatement(type, createAssignment(fieldRef, rhs).makeStmt()); } private void addMethodDefinitionStatement(JMethod method, JsExprStmt methodDefinitionStatement) { getGlobalStatements().add(methodDefinitionStatement); methodByGlobalStatement.put(methodDefinitionStatement, method); } private void addMethodDefinitionStatement(int position, JMethod method, JsExprStmt methodDefinitionStatement) { getGlobalStatements().add(position, methodDefinitionStatement); methodByGlobalStatement.put(methodDefinitionStatement, method); } private void addTypeDefinitionStatement(JDeclaredType x, JsStatement statement) { getGlobalStatements().add(statement); javaTypeByGlobalStatement.put(statement, x); } /* * Declare an empty synthesized constructor that looks like: * function ClassName(){} * * Closure Compiler's RewriteFunctionExpressions pass can be enabled to turn these back * into a factory method after optimizations. * * TODO(goktug): throw Error in the body to prevent instantiation via this constructor. */ private JsName declareSynthesizedClosureConstructor(JDeclaredType x) { SourceInfo sourceInfo = x.getSourceInfo(); JsName classVar = topScope.declareName(JjsUtils.mangledNameString(x)); JsFunction closureCtor = JsUtils.createEmptyFunctionLiteral(sourceInfo, topScope, classVar); JsExprStmt statement = closureCtor.makeStmt(); // This synthetic statement must be in the initial fragment, do not add to typeDefinitions getGlobalStatements().add(statement); names.put(x, classVar); return classVar; } /* * Sets up the castmap for type X */ private void setupCastMapForUnboxedType(JDeclaredType type, String castMapField) { // Cast.[castMapName] = /* cast map */ { ..:1, ..:1} JsName castableTypeMapName = getIndexedFieldJsName(castMapField); JsNameRef castMapVarRef = castableTypeMapName.makeRef(type.getSourceInfo()); JsExpression castMapLiteral = generateCastableTypeMap(type); addTypeDefinitionStatement(type, createAssignment(castMapVarRef, castMapLiteral).makeStmt()); } private void maybeGenerateObjectMethodsAliases(JDeclaredType type) { if (type == program.getTypeJavaLangObject()) { // special: setup a "toString" alias for java.lang.Object.toString() Set overridableJavaLangObjectMethods = ImmutableSet.of( program.getIndexedMethodOrNull(RuntimeConstants.OBJECT_EQUALS), program.getIndexedMethodOrNull(RuntimeConstants.OBJECT_HASHCODE), program.getIndexedMethodOrNull(RuntimeConstants.OBJECT_TO_STRING)); for (JMethod method : type.getMethods()) { if (overridableJavaLangObjectMethods.contains(method)) { JsName methodJsName = objectScope.declareUnobfuscatableName(method.getName()); generatePrototypeDefinitionAlias(method, methodJsName); } } } } private void generatePrototypeAssignment(JMethod method, JsName name, JsExpression rhs) { generatePrototypeAssignment(method, name, rhs, method.getJsMemberType()); } /** * Create a vtable assignment of the form _.polyname = rhs; and register the line as * created for {@code method}. */ private void generatePrototypeAssignment(JMethod method, JsName name, JsExpression rhs, JsMemberType memberType) { SourceInfo sourceInfo = method.getSourceInfo(); JsNameRef prototypeQualifierOf = getPrototypeQualifierOf(method); JsNameRef lhs = name.makeQualifiedRef(sourceInfo, prototypeQualifierOf); switch (memberType) { case GETTER: case SETTER: emitPropertyImplementation(method, prototypeQualifierOf, name.makeRef(sourceInfo), rhs); break; default: emitMethodImplementation(method, lhs, createAssignment(lhs, rhs).makeStmt()); break; } } private void emitPropertyImplementation(JMethod method, JsNameRef prototype, JsNameRef name, JsExpression methodDefinitionStatement) { SourceInfo sourceInfo = method.getSourceInfo(); // We use Object.defineProperties instead of Object.defineProperty to make sure the // property name appears as an identifier and not as a string. // Some JS optimizers, e.g. the closure compiler, relies on this subtle difference for // obfuscating property names. JsNameRef definePropertyMethod = getIndexedMethodJsName(RuntimeConstants.RUNTIME_DEFINE_PROPERTIES).makeRef(sourceInfo); JsObjectLiteral definePropertyLiteral = JsObjectLiteral.builder(sourceInfo) // {name: {get: function() { ..... }} or {set : function (v) {....}}} .add(name, JsObjectLiteral.builder(sourceInfo) // {get: function() { ..... }} or {set : function (v) {....}} .add(method.getJsMemberType().getPropertyAccessorKey(), methodDefinitionStatement) .build()) .build(); addMethodDefinitionStatement(method, new JsInvocation(sourceInfo, definePropertyMethod, prototype, definePropertyLiteral).makeStmt()); } private void emitMethodImplementation(JMethod method, JsNameRef functionNameRef, JsExprStmt methodDefinitionStatement) { addMethodDefinitionStatement(method, methodDefinitionStatement); if (shouldEmitDisplayNames()) { addMethodDefinitionStatement(method, outputDisplayName(functionNameRef, method)); addMethodDefinitionStatement(method, outputFunctionNameProperty(functionNameRef, method)); } } private void generatePrototypeDefinitionAlias(JMethod method, JsName alias) { JsName polyName = polymorphicNames.get(method); JsExpression bridge = JsUtils.createBridge(method, polyName, topScope); // Aliases are never property accessors. generatePrototypeAssignment(method, alias, bridge, JsMemberType.NONE); } private JsExprStmt outputDisplayName(JsNameRef function, JMethod method) { JsNameRef displayName = new JsNameRef(function.getSourceInfo(), "displayName"); displayName.setQualifier(function); String displayStringName = getDisplayName(method); JsStringLiteral displayMethodName = new JsStringLiteral(function.getSourceInfo(), displayStringName); return createAssignment(displayName, displayMethodName).makeStmt(); } private JsExprStmt outputFunctionNameProperty(JsNameRef function, JMethod method) { String displayStringName = getDisplayName(method); SourceInfo sourceInfo = function.getSourceInfo(); JsStringLiteral displayMethodName = new JsStringLiteral(sourceInfo, displayStringName); JsObjectLiteral props = JsObjectLiteral.builder(sourceInfo) .add(new JsStringLiteral(sourceInfo, "name"), JsObjectLiteral.builder(sourceInfo) .add("value", displayMethodName) .build()) .build(); JsExpression[] args = {function, props}; return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_DEFINE_PROPERTIES, args) .makeStmt(); } private boolean shouldEmitDisplayNames() { return methodNameMappingMode != OptionMethodNameDisplayMode.Mode.NONE; } private String getDisplayName(JMethod method) { switch (methodNameMappingMode) { case ONLY_METHOD_NAME: return method.getName(); case ABBREVIATED: return method.getEnclosingType().getShortName() + "." + method.getName(); case FULL: return method.getEnclosingType().getName() + "." + method.getName(); default: assert false : "Invalid display mode option " + methodNameMappingMode; } return null; } /** * Creates the assignment for all polynames for a certain class, assumes that the global * variable _ points the JavaScript prototype for {@code type}. */ private void generatePrototypeDefinitions(JDeclaredType type) { assert !program.isRepresentedAsNativeJsPrimitive(type); // Emit synthetic methods first. In JsInterop we allow a more user written method to be named // with the same name as a synthetic bridge (required due to generics) relying that the // synthetic method is output first into the prototype slot and rewritten in this situation. // TODO(rluble): this is a band aid. The user written method (and its overrides) should be // automatically JsIgnored. Otherwise some semantics become looser. E.g. the synthetic bridge // method may be casting some of the parameters. Such casts are lost in this scheme. Iterable orderedInstanceMethods = Iterables.concat( Iterables.filter(type.getMethods(), Predicates.and( JjsPredicates.IS_SYNTHETIC, JjsPredicates.NEEDS_DYNAMIC_DISPATCH)), Iterables.filter(type.getMethods(), Predicates.and( Predicates.not(JjsPredicates.IS_SYNTHETIC), JjsPredicates.NEEDS_DYNAMIC_DISPATCH))); for (JMethod method : orderedInstanceMethods) { generatePrototypeDefinition(method, (JsExpression) transformMethod(method)); } } private void generatePrototypeDefinition(JMethod method, JsExpression functionDefinition) { if (functionDefinition != null) { generatePrototypeAssignment(method, polymorphicNames.get(method), functionDefinition); } if (method.exposesNonJsMember()) { JsName internalMangledName = interfaceScope.declareName(mangleNameForPoly(method), method.getName()); generatePrototypeDefinitionAlias(method, internalMangledName); } if (method.exposesPackagePrivateMethod()) { // Here is the situation where this is needed: // // class a.A { m() {} } // class b.B extends a.A { m() {} } // interface I { m(); } // class a.C { // { A a = new b.B(); a.m() // calls A::m()} } // { I i = new b.B(); a.m() // calls B::m()} } // } // // Up to this point it is clear that package private names need to be different than // public names. // // Add class a.D extends a.A implements I { public m() } // // a.D collapses A::m and I::m into the same function and it was clear that two // two different names were already needed, hence when creating the vtable for a.D // both names have to point to the same function. generatePrototypeDefinitionAlias(method, getPackagePrivateName(method)); } } /** * Returns either _ or ClassCtor.prototype depending on output mode. */ private JsNameRef getPrototypeQualifierOf(JMember member) { return getPrototypeQualifierOf(member.getEnclosingType(), member.getSourceInfo()); } /** * Returns either _ or ClassCtor.prototype depending on output mode. */ private JsNameRef getPrototypeQualifierOf(JDeclaredType type, SourceInfo info) { return closureCompilerFormatEnabled ? prototype.makeQualifiedRef(info, names.get(type).makeRef(info)) : globalTemp.makeRef(info); } /** * Returns the package private JsName for {@code method}. */ private JsName getPackagePrivateName(JMethod method) { for (JMethod overridenMethod : method.getOverriddenMethods()) { if (overridenMethod.isPackagePrivate()) { JsName name = polymorphicNames.get(overridenMethod); assert name != null; return name; } } throw new AssertionError( method.toString() + " overrides a package private method but was not found."); } private void handleClinit(JDeclaredType type, JsFunction clinitFunction) { clinitFunctionForType.put(type, clinitFunction); JDeclaredType superClass = type.getSuperClass(); JsFunction superClinitFunction = superClass == null ? null : clinitFunctionForType.get(superClass.getClinitTarget()); clinitFunction.setSuperClinit(superClinitFunction); List statements = clinitFunction.getBody().getStatements(); SourceInfo sourceInfo = clinitFunction.getSourceInfo(); // Self-assign to the global noop method immediately (to prevent reentrancy). In incremental // mode the more costly Object constructor function is used as the noop method since doing so // provides a better debug experience that does not step into already used clinits. JsName emptyFunctionFnName = incremental ? objectConstructorFunction.getName() : getIndexedMethodJsName(RuntimeConstants.RUNTIME_EMPTY_METHOD); JsExpression assignment = createAssignment(clinitFunction.getName().makeRef(sourceInfo), emptyFunctionFnName.makeRef(sourceInfo)); statements.add(0, assignment.makeStmt()); } private boolean isMethodPotentiallyCalledAcrossClasses(JMethod method) { assert incremental || crossClassTargets != null; return crossClassTargets == null || crossClassTargets.contains(method) || method.isJsInteropEntryPoint(); } private Iterable getPotentiallyAliveConstructors(JDeclaredType x) { return Iterables.filter(x.getMethods(), new Predicate() { @Override public boolean apply(JMethod m) { return isMethodPotentiallyALiveConstructor(m); } }); } /** * Whether a method is a constructor that is actually newed. Note that in absence of whole * world knowledge evey constructor is potentially live. */ private boolean isMethodPotentiallyALiveConstructor(JMethod method) { if (!(method instanceof JConstructor)) { return false; } assert incremental || liveCtors != null; return liveCtors == null || liveCtors.contains(method); } private JsInvocation maybeCreateClinitCall(JField x) { if (!x.isStatic() || x.isCompileTimeConstant()) { // Access to compile time constants do not trigger class initialization (JLS 12.4.1). return null; } JDeclaredType targetType = x.getEnclosingType().getClinitTarget(); if (targetType == null || targetType == program.getTypeClassLiteralHolder() // When currentMethod == null, the clinit is being hoisted to the global scope. || (currentMethod != null && !currentMethod.getEnclosingType().checkClinitTo(targetType))) { return null; } JMethod clinitMethod = targetType.getClinitMethod(); SourceInfo sourceInfo = x.getSourceInfo(); return new JsInvocation(sourceInfo, names.get(clinitMethod).makeRef(sourceInfo)); } private JsInvocation maybeCreateClinitCall(JMethod method) { if (!isMethodPotentiallyCalledAcrossClasses(method)) { // Global optimized compile can prune some clinit calls. return null; } JDeclaredType enclosingType = method.getEnclosingType(); if (method.canBePolymorphic() || (program.isStaticImpl(method) && !method.isJsOverlay())) { return null; } if (enclosingType == null || !enclosingType.hasClinit()) { return null; } // Avoid recursion sickness. if (JProgram.isClinit(method)) { return null; } JMethod clinitMethod = enclosingType.getClinitTarget().getClinitMethod(); SourceInfo sourceInfo = method.getSourceInfo(); return new JsInvocation(sourceInfo, names.get(clinitMethod).makeRef(sourceInfo)); } /** * If a field is a literal, we can potentially treat it as immutable and assign it once on the * prototype, to be reused by all instances of the class, instead of re-assigning the same * literal in each constructor. */ private boolean initializeAtTopScope(JField x) { if (x.getEnclosingType().isJsFunctionImplementation()) { // JsFunction implementation are plain JS functions with no class prototype, fields // need to be initialized and placed on the instance itself. return false; } if (x.getLiteralInitializer() == null) { return false; } if (x.isFinal() || x.isStatic() || x.isCompileTimeConstant()) { // we can definitely initialize at top-scope, as JVM does so as well return true; } return !uninitializedValuePotentiallyObservable.apply(x); } /** * Helpers to avoid casting (can be removed when compiling in Java 8). */ private T transform(JExpression expression) { return transform((JNode) expression); } private T transform(JStatement statement) { return transform((JNode) statement); } private JsBlock transform(JBlock statement) { return transform((JNode) statement); } } private void addVarsIfNotEmpty(JsVars vars) { if (!vars.isEmpty()) { getGlobalStatements().add(vars); } } private List getGlobalStatements() { return jsProgram.getGlobalBlock().getStatements(); } /** * Return false if the method needs to be generated. Some methods do not need any output, * in particular abstract methods and static intializers that are never called. */ private static boolean doesNotHaveConcreteImplementation(JMethod method) { return method.isAbstract() || method.isJsNative() || JjsUtils.isJsMemberUnnecessaryAccidentalOverride(method) || (JProgram.isClinit(method) && method.getEnclosingType().getClinitTarget() != method.getEnclosingType()); } private static class JavaToJsOperatorMap { private static final Map bOpMap = Maps.newEnumMap(JBinaryOperator.class); private static final Map uOpMap = Maps.newEnumMap(JUnaryOperator.class); static { bOpMap.put(JBinaryOperator.MUL, JsBinaryOperator.MUL); bOpMap.put(JBinaryOperator.DIV, JsBinaryOperator.DIV); bOpMap.put(JBinaryOperator.MOD, JsBinaryOperator.MOD); bOpMap.put(JBinaryOperator.ADD, JsBinaryOperator.ADD); bOpMap.put(JBinaryOperator.CONCAT, JsBinaryOperator.ADD); bOpMap.put(JBinaryOperator.SUB, JsBinaryOperator.SUB); bOpMap.put(JBinaryOperator.SHL, JsBinaryOperator.SHL); bOpMap.put(JBinaryOperator.SHR, JsBinaryOperator.SHR); bOpMap.put(JBinaryOperator.SHRU, JsBinaryOperator.SHRU); bOpMap.put(JBinaryOperator.LT, JsBinaryOperator.LT); bOpMap.put(JBinaryOperator.LTE, JsBinaryOperator.LTE); bOpMap.put(JBinaryOperator.GT, JsBinaryOperator.GT); bOpMap.put(JBinaryOperator.GTE, JsBinaryOperator.GTE); bOpMap.put(JBinaryOperator.EQ, JsBinaryOperator.EQ); bOpMap.put(JBinaryOperator.NEQ, JsBinaryOperator.NEQ); bOpMap.put(JBinaryOperator.BIT_AND, JsBinaryOperator.BIT_AND); bOpMap.put(JBinaryOperator.BIT_XOR, JsBinaryOperator.BIT_XOR); bOpMap.put(JBinaryOperator.BIT_OR, JsBinaryOperator.BIT_OR); bOpMap.put(JBinaryOperator.AND, JsBinaryOperator.AND); bOpMap.put(JBinaryOperator.OR, JsBinaryOperator.OR); bOpMap.put(JBinaryOperator.ASG, JsBinaryOperator.ASG); bOpMap.put(JBinaryOperator.ASG_ADD, JsBinaryOperator.ASG_ADD); bOpMap.put(JBinaryOperator.ASG_CONCAT, JsBinaryOperator.ASG_ADD); bOpMap.put(JBinaryOperator.ASG_SUB, JsBinaryOperator.ASG_SUB); bOpMap.put(JBinaryOperator.ASG_MUL, JsBinaryOperator.ASG_MUL); bOpMap.put(JBinaryOperator.ASG_DIV, JsBinaryOperator.ASG_DIV); bOpMap.put(JBinaryOperator.ASG_MOD, JsBinaryOperator.ASG_MOD); bOpMap.put(JBinaryOperator.ASG_SHL, JsBinaryOperator.ASG_SHL); bOpMap.put(JBinaryOperator.ASG_SHR, JsBinaryOperator.ASG_SHR); bOpMap.put(JBinaryOperator.ASG_SHRU, JsBinaryOperator.ASG_SHRU); bOpMap.put(JBinaryOperator.ASG_BIT_AND, JsBinaryOperator.ASG_BIT_AND); bOpMap.put(JBinaryOperator.ASG_BIT_OR, JsBinaryOperator.ASG_BIT_OR); bOpMap.put(JBinaryOperator.ASG_BIT_XOR, JsBinaryOperator.ASG_BIT_XOR); uOpMap.put(JUnaryOperator.INC, JsUnaryOperator.INC); uOpMap.put(JUnaryOperator.DEC, JsUnaryOperator.DEC); uOpMap.put(JUnaryOperator.NEG, JsUnaryOperator.NEG); uOpMap.put(JUnaryOperator.NOT, JsUnaryOperator.NOT); uOpMap.put(JUnaryOperator.BIT_NOT, JsUnaryOperator.BIT_NOT); } public static JsBinaryOperator get(JBinaryOperator op) { return bOpMap.get(op); } public static JsUnaryOperator get(JUnaryOperator op) { return uOpMap.get(op); } } private class CollectJsFunctionsForInlining extends JVisitor { // JavaScript functions that arise from methods that were not inlined in the Java AST // NOTE: We use a LinkedHashSet to preserve the order of insertion. So that the following passes // that use this result are deterministic. private Set functionsForJsInlining = Sets.newLinkedHashSet(); private JMethod currentMethod; @Override public void endVisit(JMethod x, Context ctx) { if (x.isJsniMethod()) { // These are methods whose bodies where not traversed by the Java method inliner. JsFunction function = jsFunctionsByJavaMethodBody.get(x.getBody()); if (function != null && function.getBody() != null) { functionsForJsInlining.add(function); } // Add all functions declared inside JSNI blocks as well. assert function != null; new JsModVisitor() { @Override public void endVisit(JsFunction x, JsContext ctx) { functionsForJsInlining.add(x); } }.accept(function); } currentMethod = null; } @Override public void endVisit(JMethodCall x, Context ctx) { JMethod target = x.getTarget(); if (target.isInliningAllowed() && (target.isJsniMethod() || program.getIndexedTypes().contains(target.getEnclosingType()) || target.getInliningMode() == InliningMode.FORCE_INLINE)) { // These are either: 1) callsites to JSNI functions, in which case MethodInliner did not // attempt to inline; 2) inserted by normalizations passes AFTER all inlining or 3) // calls to methods annotated with @ForceInline that were not inlined by the simple // MethodInliner. JsFunction function = jsFunctionsByJavaMethodBody.get(currentMethod.getBody()); if (function != null && function.getBody() != null) { functionsForJsInlining.add(function); } } } @Override public boolean visit(JMethod x, Context ctx) { currentMethod = x; return true; } public Set getFunctionsForJsInlining() { accept(program); return functionsForJsInlining; } } /** * Computes:

*

    *
  • 1. whether a constructors are live directly (through being in a new operation) or * indirectly (only called by other constructors). Only directly live constructors become * JS constructor, otherwise they will behave like regular static functions. *
  • 2. whether there exists cross class (static) calls or accesses that would need clinits to * be triggered. If not clinits need only be called in constructors. *
  • *
  • *
*/ private class RecordCrossClassCallsAndConstructorLiveness extends JVisitor { // TODO(rluble): This analysis should be extracted from GenerateJavaScriptAST into its own // JAVA optimization pass. Constructors that are not newed can be transformed into statified // regular methods; and methods that are not called from outside the class boundary can be // privatized. Currently we do not use the private modifier to avoid emitting clinits, instead // we use the result of this analysis (private methods CAN be called from JSNI in an unrelated // class, touche!). { crossClassTargets = Sets.newHashSet(); liveCtors = Sets.newIdentityHashSet(); } private JMethod currentMethod; @Override public void endVisit(JMethod x, Context ctx) { // methods which are exported or static indexed methods may be called externally if (x.isJsInteropEntryPoint() || (x.isStatic() && program.getIndexedMethods().contains(x))) { if (x instanceof JConstructor) { // exported ctors always considered live liveCtors.add((JConstructor) x); } // could be called from JS, so clinit must be called from body crossClassTargets.add(x); } currentMethod = null; } @Override public void endVisit(JMethodCall x, Context ctx) { JDeclaredType sourceType = currentMethod.getEnclosingType(); JDeclaredType targetType = x.getTarget().getEnclosingType(); if (sourceType.checkClinitTo(targetType)) { crossClassTargets.add(x.getTarget()); } } @Override public void endVisit(JNewInstance x, Context ctx) { super.endVisit(x, ctx); liveCtors.add(x.getTarget()); } @Override public void endVisit(JProgram x, Context ctx) { // Entry methods can be called externally, so they must run clinit. crossClassTargets.addAll(x.getEntryMethods()); } @Override public void endVisit(JsniMethodRef x, Context ctx) { if (x.getTarget() instanceof JConstructor) { liveCtors.add((JConstructor) x.getTarget()); } endVisit((JMethodCall) x, ctx); } @Override public boolean visit(JMethod x, Context ctx) { currentMethod = x; return true; } } private static class SortVisitor extends JVisitor { @Override public void endVisit(JClassType x, Context ctx) { x.sortFields(HasName.BY_NAME_COMPARATOR); x.sortMethods(JMethod.BY_SIGNATURE_COMPARATOR); } @Override public void endVisit(JInterfaceType x, Context ctx) { x.sortFields(HasName.BY_NAME_COMPARATOR); x.sortMethods(JMethod.BY_SIGNATURE_COMPARATOR); } @Override public void endVisit(JMethodBody x, Context ctx) { x.sortLocals(HasName.BY_NAME_COMPARATOR); } @Override public void endVisit(JProgram x, Context ctx) { Collections.sort(x.getEntryMethods(), JMethod.BY_SIGNATURE_COMPARATOR); Collections.sort(x.getDeclaredTypes(), HasName.BY_NAME_COMPARATOR); } @Override public boolean visit(JMethodBody x, Context ctx) { // No need to visit method bodies. return false; } } /** * This is the main entry point for the translation from Java to JavaScript. Starts from a * Java AST and constructs a JavaScript AST while collecting other useful information that * is used in subsequent passes. * * @param logger a TreeLogger * @param program a Java AST * @param jsProgram an (empty) JavaScript AST * @param symbolTable an (empty) symbol table that will be populated here * * @return A pair containing a JavaToJavaScriptMap and a Set of JsFunctions that need to be * considered for inlining. */ public static Pair> exec(TreeLogger logger, JProgram program, JsProgram jsProgram, CompilerContext compilerContext, TypeMapper typeMapper, Map symbolTable, PermutationProperties props) { Event event = SpeedTracerLogger.start(CompilerEventType.GENERATE_JS_AST); try { GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(logger, program, jsProgram, compilerContext, typeMapper, symbolTable, props); return generateJavaScriptAST.execImpl(); } finally { event.end(); } } private static final ImmutableList METHODS_PROVIDED_BY_PREAMBLE = ImmutableList.of( "Class.createForClass", "Class.createForPrimitive", "Class.createForInterface", "Class.createForEnum"); private final Map catchMap = Maps.newIdentityHashMap(); private final Set catchParamIdentifiers = Sets.newHashSet(); private final Map classScopes = Maps.newIdentityHashMap(); /** * A list of methods that are called from another class (ie might need to * clinit). */ private Set crossClassTargets = null; /** * Contains JsNames for all interface methods. A special scope is needed so * that independent classes will obfuscate their interface implementation * methods the same way. */ private final JsScope interfaceScope; private final JsProgram jsProgram; private Set liveCtors = null; /** * Classes that could potentially see uninitialized values for fields that are initialized in the * declaration. */ private Predicate uninitializedValuePotentiallyObservable; private final Map jsFunctionsByJavaMethodBody = Maps.newIdentityHashMap(); private final Map names = Maps.newIdentityHashMap(); /** * Contains JsNames for the Object instance methods, such as equals, hashCode, * and toString. All other class scopes have this scope as an ultimate parent. */ private final JsScope objectScope; private final Map polymorphicNames = Maps.newIdentityHashMap(); private final JProgram program; /** * SEt of all targets of JNameOf. */ private Set nameOfTargets = Sets.newHashSet(); private final TreeLogger logger; /** * Maps JsNames to machine-usable identifiers. */ private final Map symbolTable; /** * Contains JsNames for all globals, such as static fields and methods. */ private final JsScope topScope; private final Map javaTypeByGlobalStatement = Maps.newHashMap(); private final Map methodByGlobalStatement = Maps.newHashMap(); private final TypeMapper typeMapper; private final MinimalRebuildCache minimalRebuildCache; private final PermutationProperties properties; private JsFunction objectConstructorFunction; private OptionMethodNameDisplayMode.Mode methodNameMappingMode; private final boolean closureCompilerFormatEnabled; private final boolean optimize; // This is also used to do some final optimizations. // TODO(rluble) move optimizations to a Java AST optimization pass. private final boolean incremental; /** * If true, polymorphic functions are made anonymous vtable declarations and * not assigned topScope identifiers. */ private final boolean stripStack; private GenerateJavaScriptAST(TreeLogger logger, JProgram program, JsProgram jsProgram, CompilerContext compilerContext, TypeMapper typeMapper, Map symbolTable, PermutationProperties properties) { this.logger = logger; this.program = program; this.jsProgram = jsProgram; this.topScope = jsProgram.getScope(); this.objectScope = jsProgram.getObjectScope(); this.interfaceScope = new JsNormalScope(objectScope, "Interfaces"); this.minimalRebuildCache = compilerContext.getMinimalRebuildCache(); this.symbolTable = symbolTable; this.typeMapper = typeMapper; this.properties = properties; PrecompileTaskOptions options = compilerContext.getOptions(); this.optimize = options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT; this.methodNameMappingMode = options.getMethodNameDisplayMode(); assert methodNameMappingMode != null; this.incremental = options.isIncrementalCompileEnabled(); this.stripStack = JsStackEmulator.getStackMode(properties) == JsStackEmulator.StackMode.STRIP; this.closureCompilerFormatEnabled = options.isClosureCompilerFormatEnabled(); this.objectConstructorFunction = new JsFunction(SourceOrigin.UNKNOWN, topScope, topScope.findExistingName("Object")); } /** * Retrieves the runtime typeId for {@code type}. */ JExpression getRuntimeTypeReference(JReferenceType type) { return typeMapper.get(type); } private String mangleName(JField x) { return JjsUtils.mangleMemberName(x.getEnclosingType().getName(), x.getName()); } private String mangleNameForGlobal(JMethod method) { String s = JjsUtils.mangleMemberName(method.getEnclosingType().getName(), method.getName()) + "__"; for (JType type : method.getOriginalParamTypes()) { s += type.getJavahSignatureName(); } s += method.getOriginalReturnType().getJavahSignatureName(); return StringInterner.get().intern(s); } private String mangleNameForPackagePrivatePoly(JMethod method) { assert method.isPackagePrivate() && !method.isStatic(); /* * Package private instance methods in different package should not override each * other, so they must have distinct polymorphic names. Therefore, add the * package to the mangled name. */ String mangledName = Joiner.on("$").join( "package_private", JjsUtils.mangledNameString(method.getEnclosingType().getPackageName()), JjsUtils.mangledNameString(method)); return StringInterner.get().intern(JjsUtils.constructManglingSignature(method, mangledName)); } private String mangleNameForPoly(JMethod method) { if (method.isPrivate()) { return mangleNameForPrivatePoly(method); } else if (method.isPackagePrivate()) { return mangleNameForPackagePrivatePoly(method); } else { return mangleNameForPublicPoly(method); } } private String mangleNameForPublicPoly(JMethod method) { return StringInterner.get().intern( JjsUtils.constructManglingSignature(method, JjsUtils.mangledNameString(method))); } private String mangleNameForPrivatePoly(JMethod method) { assert method.isPrivate() && !method.isStatic(); /* * Private instance methods in different classes should not override each * other, so they must have distinct polymorphic names. Therefore, add the * class name to the mangled name. */ String mangledName = Joiner.on("$").join( "private", JjsUtils.mangledNameString(method.getEnclosingType()), JjsUtils.mangledNameString(method)); return StringInterner.get().intern(JjsUtils.constructManglingSignature(method, mangledName)); } private final Map classLiteralDeclarationsByType = Maps.newLinkedHashMap(); private void contructTypeToClassLiteralDeclarationMap() { /* * Must execute in clinit statement order, NOT field order, so that back * refs to super classes are preserved. */ JMethodBody clinitBody = (JMethodBody) program.getTypeClassLiteralHolder().getClinitMethod().getBody(); for (JStatement stmt : clinitBody.getStatements()) { if (!(stmt instanceof JDeclarationStatement)) { continue; } JDeclarationStatement classLiteralDeclaration = (JDeclarationStatement) stmt; JType type = program.getTypeByClassLiteralField( (JField) ((JDeclarationStatement) stmt).getVariableRef().getTarget()); assert !classLiteralDeclarationsByType.containsKey(type); classLiteralDeclarationsByType.put(type, classLiteralDeclaration); } } private Pair> execImpl() { NameClashesFixer.exec(program); uninitializedValuePotentiallyObservable = optimize ? ComputePotentiallyObservableUninitializedValues.analyze(program) : Predicates.alwaysTrue(); new FindNameOfTargets().accept(program); new SortVisitor().accept(program); if (!incremental) { // TODO(rluble): pull out this analysis and make it a Java AST optimization pass. new RecordCrossClassCallsAndConstructorLiveness().accept(program); } // Map class literals to their respective types. contructTypeToClassLiteralDeclarationMap(); new CreateNamesAndScopesVisitor().accept(program); new GenerateJavaScriptTransformer().transform(program); // TODO(spoon): Instead of gathering the information here, get it via // SourceInfo JavaToJavaScriptMap jjsMap = new JavaToJavaScriptMapImpl(program.getDeclaredTypes(), names, javaTypeByGlobalStatement, methodByGlobalStatement); Set functionsForJsInlining = incremental ? Collections.emptySet() : new CollectJsFunctionsForInlining().getFunctionsForJsInlining(); return Pair.create(jjsMap, functionsForJsInlining); } private JsFunction getJsFunctionFor(JMethod jMethod) { return jsFunctionsByJavaMethodBody.get(jMethod.getBody()); } private JsName getIndexedMethodJsName(String indexedName) { return names.get(program.getIndexedMethod(indexedName)); } private JsName getIndexedFieldJsName(String indexedName) { return names.get(program.getIndexedField(indexedName)); } private static JsNameRef createGlobalQualifier(String qualifier, SourceInfo sourceInfo) { return JsUtils.createQualifiedNameRef(JsInteropUtil.normalizeQualifier(qualifier), sourceInfo); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy