/**
  * Generator output can create opportunities for further generator execution, so runGenerators()
  * is repeated to a fixed point. But previously handled generator/reboundType pairs should be
  * ignored.
  */
 private void removePreviouslyReboundCombinations(
     final String generatorName, Set<String> newReboundTypeNames) {
   newReboundTypeNames.removeAll(
       Sets.newHashSet(
           Sets.filter(
               newReboundTypeNames,
               new Predicate<String>() {
                 @Override
                 public boolean apply(@Nullable String newReboundTypeName) {
                   return generatorNamesByPreviouslyReboundTypeName.containsEntry(
                       newReboundTypeName, generatorName);
                 }
               })));
 }
Ejemplo n.º 2
0
 /**
  * Builds the starter set of type names that should be indexed when seen during addType(). This
  * set is a thread safe instance variable and external logic is free to modify it as further
  * requirements are discovered.
  */
 private static Set<String> buildInitialTypeNamesToIndex() {
   Set<String> typeNamesToIndex = Sets.newHashSet();
   typeNamesToIndex.addAll(
       ImmutableList.of(
           "java.io.Serializable",
           "java.lang.Object",
           "java.lang.String",
           "java.lang.Class",
           "java.lang.CharSequence",
           "java.lang.Cloneable",
           "java.lang.Comparable",
           "java.lang.Enum",
           "java.lang.Iterable",
           "java.util.Iterator",
           "java.lang.AssertionError",
           "java.lang.Boolean",
           "java.lang.Byte",
           "java.lang.Character",
           "java.lang.Short",
           "java.lang.Integer",
           "java.lang.Long",
           "java.lang.Float",
           "java.lang.Double",
           "java.lang.Throwable",
           "com.google.gwt.core.client.GWT",
           JAVASCRIPTOBJECT,
           CLASS_LITERAL_HOLDER,
           "com.google.gwt.core.client.RunAsyncCallback",
           "com.google.gwt.core.client.impl.AsyncFragmentLoader",
           "com.google.gwt.core.client.impl.Impl",
           "com.google.gwt.core.client.prefetch.RunAsyncCode"));
   typeNamesToIndex.addAll(CODEGEN_TYPES_SET);
   return typeNamesToIndex;
 }
Ejemplo n.º 3
0
 public void setNeedsDelegateToWidget(Property property, JClassType type) {
   if (!isNeedsDelegateToWidget(type)) {
     needsDelegateToWidget.put(type, Sets.newHashSet(property));
   } else if (!needsDelegateToWidget.get(type).contains(property)) {
     needsDelegateToWidget.get(type).add(property);
   }
 }
 // VisibleForTesting
 protected Set<String> getTypeNames(Set<JDeclaredType> types) {
   Set<String> typeNames = Sets.newHashSet();
   for (JDeclaredType type : types) {
     typeNames.add(type.getName());
   }
   return typeNames;
 }
Ejemplo n.º 5
0
 /**
  * Return the set of methods affected (because they are or callers of) by the modifications to the
  * given set functions.
  */
 private Set<JMethod> affectedMethods(
     Set<JMethod> modifiedMethods, OptimizerContext optimizerCtx) {
   assert (modifiedMethods != null);
   Set<JMethod> affectedMethods = Sets.newLinkedHashSet();
   affectedMethods.addAll(modifiedMethods);
   affectedMethods.addAll(optimizerCtx.getCallers(modifiedMethods));
   return affectedMethods;
 }
 // VisibleForTesting
 protected Set<JDeclaredType> gatherReboundTypes(RebindPermutationOracle rpo) {
   Collection<CompilationUnit> compilationUnits =
       rpo.getCompilationState().getCompilationUnits();
   Set<JDeclaredType> reboundTypes = Sets.newLinkedHashSet();
   for (CompilationUnit compilationUnit : compilationUnits) {
     for (JDeclaredType type : compilationUnit.getTypes()) {
       ReboundTypeRecorder.exec(type, reboundTypes);
     }
   }
   return reboundTypes;
 }
Ejemplo n.º 7
0
  public void testSeparateModuleReferences() throws UnableToCompleteException {
    compilerContext = compilerContextBuilder.compileMonolithic(false).build();
    ModuleDef libraryOneModule =
        ModuleDefLoader.loadFromClassPath(
            TreeLogger.NULL,
            compilerContext,
            "com.google.gwt.dev.cfg.testdata.separate.libraryone.LibraryOne",
            false);

    // The module sees itself and it's direct fileset module as "target" modules.
    assertEquals(
        Sets.newHashSet(
            "com.google.gwt.dev.cfg.testdata.separate.libraryone.LibraryOne",
            "com.google.gwt.dev.cfg.testdata.separate.filesetone.FileSetOne"),
        libraryOneModule.getTargetLibraryModuleNames());
    // The module sees the referenced library module as a "library" module.
    assertEquals(
        Sets.newHashSet("com.google.gwt.dev.cfg.testdata.separate.librarytwo.LibraryTwo"),
        libraryOneModule.getExternalLibraryModuleNames());
  }
Ejemplo n.º 8
0
  public void testSeparateLibraryModuleReferences() throws UnableToCompleteException {
    compilerContext = compilerContextBuilder.compileMonolithic(false).build();
    ModuleDefLoader.loadFromClassPath(
        TreeLogger.NULL,
        compilerContext,
        "com.google.gwt.dev.cfg.testdata.separate.libraryone.LibraryOne",
        false);

    // The library writer was given the module and it's direct fileset module xml files as build
    // resources.
    assertEquals(
        Sets.newHashSet(
            "com/google/gwt/dev/cfg/testdata/separate/filesetone/FileSetOne.gwt.xml",
            "com/google/gwt/dev/cfg/testdata/separate/libraryone/LibraryOne.gwt.xml"),
        mockLibraryWriter.getBuildResourcePaths());
    // The library writer was given LibraryTwo as a dependency library.
    assertEquals(
        Sets.newHashSet("com.google.gwt.dev.cfg.testdata.separate.librarytwo.LibraryTwo"),
        mockLibraryWriter.getDependencyLibraryNames());
  }
Ejemplo n.º 9
0
  public void testResourcesVisible() throws Exception {
    TreeLogger logger = TreeLogger.NULL;
    ModuleDef one =
        ModuleDefLoader.loadFromClassPath(
            logger, compilerContext, "com.google.gwt.dev.cfg.testdata.merging.One");

    // Sees the logo.png image but not the java source file.
    assertEquals(
        one.getBuildResourceOracle().getPathNames(),
        Sets.newHashSet("com/google/gwt/dev/cfg/testdata/merging/resources/logo.png"));
  }
Ejemplo n.º 10
0
  static {
    if (System.getProperty("gwt.coverage") != null) {
      IMMORTAL_CODEGEN_TYPES_SET.add("com.google.gwt.lang.CoverageUtil");
    }
    CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET);

    /*
     * The format to trace methods is a colon-separated list of
     * "className.methodName", such as "Hello.onModuleLoad:Foo.bar". You can
     * fully-qualify a class to disambiguate classes, and you can also append
     * the JSNI signature of the method to disambiguate overloads, ala
     * "Foo.bar(IZ)".
     */
    String toTrace = System.getProperty("gwt.jjs.traceMethods");
    if (toTrace != null) {
      String[] split = toTrace.split(":");
      for (String str : split) {
        int pos = str.lastIndexOf('.');
        if (pos > 0) {
          String className = str.substring(0, pos);
          String methodName = str.substring(pos + 1);
          Set<String> set = traceMethods.get(className);
          if (set == null) {
            set = Sets.newHashSet();
            traceMethods.put(className, set);
          }
          set.add(methodName);
        }
      }
    }

    primitiveTypes.put(JPrimitiveType.BOOLEAN.getName(), JPrimitiveType.BOOLEAN);
    primitiveTypes.put(JPrimitiveType.BYTE.getName(), JPrimitiveType.BYTE);
    primitiveTypes.put(JPrimitiveType.CHAR.getName(), JPrimitiveType.CHAR);
    primitiveTypes.put(JPrimitiveType.DOUBLE.getName(), JPrimitiveType.DOUBLE);
    primitiveTypes.put(JPrimitiveType.FLOAT.getName(), JPrimitiveType.FLOAT);
    primitiveTypes.put(JPrimitiveType.INT.getName(), JPrimitiveType.INT);
    primitiveTypes.put(JPrimitiveType.LONG.getName(), JPrimitiveType.LONG);
    primitiveTypes.put(JPrimitiveType.SHORT.getName(), JPrimitiveType.SHORT);
    primitiveTypes.put(JPrimitiveType.VOID.getName(), JPrimitiveType.VOID);

    primitiveTypesDeprecated.put(
        JPrimitiveType.BOOLEAN.getJsniSignatureName(), JPrimitiveType.BOOLEAN);
    primitiveTypesDeprecated.put(JPrimitiveType.BYTE.getJsniSignatureName(), JPrimitiveType.BYTE);
    primitiveTypesDeprecated.put(JPrimitiveType.CHAR.getJsniSignatureName(), JPrimitiveType.CHAR);
    primitiveTypesDeprecated.put(
        JPrimitiveType.DOUBLE.getJsniSignatureName(), JPrimitiveType.DOUBLE);
    primitiveTypesDeprecated.put(JPrimitiveType.FLOAT.getJsniSignatureName(), JPrimitiveType.FLOAT);
    primitiveTypesDeprecated.put(JPrimitiveType.INT.getJsniSignatureName(), JPrimitiveType.INT);
    primitiveTypesDeprecated.put(JPrimitiveType.LONG.getJsniSignatureName(), JPrimitiveType.LONG);
    primitiveTypesDeprecated.put(JPrimitiveType.SHORT.getJsniSignatureName(), JPrimitiveType.SHORT);
    primitiveTypesDeprecated.put(JPrimitiveType.VOID.getJsniSignatureName(), JPrimitiveType.VOID);
  }
    /**
     * Figures out which generators should run based on the current state and runs them. Generator
     * execution can create new opportunities for further generator execution so this function
     * should be invoked repeatedly till a fixed point is reached.<br>
     * Returns whether a fixed point was reached.
     */
    private boolean runGenerators() throws UnableToCompleteException {
      boolean fixedPoint = true;
      boolean globalCompile = compilerContext.getOptions().shouldLink();
      Set<Rule> generatorRules = Sets.newHashSet(module.getGeneratorRules());

      for (Rule rule : generatorRules) {
        RuleGenerateWith generatorRule = (RuleGenerateWith) rule;
        String generatorName = generatorRule.getName();

        if (generatorRule.contentDependsOnTypes() && !globalCompile) {
          // Type unstable generators can only be safely run in the global phase.
          // TODO(stalcup): modify type unstable generators such that their output is no longer
          // unstable.
          continue;
        }

        // Run generator for new rebound types.
        Set<String> newReboundTypeNames =
            compilerContext.gatherNewReboundTypeNamesForGenerator(generatorName);
        fixedPoint &= runGenerator(generatorRule, newReboundTypeNames);

        // If the content of generator output varies when some relevant properties change and some
        // relevant properties have changed.
        if (generatorRule.contentDependsOnProperties()
            && relevantPropertiesHaveChanged(generatorRule)) {
          // Rerun the generator on old rebound types to replace old stale output.
          Set<String> oldReboundTypeNames =
              compilerContext.gatherOldReboundTypeNamesForGenerator(generatorName);
          fixedPoint &= runGenerator(generatorRule, oldReboundTypeNames);
        }

        compilerContext.getLibraryWriter().addRanGeneratorName(generatorName);
      }

      return fixedPoint;
    }
Ejemplo n.º 12
0
  /** Method inlining visitor. */
  private class InliningVisitor extends JChangeTrackingVisitor {

    public InliningVisitor(OptimizerContext optimizerCtx) {
      super(optimizerCtx);
    }

    /**
     * Resets with each new visitor, which is good since things that couldn't be inlined before
     * might become inlinable.
     */
    private final Set<JMethod> cannotInline = Sets.newHashSet();

    private final Stack<JExpression> expressionsWhoseValuesAreIgnored = Stack.create();

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

    @Override
    public void endVisit(JMethodCall x, Context ctx) {
      JMethod method = x.getTarget();

      if (getCurrentMethod() == method) {
        // Never try to inline a recursive call!
        return;
      }

      if (cannotInline.contains(method)) {
        return;
      }

      if (tryInlineMethodCall(x, ctx) == InlineResult.BLACKLIST) {
        // Do not try to inline this method again
        cannotInline.add(method);
      }
    }

    @Override
    public void endVisit(JMultiExpression x, Context ctx) {
      for (int i = 0; i < x.getExpressions().size() - 1; i++) {
        expressionsWhoseValuesAreIgnored.pop();
      }
    }

    private InlineResult tryInlineMethodCall(JMethodCall x, Context ctx) {
      JMethod method = x.getTarget();

      if (!method.isStatic() || method.isNative() || method.canBeImplementedExternally()) {
        // Only inline static methods that are not native.
        return InlineResult.BLACKLIST;
      }

      if (!method.isInliningAllowed()) {
        return InlineResult.BLACKLIST;
      }

      JMethodBody body = (JMethodBody) method.getBody();
      List<JStatement> stmts = body.getStatements();

      if (method.getEnclosingType() != null
          && method.getEnclosingType().getClinitMethod() == method
          && !stmts.isEmpty()) {
        // clinit() calls cannot be inlined unless they are empty
        return InlineResult.BLACKLIST;
      }

      // try to inline
      List<JExpression> expressions = extractExpressionsFromBody(body);
      if (expressions == null) {
        // If it will never be possible to inline the method, add it to a
        // blacklist
        return InlineResult.BLACKLIST;
      }

      return tryInlineBody(x, ctx, expressions, expressionsWhoseValuesAreIgnored.contains(x));
    }

    @Override
    public void endVisit(JNewInstance x, Context ctx) {
      // Do not inline new operations.
    }

    @Override
    public boolean visit(JExpressionStatement x, Context ctx) {
      expressionsWhoseValuesAreIgnored.push(x.getExpr());
      return true;
    }

    @Override
    public boolean enter(JMethod x, Context ctx) {
      if (program.getStaticImpl(x) != null) {
        /*
         * Never inline a static impl into the calling instance method. We used
         * to allow this, and it required all kinds of special logic in the
         * optimizers to keep the AST sane. This was because it was possible to
         * tighten an instance call to its static impl after the static impl had
         * already been inlined, this meant any "flow" type optimizer would have
         * to fake artificial flow from the instance method to the static impl.
         *
         * TODO: allow the inlining if we are the last remaining call site, and
         * prune the static impl? But it might tend to generate more code.
         */
        return false;
      }
      return true;
    }

    @Override
    public boolean visit(JMultiExpression x, Context ctx) {
      for (int i = 0; i < x.getExpressions().size() - 1; i++) {
        expressionsWhoseValuesAreIgnored.push(x.getExpression(i));
      }
      return true;
    }

    private JMethodCall createClinitCall(JMethodCall x) {
      JDeclaredType targetType = x.getTarget().getEnclosingType().getClinitTarget();
      if (!getCurrentMethod().getEnclosingType().checkClinitTo(targetType)) {
        // Access from this class to the target class won't trigger a clinit
        return null;
      }
      if (program.isStaticImpl(x.getTarget()) && !x.getTarget().getEnclosingType().isJsoType()) {
        // No clinit needed; target is really a non-jso instance method.
        return null;
      }
      if (JProgram.isClinit(x.getTarget())) {
        // This is a clinit call, doesn't need another clinit
        return null;
      }

      JMethod clinit = targetType.getClinitMethod();

      // If the clinit is a non-native, empty body we can optimize it out here
      if (!clinit.isNative() && (((JMethodBody) clinit.getBody())).getStatements().size() == 0) {
        return null;
      }

      return new JMethodCall(x.getSourceInfo(), null, clinit);
    }

    /**
     * Creates a JMultiExpression from a set of JExpressionStatements, optionally terminated by a
     * JReturnStatement. If the method doesn't match this pattern, it returns <code>null</code>.
     *
     * <p>If a method has a non-void return statement and can be represented as a multi-expression,
     * the output of the multi-expression will be the return expression of the method. If the method
     * is void, the output of the multi-expression should be considered undefined.
     */
    private List<JExpression> extractExpressionsFromBody(JMethodBody body) {
      List<JExpression> expressions = Lists.newArrayList();
      CloneCalleeExpressionVisitor cloner = new CloneCalleeExpressionVisitor();

      for (JStatement stmt : body.getStatements()) {
        if (stmt instanceof JDeclarationStatement) {
          JDeclarationStatement declStatement = (JDeclarationStatement) stmt;
          if (!(declStatement.getVariableRef() instanceof JLocalRef)) {
            return null;
          }
          JExpression initializer = declStatement.getInitializer();
          if (initializer == null) {
            continue;
          }
          JLocal local = (JLocal) declStatement.getVariableRef().getTarget();
          JExpression clone =
              new JBinaryOperation(
                  stmt.getSourceInfo(),
                  local.getType(),
                  JBinaryOperator.ASG,
                  new JLocalRef(declStatement.getVariableRef().getSourceInfo(), local),
                  cloner.cloneExpression(initializer));
          expressions.add(clone);
        } else if (stmt instanceof JExpressionStatement) {
          JExpressionStatement exprStmt = (JExpressionStatement) stmt;
          JExpression expr = exprStmt.getExpr();
          JExpression clone = cloner.cloneExpression(expr);
          expressions.add(clone);
        } else if (stmt instanceof JReturnStatement) {
          JReturnStatement returnStatement = (JReturnStatement) stmt;
          JExpression expr = returnStatement.getExpr();
          if (expr != null) {
            JExpression clone = cloner.cloneExpression(expr);
            clone = maybeCast(clone, body.getMethod().getType());
            expressions.add(clone);
          }
          // We hit an unconditional return; no need to evaluate anything else.
          break;
        } else {
          // Any other kind of statement won't be inlinable.
          return null;
        }
      }

      return expressions;
    }

    /**
     * Creates a lists of expression for evaluating a method call instance, possible clinit, and all
     * arguments. This is a precursor for inlining the remainder of a method that does not reference
     * any parameters.
     */
    private List<JExpression> expressionsIncludingArgs(JMethodCall x) {
      List<JExpression> expressions = Lists.newArrayListWithCapacity(x.getArgs().size() + 2);
      expressions.add(x.getInstance());
      expressions.add(createClinitCall(x));

      for (int i = 0, c = x.getArgs().size(); i < c; ++i) {
        JExpression arg = x.getArgs().get(i);
        ExpressionAnalyzer analyzer = new ExpressionAnalyzer();
        analyzer.accept(arg);

        if (analyzer.hasAssignment() || analyzer.canThrowException()) {
          expressions.add(arg);
        }
      }
      return expressions;
    }

    /**
     * Inline a call to an expression. Returns {@code InlineResult.BLACKLIST} if the method is
     * deemed not inlineable regardless of call site; {@code InlineResult.DO_NOT_BLACKLIST}
     * otherwise.
     */
    private InlineResult tryInlineBody(
        JMethodCall x,
        Context ctx,
        List<JExpression> bodyAsExpressionList,
        boolean ignoringReturn) {

      if (isTooComplexToInline(bodyAsExpressionList, ignoringReturn)) {
        return InlineResult.BLACKLIST;
      }

      // Do not inline anything that modifies one of its params.
      ExpressionAnalyzer targetAnalyzer = new ExpressionAnalyzer();
      targetAnalyzer.accept(bodyAsExpressionList);
      if (targetAnalyzer.hasAssignmentToParameter()) {
        return InlineResult.BLACKLIST;
      }

      // Make sure the expression we're about to inline doesn't include a call
      // to the target method!
      RecursionCheckVisitor recursionCheckVisitor = new RecursionCheckVisitor(x.getTarget());
      recursionCheckVisitor.accept(bodyAsExpressionList);
      if (recursionCheckVisitor.isRecursive()) {
        return InlineResult.BLACKLIST;
      }

      /*
       * After this point, it's possible that the method might be inlinable at
       * some call sites, depending on its arguments. From here on return 'true'
       * as the method might be inlinable elsewhere.
       */

      /*
       * There are a different number of parameters than args - this is likely a
       * result of parameter pruning. Don't consider this call site a candidate.
       *
       * TODO: would this be possible in the trivial delegation case?
       */
      if (x.getTarget().getParams().size() != x.getArgs().size()) {
        // Could not inline this call but the method might be inlineable at a different call site.
        return InlineResult.DO_NOT_BLACKLIST;
      }

      // Run the order check. This verifies that all the parameters are
      // referenced once and only once, not within a conditionally-executing
      // expression and before any tricky target expressions, such as:
      // - assignments to any variable
      // - expressions that throw exceptions
      // - field references

      /*
       * Ensure correct evaluation order or params relative to each other and to
       * other expressions.
       */
      OrderVisitor orderVisitor = new OrderVisitor(x.getTarget().getParams());
      orderVisitor.accept(bodyAsExpressionList);

      switch (orderVisitor.checkResults()) {
        case NO_REFERENCES:
          /*
           * A method that doesn't touch any parameters is trivially inlinable (this
           * covers the empty method case)
           */
          if (!x.hasSideEffects()) {
            markCallsAsSideEffectFree(bodyAsExpressionList);
          }
          new LocalVariableExtruder(getCurrentMethod()).accept(bodyAsExpressionList);
          List<JExpression> expressions = expressionsIncludingArgs(x);
          expressions.addAll(bodyAsExpressionList);
          ctx.replaceMe(JjsUtils.createOptimizedMultiExpression(ignoringReturn, expressions));
          return InlineResult.DO_NOT_BLACKLIST;
        case FAILS:
          /*
           * We can still inline in the case where all of the actual arguments are
           * "safe". They must have no side effects, and also have values which
           * could not be affected by the execution of any code within the callee.
           */
          for (JExpression arg : x.getArgs()) {
            ExpressionAnalyzer argAnalyzer = new ExpressionAnalyzer();
            argAnalyzer.accept(arg);

            if (argAnalyzer.hasAssignment()
                || argAnalyzer.accessesField()
                || argAnalyzer.createsObject()
                || argAnalyzer.canThrowException()) {

              /*
               * This argument evaluation could affect or be affected by the
               * callee so we cannot inline here.
               */
              // Could not inline this call but the method is potentially inlineable.
              return InlineResult.DO_NOT_BLACKLIST;
            }
          }
          // Fall through!
        case CORRECT_ORDER:
        default:
          if (!x.hasSideEffects()) {
            markCallsAsSideEffectFree(bodyAsExpressionList);
          }
          new LocalVariableExtruder(getCurrentMethod()).accept(bodyAsExpressionList);
          // Replace all params in the target expression with the actual arguments.
          ParameterReplacer replacer = new ParameterReplacer(x);
          replacer.accept(bodyAsExpressionList);
          bodyAsExpressionList.add(0, x.getInstance());
          bodyAsExpressionList.add(1, createClinitCall(x));
          ctx.replaceMe(
              JjsUtils.createOptimizedMultiExpression(ignoringReturn, bodyAsExpressionList));
          return InlineResult.DO_NOT_BLACKLIST;
      }
    }
  }
Ejemplo n.º 13
0
/** A visitor that visits every location in the AST where instrumentation is desirable. */
public abstract class CoverageVisitor extends JsModVisitor {
  private int lastLine = -1;
  private String lastFile = "";
  private Set<String> instrumentedFiles;

  /**
   * Nodes in this set are used in a context that expects a reference, not just an arbitrary
   * expression. For example, <code>delete</code> takes a reference. These are tracked because it
   * wouldn't be safe to rewrite <code>delete foo.bar</code> to <code>delete (line='123',foo).bar
   * </code>.
   */
  private final Set<JsNode> nodesInRefContext = Sets.newHashSet();

  public CoverageVisitor(Set<String> instrumentedFiles) {
    this.instrumentedFiles = instrumentedFiles;
  }

  @Override
  public void endVisit(JsArrayAccess x, JsContext ctx) {
    visitExpression(x, ctx);
  }

  @Override
  public void endVisit(JsBinaryOperation x, JsContext ctx) {
    visitExpression(x, ctx);
  }

  @Override
  public void endVisit(JsInvocation x, JsContext ctx) {
    nodesInRefContext.remove(x.getQualifier());
    visitExpression(x, ctx);
  }

  @Override
  public void endVisit(JsNameRef x, JsContext ctx) {
    visitExpression(x, ctx);
  }

  @Override
  public void endVisit(JsNew x, JsContext ctx) {
    visitExpression(x, ctx);
  }

  @Override
  public void endVisit(JsPostfixOperation x, JsContext ctx) {
    visitExpression(x, ctx);
  }

  @Override
  public void endVisit(JsPrefixOperation x, JsContext ctx) {
    visitExpression(x, ctx);
    nodesInRefContext.remove(x.getArg());
  }

  /**
   * This is essentially a hacked-up version of JsFor.traverse to account for flow control differing
   * from visitation order. It resets lastFile and lastLine before the condition and increment
   * expressions in the for loop so that location data will be recorded correctly.
   */
  @Override
  public boolean visit(JsFor x, JsContext ctx) {
    if (x.getInitExpr() != null) {
      x.setInitExpr(accept(x.getInitExpr()));
    } else if (x.getInitVars() != null) {
      x.setInitVars(accept(x.getInitVars()));
    }

    if (x.getCondition() != null) {
      resetPosition();
      x.setCondition(accept(x.getCondition()));
    }

    if (x.getIncrExpr() != null) {
      resetPosition();
      x.setIncrExpr(accept(x.getIncrExpr()));
    }
    accept(x.getBody());
    return false;
  }

  @Override
  public boolean visit(JsInvocation x, JsContext ctx) {
    nodesInRefContext.add(x.getQualifier());
    return true;
  }

  @Override
  public boolean visit(JsPrefixOperation x, JsContext ctx) {
    if (x.getOperator() == JsUnaryOperator.DELETE || x.getOperator() == JsUnaryOperator.TYPEOF) {
      nodesInRefContext.add(x.getArg());
    }
    return true;
  }

  /**
   * Similar to JsFor, this resets the current location information before evaluating the condition.
   */
  @Override
  public boolean visit(JsWhile x, JsContext ctx) {
    resetPosition();
    x.setCondition(accept(x.getCondition()));
    accept(x.getBody());
    return false;
  }

  protected abstract void endVisit(JsExpression x, JsContext ctx);

  private void resetPosition() {
    lastFile = "";
    lastLine = -1;
  }

  private void visitExpression(JsExpression x, JsContext ctx) {
    if (ctx.isLvalue()) {
      // Assignments to comma expressions aren't legal
      return;
    } else if (nodesInRefContext.contains(x)) {
      // Don't modify references into non-references
      return;
    } else if (!instrumentedFiles.contains(x.getSourceInfo().getFileName())) {
      return;
    } else if (x.getSourceInfo().getStartLine() == lastLine
        && (x.getSourceInfo().getFileName().equals(lastFile))) {
      return;
    }
    lastLine = x.getSourceInfo().getStartLine();
    lastFile = x.getSourceInfo().getFileName();
    endVisit(x, ctx);
  }
}
Ejemplo n.º 14
0
/** Root for the AST representing an entire Java program. */
public class JProgram extends JNode {

  /** Returns whether a class is a synthetic Prototype class generated by APT or user. */
  public static boolean isJsInterfacePrototype(JDeclaredType classType) {
    return classType instanceof JClassType && ((JClassType) classType).isJsPrototypeStub();
  }

  private static final class ArrayTypeComparator implements Comparator<JArrayType>, Serializable {
    @Override
    public int compare(JArrayType o1, JArrayType o2) {
      int comp = o1.getDims() - o2.getDims();
      if (comp != 0) {
        return comp;
      }
      return o1.getName().compareTo(o2.getName());
    }
  }

  private static final class TreeStatistics extends JVisitor {
    private int nodeCount = 0;

    public int getNodeCount() {
      return nodeCount;
    }

    @Override
    public boolean visit(JNode x, Context ctx) {
      nodeCount++;
      return true;
    }
  }

  public static final Set<String> CODEGEN_TYPES_SET =
      Sets.newLinkedHashSet(
          Arrays.asList(
              "com.google.gwt.lang.Array",
              "com.google.gwt.lang.Cast",
              "com.google.gwt.lang.RuntimePropertyRegistry",
              "com.google.gwt.lang.Exceptions",
              "com.google.gwt.lang.LongLib",
              "com.google.gwt.lang.Stats",
              "com.google.gwt.lang.Util"));

  /*
   * Types which are not referenced by any Java code, but are required to exist
   * after Java optimizations have run in order to be used by backend
   * code-generation. These classes and their members, are considered live
   * by ControlFlowAnalysis, at all times. Immortal types always live in the
   * initial fragment and their definitions are hoisted to appear before all
   * other types. Only static methods and fields are allowed, and no clinits
   * are run. Field initializers must be primitives, literals, or one of
   * JSO.createObject() or JSO.createArray().
   *
   * Classes are inserted into the JsAST in the order they appear in the Set.
   */
  public static final Set<String> IMMORTAL_CODEGEN_TYPES_SET =
      Sets.newLinkedHashSet(
          Arrays.asList(
              "com.google.gwt.lang.CollapsedPropertyHolder",
              "com.google.gwt.lang.JavaClassHierarchySetupUtil"));

  public static final String JAVASCRIPTOBJECT = "com.google.gwt.core.client.JavaScriptObject";

  static final Map<String, Set<String>> traceMethods = Maps.newHashMap();

  private static final Comparator<JArrayType> ARRAYTYPE_COMPARATOR = new ArrayTypeComparator();

  private static final int IS_ARRAY = 2;

  private static final int IS_CLASS = 3;

  private static final int IS_INTERFACE = 1;

  private static final int IS_NULL = 0;

  private static final Map<String, JPrimitiveType> primitiveTypes = Maps.newHashMap();

  @Deprecated
  private static final Map<String, JPrimitiveType> primitiveTypesDeprecated = Maps.newHashMap();

  static {
    if (System.getProperty("gwt.coverage") != null) {
      IMMORTAL_CODEGEN_TYPES_SET.add("com.google.gwt.lang.CoverageUtil");
    }
    CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET);

    /*
     * The format to trace methods is a colon-separated list of
     * "className.methodName", such as "Hello.onModuleLoad:Foo.bar". You can
     * fully-qualify a class to disambiguate classes, and you can also append
     * the JSNI signature of the method to disambiguate overloads, ala
     * "Foo.bar(IZ)".
     */
    String toTrace = System.getProperty("gwt.jjs.traceMethods");
    if (toTrace != null) {
      String[] split = toTrace.split(":");
      for (String str : split) {
        int pos = str.lastIndexOf('.');
        if (pos > 0) {
          String className = str.substring(0, pos);
          String methodName = str.substring(pos + 1);
          Set<String> set = traceMethods.get(className);
          if (set == null) {
            set = Sets.newHashSet();
            traceMethods.put(className, set);
          }
          set.add(methodName);
        }
      }
    }

    primitiveTypes.put(JPrimitiveType.BOOLEAN.getName(), JPrimitiveType.BOOLEAN);
    primitiveTypes.put(JPrimitiveType.BYTE.getName(), JPrimitiveType.BYTE);
    primitiveTypes.put(JPrimitiveType.CHAR.getName(), JPrimitiveType.CHAR);
    primitiveTypes.put(JPrimitiveType.DOUBLE.getName(), JPrimitiveType.DOUBLE);
    primitiveTypes.put(JPrimitiveType.FLOAT.getName(), JPrimitiveType.FLOAT);
    primitiveTypes.put(JPrimitiveType.INT.getName(), JPrimitiveType.INT);
    primitiveTypes.put(JPrimitiveType.LONG.getName(), JPrimitiveType.LONG);
    primitiveTypes.put(JPrimitiveType.SHORT.getName(), JPrimitiveType.SHORT);
    primitiveTypes.put(JPrimitiveType.VOID.getName(), JPrimitiveType.VOID);

    primitiveTypesDeprecated.put(
        JPrimitiveType.BOOLEAN.getJsniSignatureName(), JPrimitiveType.BOOLEAN);
    primitiveTypesDeprecated.put(JPrimitiveType.BYTE.getJsniSignatureName(), JPrimitiveType.BYTE);
    primitiveTypesDeprecated.put(JPrimitiveType.CHAR.getJsniSignatureName(), JPrimitiveType.CHAR);
    primitiveTypesDeprecated.put(
        JPrimitiveType.DOUBLE.getJsniSignatureName(), JPrimitiveType.DOUBLE);
    primitiveTypesDeprecated.put(JPrimitiveType.FLOAT.getJsniSignatureName(), JPrimitiveType.FLOAT);
    primitiveTypesDeprecated.put(JPrimitiveType.INT.getJsniSignatureName(), JPrimitiveType.INT);
    primitiveTypesDeprecated.put(JPrimitiveType.LONG.getJsniSignatureName(), JPrimitiveType.LONG);
    primitiveTypesDeprecated.put(JPrimitiveType.SHORT.getJsniSignatureName(), JPrimitiveType.SHORT);
    primitiveTypesDeprecated.put(JPrimitiveType.VOID.getJsniSignatureName(), JPrimitiveType.VOID);
  }

  /** Helper to create an assignment, used to initalize fields, etc. */
  public static JExpressionStatement createAssignmentStmt(
      SourceInfo info, JExpression lhs, JExpression rhs) {
    JBinaryOperation assign =
        new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
    return assign.makeStatement();
  }

  public static JLocal createLocal(
      SourceInfo info, String name, JType type, boolean isFinal, JMethodBody enclosingMethodBody) {
    assert (name != null);
    assert (type != null);
    assert (enclosingMethodBody != null);
    JLocal x = new JLocal(info, name, type, isFinal, enclosingMethodBody);
    enclosingMethodBody.addLocal(x);
    return x;
  }

  public static JParameter createParameter(
      SourceInfo info,
      String name,
      JType type,
      boolean isFinal,
      boolean isThis,
      JMethod enclosingMethod) {
    assert (name != null);
    assert (type != null);
    assert (enclosingMethod != null);

    JParameter x = new JParameter(info, name, type, isFinal, isThis, enclosingMethod);

    enclosingMethod.addParam(x);
    return x;
  }

  public static List<JDeclaredType> deserializeTypes(ObjectInputStream stream)
      throws IOException, ClassNotFoundException {
    @SuppressWarnings("unchecked")
    List<JDeclaredType> types = (List<JDeclaredType>) stream.readObject();
    for (JDeclaredType type : types) {
      type.readMembers(stream);
    }
    for (JDeclaredType type : types) {
      type.readMethodBodies(stream);
    }
    return types;
  }

  public static String getFullName(JMethod method) {
    return method.getEnclosingType().getName() + "." + getJsniSig(method);
  }

  public static String getJsniSig(JMethod method) {
    return getJsniSig(method, true);
  }

  public static String getJsniSig(JMethod method, boolean addReturnType) {
    StringBuilder sb = new StringBuilder();
    sb.append(method.getName());
    sb.append("(");
    for (int i = 0; i < method.getOriginalParamTypes().size(); ++i) {
      JType type = method.getOriginalParamTypes().get(i);
      sb.append(type.getJsniSignatureName());
    }
    sb.append(")");
    if (addReturnType) {
      sb.append(method.getOriginalReturnType().getJsniSignatureName());
    }
    return sb.toString();
  }

  public static boolean isClinit(JMethod method) {
    JDeclaredType enclosingType = method.getEnclosingType();
    if ((enclosingType != null) && (method == enclosingType.getClinitMethod())) {
      assert (method.getName().equals("$clinit"));
      return true;
    } else {
      return false;
    }
  }

  public static boolean isTracingEnabled() {
    return traceMethods.size() > 0;
  }

  public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream)
      throws IOException {
    stream.writeObject(types);
    for (JDeclaredType type : types) {
      type.writeMembers(stream);
    }
    for (JDeclaredType type : types) {
      type.writeMethodBodies(stream);
    }
  }

  public final List<JClassType> codeGenTypes = Lists.newArrayList();

  public final List<JClassType> immortalCodeGenTypes = Lists.newArrayList();

  public final JTypeOracle typeOracle;

  /** Special serialization treatment. */
  // TODO(stalcup): make this a set, or take special care to make updates unique when lazily loading
  // in types. At the moment duplicates are accumulating.
  private transient List<JDeclaredType> allTypes = Lists.newArrayList();

  private final Map<JType, JArrayType> arrayTypes = Maps.newHashMap();

  private Map<JReferenceType, JCastMap> castMaps;

  private BiMap<JType, JField> classLiteralFieldsByType;

  private final List<JMethod> entryMethods = Lists.newArrayList();

  private final Map<String, JField> indexedFields = Maps.newHashMap();

  private final Map<String, JMethod> indexedMethods = Maps.newHashMap();

  /** An index of types, from type name to type instance. */
  private final Map<String, JDeclaredType> indexedTypes = Maps.newHashMap();

  /**
   * The set of names of types (beyond the basic INDEX_TYPES_SET) whose instance should be indexed
   * when seen.
   */
  private final Set<String> typeNamesToIndex = buildInitialTypeNamesToIndex();

  private final Map<JMethod, JMethod> instanceToStaticMap = Maps.newIdentityHashMap();

  private String propertyProviderRegistratorTypeSourceName;

  // wrap up .add here, and filter out forced source
  private Set<String> referenceOnlyTypeNames = Sets.newHashSet();

  /** Filled in by ReplaceRunAsync, once the numbers are known. */
  private List<JRunAsync> runAsyncs = Lists.newArrayList();

  private LinkedHashSet<JRunAsync> initialAsyncSequence = Sets.newLinkedHashSet();

  private List<Integer> initialFragmentIdSequence = Lists.newArrayList();

  private String runtimeRebindRegistratorTypeName;

  private final Map<JMethod, JMethod> staticToInstanceMap = Maps.newIdentityHashMap();

  private JClassType typeClass;

  private JInterfaceType typeJavaIoSerializable;

  private JInterfaceType typeJavaLangCloneable;

  private JClassType typeJavaLangEnum;

  private JClassType typeJavaLangObject;

  private final Map<String, JDeclaredType> typeNameMap = Maps.newHashMap();

  private List<JReferenceType> typesByQueryId;

  private Map<JField, JType> typesByClassLiteralField;

  private JClassType typeSpecialClassLiteralHolder;

  private JClassType typeSpecialJavaScriptObject;

  private JClassType typeString;

  private FragmentPartitioningResult fragmentPartitioningResult;

  /**
   * Set of method that are pinned and should be skipped by optimizations such as inlining,
   * statification and prunned.
   */
  private Set<JMethod> pinnedMethods = Sets.newHashSet();

  /** Returns true if the inliner should try to inline {@code method}. */
  public boolean isInliningAllowed(JMethod method) {
    return !pinnedMethods.contains(method);
  }

  /** Returns true if {@link MakeCallsStatic} should try to statify {@code method}. */
  public boolean isDevitualizationAllowed(JMethod method) {
    return !pinnedMethods.contains(method);
  }

  /** Add a pinned method. */
  public void addPinnedMethod(JMethod method) {
    pinnedMethods.add(method);
  }

  public JProgram() {
    super(SourceOrigin.UNKNOWN);
    typeOracle = new JTypeOracle(this, true);
  }

  public JProgram(boolean hasWholeWorldKnowledge) {
    super(SourceOrigin.UNKNOWN);
    typeOracle = new JTypeOracle(this, hasWholeWorldKnowledge);
  }

  public void addEntryMethod(JMethod entryPoint) {
    assert !entryMethods.contains(entryPoint);
    entryMethods.add(entryPoint);
  }

  /**
   * Adds the given type name to the set of type names (beyond the basic INDEX_TYPES_SET) whose
   * instance should be indexed when seen.
   */
  public void addIndexedTypeName(String typeName) {
    typeNamesToIndex.add(typeName);
  }

  public void addReferenceOnlyType(JDeclaredType type) {
    referenceOnlyTypeNames.add(type.getName());
  }

  public void addType(JDeclaredType type) {
    allTypes.add(type);
    String name = type.getName();
    putIntoTypeMap(name, type);

    if (CODEGEN_TYPES_SET.contains(name)) {
      codeGenTypes.add((JClassType) type);
    }

    if (IMMORTAL_CODEGEN_TYPES_SET.contains(name)) {
      immortalCodeGenTypes.add((JClassType) type);
    }

    if (typeNamesToIndex.contains(name)) {
      indexedTypes.put(type.getShortName(), type);
      for (JMethod method : type.getMethods()) {
        if (!method.isPrivate()) {
          indexedMethods.put(type.getShortName() + '.' + method.getName(), method);
        }
      }
      for (JField field : type.getFields()) {
        indexedFields.put(type.getShortName() + '.' + field.getName(), field);
      }
      if (name.equals("java.lang.Object")) {
        typeJavaLangObject = (JClassType) type;
      } else if (name.equals("java.lang.String")) {
        typeString = (JClassType) type;
      } else if (name.equals("java.lang.Enum")) {
        typeJavaLangEnum = (JClassType) type;
      } else if (name.equals("java.lang.Class")) {
        typeClass = (JClassType) type;
      } else if (name.equals(JAVASCRIPTOBJECT)) {
        typeSpecialJavaScriptObject = (JClassType) type;
      } else if (name.equals("com.google.gwt.lang.ClassLiteralHolder")) {
        typeSpecialClassLiteralHolder = (JClassType) type;
      } else if (name.equals("java.lang.Cloneable")) {
        typeJavaLangCloneable = (JInterfaceType) type;
      } else if (name.equals("java.io.Serializable")) {
        typeJavaIoSerializable = (JInterfaceType) type;
      }
    }
  }

  /**
   * Return a minimal upper bound of a set of types. That is, a type that is a supertype of all the
   * input types and is as close as possible to the input types.
   *
   * <p>NOTE: Ideally we would like to return the least upper bound but it does not exit as the Java
   * type hierarchy is not really a lattice.
   *
   * <p>Hence, this function depends on the collection order. E.g.
   *
   * <p>I O |\ / \ | A B \ / \ / C
   *
   * <p>where I is an interface an {O,A,B,C} are classes.
   *
   * <p>generalizeTypes({A,C}) could either be I or O.
   *
   * <p>In particular generalizeTypes({I,A,C}) = I and generalizeTypes({A,C,I}) = O.
   */
  public JReferenceType generalizeTypes(Collection<? extends JReferenceType> types) {
    assert (types != null);
    assert (!types.isEmpty());
    Iterator<? extends JReferenceType> it = types.iterator();
    JReferenceType curType = it.next();
    while (it.hasNext()) {
      curType = generalizeTypes(curType, it.next());
      if (curType == typeJavaLangObject) {
        break;
      }
    }
    return curType;
  }

  /**
   * Return the least upper bound of two types. That is, the smallest type that is a supertype of
   * both types.
   */
  public JReferenceType generalizeTypes(JReferenceType type1, JReferenceType type2) {
    if (type1 == type2) {
      return type1;
    }

    if (type1 instanceof JNonNullType && type2 instanceof JNonNullType) {
      // Neither can be null.
      type1 = type1.getUnderlyingType();
      type2 = type2.getUnderlyingType();
      return generalizeTypes(type1, type2).getNonNull();
    } else if (type1 instanceof JNonNullType) {
      // type2 can be null, so the result can be null
      type1 = type1.getUnderlyingType();
    } else if (type2 instanceof JNonNullType) {
      // type1 can be null, so the result can be null
      type2 = type2.getUnderlyingType();
    }
    assert !(type1 instanceof JNonNullType);
    assert !(type2 instanceof JNonNullType);

    int classify1 = classifyType(type1);
    int classify2 = classifyType(type2);

    if (classify1 == IS_NULL) {
      return type2;
    }

    if (classify2 == IS_NULL) {
      return type1;
    }

    if (classify1 == classify2) {

      // same basic kind of type
      if (classify1 == IS_INTERFACE) {

        if (typeOracle.canTriviallyCast(type1, type2)) {
          return type2;
        }

        if (typeOracle.canTriviallyCast(type2, type1)) {
          return type1;
        }

        // unrelated
        return typeJavaLangObject;

      } else if (classify1 == IS_ARRAY) {

        JArrayType aType1 = (JArrayType) type1;
        JArrayType aType2 = (JArrayType) type2;
        int dims1 = aType1.getDims();
        int dims2 = aType2.getDims();

        int minDims = Math.min(dims1, dims2);
        /*
         * At a bare minimum, any two arrays generalize to an Object array with
         * one less dim than the lesser of the two; that is, int[][][][] and
         * String[][][] generalize to Object[][]. If minDims is 1, then they
         * just generalize to Object.
         */
        JReferenceType minimalGeneralType;
        if (minDims > 1) {
          minimalGeneralType = getTypeArray(typeJavaLangObject, minDims - 1);
        } else {
          minimalGeneralType = typeJavaLangObject;
        }

        if (dims1 == dims2) {

          // Try to generalize by leaf types
          JType leafType1 = aType1.getLeafType();
          JType leafType2 = aType2.getLeafType();

          if (!(leafType1 instanceof JReferenceType) || !(leafType2 instanceof JReferenceType)) {
            return minimalGeneralType;
          }

          /*
           * Both are reference types; the result is the generalization of the
           * leaf types combined with the number of dims; that is, Foo[] and
           * Bar[] generalize to X[] where X is the generalization of Foo and
           * Bar.
           */
          JReferenceType leafRefType1 = (JReferenceType) leafType1;
          JReferenceType leafRefType2 = (JReferenceType) leafType2;

          /**
           * Never generalize arrays to arrays of {@link JNonNullType} as null array initialization
           * is not accounted for in {@link TypeTightener}.
           */
          JReferenceType leafGeneralization =
              generalizeTypes(leafRefType1, leafRefType2).getUnderlyingType();
          return getTypeArray(leafGeneralization, dims1);

        } else {

          // Conflicting number of dims

          // int[][] and Object[] generalize to Object[]
          JArrayType lesser = dims1 < dims2 ? aType1 : aType2;
          if (lesser.getLeafType() == typeJavaLangObject) {
            return lesser;
          }

          // Totally unrelated
          return minimalGeneralType;
        }

      } else {

        assert (classify1 == IS_CLASS);
        JClassType class1 = (JClassType) type1;
        JClassType class2 = (JClassType) type2;

        /*
         * see how far each type is from object; walk the one who's farther up
         * until they're even; then walk them up together until they meet (worst
         * case at Object)
         */
        int distance1 = countSuperTypes(class1);
        int distance2 = countSuperTypes(class2);
        for (; distance1 > distance2; --distance1) {
          class1 = class1.getSuperClass();
        }

        for (; distance1 < distance2; --distance2) {
          class2 = class2.getSuperClass();
        }

        while (class1 != class2) {
          class1 = class1.getSuperClass();
          class2 = class2.getSuperClass();
        }

        return class1;
      }
    } else {

      // different kinds of types
      int lesser = Math.min(classify1, classify2);
      int greater = Math.max(classify1, classify2);

      JReferenceType tLesser = classify1 < classify2 ? type1 : type2;
      JReferenceType tGreater = classify1 > classify2 ? type1 : type2;

      if (lesser == IS_INTERFACE && greater == IS_CLASS) {

        // just see if the class implements the interface
        if (typeOracle.canTriviallyCast(tGreater, tLesser)) {
          return tLesser;
        }

        // unrelated
        return typeJavaLangObject;

      } else if (greater == IS_ARRAY
          && ((tLesser == typeJavaLangCloneable) || (tLesser == typeJavaIoSerializable))) {
        return tLesser;
      } else {

        // unrelated: the best commonality between an interface and array, or
        // between an array and a class is Object
        return typeJavaLangObject;
      }
    }
  }

  /**
   * Returns a sorted list of array types, so the returned set can be iterated over without
   * introducing nondeterminism.
   */
  public List<JArrayType> getAllArrayTypes() {
    List<JArrayType> result = Lists.newArrayList(arrayTypes.values());
    Collections.sort(result, ARRAYTYPE_COMPARATOR);
    return result;
  }

  public Map<JReferenceType, JCastMap> getCastMap() {
    return Collections.unmodifiableMap(castMaps);
  }

  public JCastMap getCastMap(JReferenceType referenceType) {
    // ensure jsonCastableTypeMaps has been initialized
    // it might not have been if the ImplementCastsAndTypeChecks has not been run
    if (castMaps == null) {
      initTypeInfo(null);
    }
    return castMaps.get(referenceType);
  }

  public JField getClassLiteralField(JType type) {
    return classLiteralFieldsByType.get(isJavaScriptObject(type) ? getJavaScriptObject() : type);
  }

  public String getClassLiteralName(JType type) {
    return type.getJavahSignatureName() + "_classLit";
  }

  public List<JDeclaredType> getDeclaredTypes() {
    return allTypes;
  }

  public List<JMethod> getEntryMethods() {
    return entryMethods;
  }

  public int getFragmentCount() {
    // Initial fragment is the +1.
    return runAsyncs.size() + 1;
  }

  public FragmentPartitioningResult getFragmentPartitioningResult() {
    return fragmentPartitioningResult;
  }

  // TODO(stalcup): this is a blatant bug. there's no unambiguous way to convert from binary name to
  // source name. JProgram needs to index types both ways.
  public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) {
    String srcTypeName = qualifiedBinaryOrSourceName.replace('$', '.');

    return typeNameMap.get(srcTypeName);
  }

  public JField getIndexedField(String string) {
    JField field = indexedFields.get(string);
    if (field == null) {
      throw new InternalCompilerException("Unable to locate index field: " + string);
    }
    return field;
  }

  public Collection<JField> getIndexedFields() {
    return Collections.unmodifiableCollection(indexedFields.values());
  }

  public JMethod getIndexedMethod(String string) {
    JMethod method = indexedMethods.get(string);
    if (method == null) {
      throw new InternalCompilerException("Unable to locate index method: " + string);
    }
    return method;
  }

  public Collection<JMethod> getIndexedMethods() {
    return Collections.unmodifiableCollection(indexedMethods.values());
  }

  public JDeclaredType getIndexedType(String string) {
    JDeclaredType type = indexedTypes.get(string);
    if (type == null) {
      throw new InternalCompilerException("Unable to locate index type: " + string);
    }
    return type;
  }

  public LinkedHashSet<JRunAsync> getInitialAsyncSequence() {
    return initialAsyncSequence;
  }

  public List<Integer> getInitialFragmentIdSequence() {
    return initialFragmentIdSequence;
  }

  public JClassType getJavaScriptObject() {
    return typeSpecialJavaScriptObject;
  }

  public JBooleanLiteral getLiteralBoolean(boolean value) {
    return JBooleanLiteral.get(value);
  }

  public JCharLiteral getLiteralChar(char value) {
    return JCharLiteral.get(value);
  }

  public JDoubleLiteral getLiteralDouble(double d) {
    return JDoubleLiteral.get(d);
  }

  public JFloatLiteral getLiteralFloat(float f) {
    return JFloatLiteral.get(f);
  }

  public JIntLiteral getLiteralInt(int value) {
    return JIntLiteral.get(value);
  }

  public JLongLiteral getLiteralLong(long value) {
    return JLongLiteral.get(value);
  }

  public JNullLiteral getLiteralNull() {
    return JNullLiteral.INSTANCE;
  }

  public JStringLiteral getStringLiteral(SourceInfo sourceInfo, String s) {
    sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(Literal.STRING));
    return new JStringLiteral(sourceInfo, s, typeString);
  }

  public List<JDeclaredType> getModuleDeclaredTypes() {
    List<JDeclaredType> moduleDeclaredTypes = Lists.newArrayList();
    for (JDeclaredType type : allTypes) {
      if (isReferenceOnly(type)) {
        continue;
      }
      moduleDeclaredTypes.add(type);
    }
    return moduleDeclaredTypes;
  }

  public int getNodeCount() {
    Event countEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "countNodes");
    TreeStatistics treeStats = new TreeStatistics();
    treeStats.accept(this);
    int numNodes = treeStats.getNodeCount();
    countEvent.end();
    return numNodes;
  }

  public JField getNullField() {
    return JField.NULL_FIELD;
  }

  public JMethod getNullMethod() {
    return JMethod.NULL_METHOD;
  }

  public String getPropertyProviderRegistratorTypeSourceName() {
    return propertyProviderRegistratorTypeSourceName;
  }

  public List<JRunAsync> getRunAsyncs() {
    return runAsyncs;
  }

  public int getCommonAncestorFragmentId(int thisFragmentId, int thatFragmentId) {
    return fragmentPartitioningResult.getCommonAncestorFragmentId(thisFragmentId, thatFragmentId);
  }

  public String getRuntimeRebindRegistratorTypeSourceName() {
    return runtimeRebindRegistratorTypeName;
  }

  public JMethod getStaticImpl(JMethod method) {
    JMethod staticImpl = instanceToStaticMap.get(method);
    assert staticImpl == null || staticImpl.getEnclosingType().getMethods().contains(staticImpl);
    return staticImpl;
  }

  public JArrayType getTypeArray(JType elementType) {
    JArrayType arrayType = arrayTypes.get(elementType);
    if (arrayType == null) {
      arrayType = new JArrayType(elementType);
      arrayTypes.put(elementType, arrayType);
    }
    return arrayType;
  }

  public JArrayType getTypeArray(JType leafType, int dimensions) {
    assert dimensions > 0;
    assert (!(leafType instanceof JArrayType));
    JArrayType result = getTypeArray(leafType);
    while (dimensions > 1) {
      result = getTypeArray(result);
      --dimensions;
    }
    return result;
  }

  public JType getTypeByClassLiteralField(JField field) {
    return typesByClassLiteralField.get(field);
  }

  public JClassType getTypeClassLiteralHolder() {
    return typeSpecialClassLiteralHolder;
  }

  /** Returns the JType corresponding to a JSNI type reference. */
  public JType getTypeFromJsniRef(String className) {
    int dim = 0;
    while (className.endsWith("[]")) {
      dim++;
      className = className.substring(0, className.length() - 2);
    }

    JType type = primitiveTypes.get(className);
    if (type == null) {
      type = getFromTypeMap(className);
    }
    // TODO(deprecation): remove support for this.
    if (type == null) {
      type = primitiveTypesDeprecated.get(className);
    }
    if (type == null || dim == 0) {
      return type;
    } else {
      return getTypeArray(type, dim);
    }
  }

  public JClassType getTypeJavaLangClass() {
    return typeClass;
  }

  public JClassType getTypeJavaLangEnum() {
    return typeJavaLangEnum;
  }

  public JClassType getTypeJavaLangObject() {
    return typeJavaLangObject;
  }

  public JClassType getTypeJavaLangString() {
    return typeString;
  }

  public Set<String> getTypeNamesToIndex() {
    return typeNamesToIndex;
  }

  public JNullType getTypeNull() {
    return JNullType.INSTANCE;
  }

  public JPrimitiveType getTypePrimitiveBoolean() {
    return JPrimitiveType.BOOLEAN;
  }

  public JPrimitiveType getTypePrimitiveByte() {
    return JPrimitiveType.BYTE;
  }

  public JPrimitiveType getTypePrimitiveChar() {
    return JPrimitiveType.CHAR;
  }

  public JPrimitiveType getTypePrimitiveDouble() {
    return JPrimitiveType.DOUBLE;
  }

  public JPrimitiveType getTypePrimitiveFloat() {
    return JPrimitiveType.FLOAT;
  }

  public JPrimitiveType getTypePrimitiveInt() {
    return JPrimitiveType.INT;
  }

  public JPrimitiveType getTypePrimitiveLong() {
    return JPrimitiveType.LONG;
  }

  public JPrimitiveType getTypePrimitiveShort() {
    return JPrimitiveType.SHORT;
  }

  public List<JReferenceType> getTypesByQueryId() {
    return typesByQueryId;
  }

  public JPrimitiveType getTypeVoid() {
    return JPrimitiveType.VOID;
  }

  public void initTypeInfo(Map<JReferenceType, JCastMap> castMapForType) {
    castMaps = castMapForType;
    if (castMaps == null) {
      castMaps = Maps.newIdentityHashMap();
    }
  }

  public boolean isJavaLangString(JType type) {
    return type == typeString || type == typeString.getNonNull();
  }

  public boolean isJavaScriptObject(JType type) {
    if (type instanceof JReferenceType && typeSpecialJavaScriptObject != null) {
      return typeOracle.canTriviallyCast((JReferenceType) type, typeSpecialJavaScriptObject);
    }
    return false;
  }

  public boolean isReferenceOnly(JDeclaredType type) {
    if (type != null) {
      return referenceOnlyTypeNames.contains(type.getName());
    }
    return false;
  }

  public boolean isStaticImpl(JMethod method) {
    return staticToInstanceMap.containsKey(method);
  }

  public static JInterfaceType maybeGetJsInterfaceFromPrototype(JDeclaredType classType) {
    if (classType == null) {
      return null;
    }
    if (classType instanceof JClassType && ((JClassType) classType).isJsPrototypeStub()) {
      for (JInterfaceType intf : classType.getImplements()) {
        if (intf.isJsInterface() && intf.getJsPrototype() != null) {
          return intf;
        }
      }
    }
    return null;
  }

  public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) {
    // Make it into a source type name.
    String srcTypeName = qualifiedBinaryName.replace('$', '.');
    typeNameMap.put(srcTypeName, type);
  }

  public void putStaticImpl(JMethod method, JMethod staticImpl) {
    instanceToStaticMap.put(method, staticImpl);
    staticToInstanceMap.put(staticImpl, method);
    if (method.isTrace()) {
      staticImpl.setTrace();
    }
  }

  public void recordClassLiteralFields(Map<JType, JField> classLiteralFields) {
    this.classLiteralFieldsByType = HashBiMap.create(classLiteralFields);
    this.typesByClassLiteralField = classLiteralFieldsByType.inverse();
  }

  public void removeStaticImplMapping(JMethod staticImpl) {
    JMethod instanceMethod = staticToInstanceMap.remove(staticImpl);
    if (instanceMethod != null) {
      instanceToStaticMap.remove(instanceMethod);
    }
  }

  public void setFragmentPartitioningResult(FragmentPartitioningResult result) {
    fragmentPartitioningResult = result;
  }

  public void setInitialFragmentIdSequence(List<Integer> initialFragmentIdSequence) {
    this.initialFragmentIdSequence = initialFragmentIdSequence;
  }

  public void setRunAsyncs(List<JRunAsync> runAsyncs) {
    this.runAsyncs = ImmutableList.copyOf(runAsyncs);
  }

  public void setInitialAsyncSequence(LinkedHashSet<JRunAsync> initialAsyncSequence) {
    assert this.initialAsyncSequence.isEmpty();
    initialFragmentIdSequence = Lists.newArrayList();
    // TODO(rluble): hack for now the initial fragments correspond to the initial runAsyncIds.
    initialFragmentIdSequence.addAll(
        Collections2.transform(
            initialAsyncSequence,
            new Function<JRunAsync, Integer>() {
              @Override
              public Integer apply(JRunAsync runAsync) {
                return runAsync.getRunAsyncId();
              }
            }));
    this.initialAsyncSequence = initialAsyncSequence;
  }

  public void setPropertyProviderRegistratorTypeSourceName(
      String propertyProviderRegistratorTypeSourceName) {
    this.propertyProviderRegistratorTypeSourceName = propertyProviderRegistratorTypeSourceName;
  }

  public void setRuntimeRebindRegistratorTypeName(String runtimeRebindRegistratorTypeName) {
    this.runtimeRebindRegistratorTypeName = runtimeRebindRegistratorTypeName;
  }

  /**
   * If {@code method} is a static impl method, returns the instance method that {@code method} is
   * the implementation of. Otherwise, returns{@code null}.
   */
  public JMethod instanceMethodForStaticImpl(JMethod method) {
    return staticToInstanceMap.get(method);
  }

  /**
   * Return the greatest lower bound of two types. That is, return the largest type that is a
   * subtype of both inputs.
   */
  public JReferenceType strongerType(JReferenceType type1, JReferenceType type2) {
    if (type1 == type2) {
      return type1;
    }

    if (type1 instanceof JNullType || type2 instanceof JNullType) {
      return JNullType.INSTANCE;
    }

    if (type1 instanceof JNonNullType != type2 instanceof JNonNullType) {
      // If either is non-nullable, the result should be non-nullable.
      return strongerType(type1.getNonNull(), type2.getNonNull());
    }

    if (typeOracle.canTriviallyCast(type1, type2)) {
      return type1;
    }

    if (typeOracle.canTriviallyCast(type2, type1)) {
      return type2;
    }

    // cannot determine a strong type, just return the first one (this makes two
    // "unrelated" interfaces work correctly in TypeTightener
    return type1;
  }

  @Override
  public void traverse(JVisitor visitor, Context ctx) {
    if (visitor.visit(this, ctx)) {
      visitModuleTypes(visitor);
    }
    visitor.endVisit(this, ctx);
  }

  /**
   * Builds the starter set of type names that should be indexed when seen during addType(). This
   * set is a thread safe instance variable and external logic is free to modify it as further
   * requirements are discovered.
   */
  private static Set<String> buildInitialTypeNamesToIndex() {
    Set<String> typeNamesToIndex = Sets.newHashSet();
    typeNamesToIndex.addAll(
        ImmutableList.of(
            "java.io.Serializable",
            "java.lang.Object",
            "java.lang.String",
            "java.lang.Class",
            "java.lang.CharSequence",
            "java.lang.Cloneable",
            "java.lang.Comparable",
            "java.lang.Enum",
            "java.lang.Iterable",
            "java.util.Iterator",
            "java.lang.AssertionError",
            "java.lang.Boolean",
            "java.lang.Byte",
            "java.lang.Character",
            "java.lang.Short",
            "java.lang.Integer",
            "java.lang.Long",
            "java.lang.Float",
            "java.lang.Double",
            "java.lang.Throwable",
            "com.google.gwt.core.client.GWT",
            JProgram.JAVASCRIPTOBJECT,
            "com.google.gwt.lang.RuntimeRebinder",
            "com.google.gwt.lang.ClassLiteralHolder",
            "com.google.gwt.core.client.RunAsyncCallback",
            "com.google.gwt.core.client.impl.AsyncFragmentLoader",
            "com.google.gwt.core.client.impl.Impl",
            "com.google.gwt.core.client.prefetch.RunAsyncCode"));
    typeNamesToIndex.addAll(CODEGEN_TYPES_SET);
    return typeNamesToIndex;
  }

  public void visitAllTypes(JVisitor visitor) {
    visitor.accept(allTypes);
  }

  public void visitModuleTypes(JVisitor visitor) {
    for (JDeclaredType type : allTypes) {
      if (isReferenceOnly(type)) {
        continue;
      }
      visitor.accept(type);
    }
  }

  private int classifyType(JReferenceType type) {
    assert !(type instanceof JNonNullType);
    if (type instanceof JNullType) {
      return IS_NULL;
    } else if (type instanceof JInterfaceType) {
      return IS_INTERFACE;
    } else if (type instanceof JArrayType) {
      return IS_ARRAY;
    } else if (type instanceof JClassType) {
      return IS_CLASS;
    }
    throw new InternalCompilerException("Unknown reference type");
  }

  private int countSuperTypes(JClassType type) {
    int count = 0;
    while ((type = type.getSuperClass()) != null) {
      ++count;
    }
    return count;
  }

  /**
   * See notes in {@link #writeObject(ObjectOutputStream)}.
   *
   * @see #writeObject(ObjectOutputStream)
   */
  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    allTypes = deserializeTypes(stream);
    stream.defaultReadObject();
  }

  /**
   * Serializing the Java AST is a multi-step process to avoid blowing out the stack.
   *
   * <ol>
   *   <li>Write all declared types in a lightweight manner to establish object identity for types
   *   <li>Write all fields; write all methods in a lightweight manner to establish object identity
   *       for methods
   *   <li>Write all method bodies
   *   <li>Write everything else, which will mostly refer to already-serialized objects.
   *   <li>Write the bodies of the entry methods (unlike all other methods, these are not contained
   *       by any type.
   * </ol>
   *
   * The goal of this process to to avoid "running away" with the stack. Without special logic here,
   * lots of things would reference types, method body code would reference both types and other
   * methods, and really, really long recursion chains would result.
   */
  private void writeObject(ObjectOutputStream stream) throws IOException {
    serializeTypes(allTypes, stream);
    stream.defaultWriteObject();
  }
}
Ejemplo n.º 15
0
/** A mock and in memory library for setting up test situations. */
public class MockLibrary implements Library {

  public static List<MockLibrary> createRandomLibraryGraph(
      int libraryCount, int maxParentsPerChild) {
    Random rng = new Random();
    List<MockLibrary> libraries = Lists.newArrayList();
    libraries.add(new MockLibrary("RootLibrary"));
    for (int libraryIndex = 0; libraryIndex < libraryCount; libraryIndex++) {
      MockLibrary childLibrary = new MockLibrary("Library-" + libraryIndex);
      int parentCount = rng.nextInt(maxParentsPerChild) + 1;

      for (int parentIndex = 0; parentIndex < parentCount; parentIndex++) {
        Library parentLibrary = libraries.get(rng.nextInt(libraries.size()));
        parentLibrary.getDependencyLibraryNames().add(childLibrary.getLibraryName());
      }
      libraries.add(childLibrary);
    }
    Collections.shuffle(libraries);
    return libraries;
  }

  private Set<String> buildResourcePaths = Sets.newHashSet();
  private Multimap<String, String> compilationUnitNamesByNestedBinaryName = HashMultimap.create();
  private Multimap<String, String> compilationUnitNamesByNestedSourceName = HashMultimap.create();
  private Map<String, CompilationUnit> compilationUnitsByTypeName = Maps.newHashMap();
  private Set<String> compilationUnitTypeNames = Sets.newHashSet();
  private Set<String> dependencyLibraryNames = Sets.newLinkedHashSet();
  private String libraryName;
  private Multimap<String, String> nestedBinaryNamesByCompilationUnitName = HashMultimap.create();
  private Multimap<String, String> nestedSourceNamesByCompilationUnitName = HashMultimap.create();
  private Multimap<String, String> newBindingPropertyValuesByName = ArrayListMultimap.create();
  private Multimap<String, String> newConfigurationPropertyValuesByName =
      ArrayListMultimap.create();
  private Set<String> ranGeneratorNames = Sets.newHashSet();
  private Set<String> reboundTypeNames = Sets.newHashSet();
  private Set<String> superSourceCompilationUnitTypeNames = Sets.newHashSet();

  public MockLibrary(String libraryName) {
    this.libraryName = libraryName;
  }

  public void addCompilationUnit(CompilationUnit compilationUnit) {
    String compilationUnitTypeSourceName = compilationUnit.getTypeName();
    compilationUnitsByTypeName.put(compilationUnitTypeSourceName, compilationUnit);
    compilationUnitTypeNames.add(compilationUnitTypeSourceName);

    Collection<CompiledClass> compiledClasses = compilationUnit.getCompiledClasses();
    for (CompiledClass compiledClass : compiledClasses) {
      String sourceName = compiledClass.getSourceName();
      String binaryName = InternalName.toBinaryName(compiledClass.getInternalName());
      nestedSourceNamesByCompilationUnitName.put(compilationUnitTypeSourceName, sourceName);
      nestedBinaryNamesByCompilationUnitName.put(compilationUnitTypeSourceName, binaryName);
      compilationUnitNamesByNestedSourceName.put(sourceName, compilationUnitTypeSourceName);
      compilationUnitNamesByNestedBinaryName.put(binaryName, compilationUnitTypeSourceName);
    }
  }

  public void addSuperSourceCompilationUnit(CompilationUnit superSourceCompilationUnit) {
    String superSourceCompilationUnitTypeSourceName = superSourceCompilationUnit.getTypeName();
    compilationUnitsByTypeName.put(
        superSourceCompilationUnitTypeSourceName, superSourceCompilationUnit);
    compilationUnitTypeNames.add(superSourceCompilationUnitTypeSourceName);

    Collection<CompiledClass> compiledClasses = superSourceCompilationUnit.getCompiledClasses();
    for (CompiledClass compiledClass : compiledClasses) {
      String sourceName = compiledClass.getSourceName();
      String binaryName = InternalName.toBinaryName(compiledClass.getInternalName());
      nestedSourceNamesByCompilationUnitName.put(
          superSourceCompilationUnitTypeSourceName, sourceName);
      nestedBinaryNamesByCompilationUnitName.put(
          superSourceCompilationUnitTypeSourceName, binaryName);
      compilationUnitNamesByNestedSourceName.put(
          sourceName, superSourceCompilationUnitTypeSourceName);
      compilationUnitNamesByNestedBinaryName.put(
          binaryName, superSourceCompilationUnitTypeSourceName);
    }
  }

  @Override
  public Resource getBuildResourceByPath(String path) {
    return null;
  }

  @Override
  public Set<String> getBuildResourcePaths() {
    return buildResourcePaths;
  }

  @Override
  public InputStream getClassFileStream(String classFilePath) {
    return null;
  }

  @Override
  public CompilationUnit getCompilationUnitByTypeBinaryName(String typeBinaryName) {
    // Convert nested binary name to enclosing type source name.
    String typeSourceName =
        compilationUnitNamesByNestedBinaryName.get(typeBinaryName).iterator().next();
    return compilationUnitsByTypeName.get(typeSourceName);
  }

  @Override
  public CompilationUnit getCompilationUnitByTypeSourceName(String typeSourceName) {
    // Convert nested source name to enclosing type source name.
    typeSourceName = compilationUnitNamesByNestedSourceName.get(typeSourceName).iterator().next();
    return compilationUnitsByTypeName.get(typeSourceName);
  }

  @Override
  public Set<String> getDependencyLibraryNames() {
    return dependencyLibraryNames;
  }

  @Override
  public ArtifactSet getGeneratedArtifacts() {
    return null;
  }

  @Override
  public String getLibraryName() {
    return libraryName;
  }

  @Override
  public Multimap<String, String> getNestedBinaryNamesByCompilationUnitName() {
    return nestedBinaryNamesByCompilationUnitName;
  }

  @Override
  public Multimap<String, String> getNestedSourceNamesByCompilationUnitName() {
    return nestedSourceNamesByCompilationUnitName;
  }

  @Override
  public Multimap<String, String> getNewBindingPropertyValuesByName() {
    return newBindingPropertyValuesByName;
  }

  @Override
  public Multimap<String, String> getNewConfigurationPropertyValuesByName() {
    return newConfigurationPropertyValuesByName;
  }

  @Override
  public ZipEntryBackedObject<PermutationResult> getPermutationResultHandle() {
    return null;
  }

  @Override
  public Resource getPublicResourceByPath(String path) {
    return null;
  }

  @Override
  public Set<String> getPublicResourcePaths() {
    return null;
  }

  @Override
  public Set<String> getRanGeneratorNames() {
    return ranGeneratorNames;
  }

  @Override
  public Set<String> getReboundTypeSourceNames() {
    return reboundTypeNames;
  }

  @Override
  public Set<String> getRegularClassFilePaths() {
    return null;
  }

  @Override
  public Set<String> getRegularCompilationUnitTypeSourceNames() {
    return compilationUnitTypeNames;
  }

  @Override
  public Set<String> getSuperSourceClassFilePaths() {
    return null;
  }

  @Override
  public Set<String> getSuperSourceCompilationUnitTypeSourceNames() {
    return superSourceCompilationUnitTypeNames;
  }

  @Override
  public String toString() {
    return libraryName;
  }
}
Ejemplo n.º 16
0
/** Incrementally builds, links, and rebuilds module trees. */
public class IncrementalBuilder {

  /** Represents a combination of whether a build succeeded and whether output changed. */
  public static enum BuildResultStatus {
    FAILED(false, false),
    SUCCESS_NO_CHANGES(false, true),
    SUCCESS_WITH_CHANGES(true, true);

    private static BuildResultStatus get(boolean success) {
      return success ? SUCCESS_WITH_CHANGES : FAILED;
    }

    private boolean outputChanged;
    private boolean success;

    private BuildResultStatus(boolean outputChanged, boolean success) {
      this.outputChanged = outputChanged;
      this.success = success;
    }

    public boolean isSuccess() {
      return success;
    }

    public boolean outputChanged() {
      return outputChanged;
    }
  }

  @VisibleForTesting
  static final String NO_FILES_HAVE_CHANGED = "No files have changed; all output is still fresh.";

  @VisibleForTesting
  protected static String formatCircularModulePathMessage(List<String> circularModulePath) {
    return "Can't compile because of a module circular reference:\n  "
        + Joiner.on("\n  ").join(circularModulePath);
  }

  private Map<String, BuildTarget> buildTargetsByCanonicalModuleName = Maps.newLinkedHashMap();
  private List<List<String>> circularReferenceModuleNameLoops = Lists.newArrayList();
  private Properties finalProperties;
  private String genDir;
  private Set<String> knownCircularlyReferentModuleNames = Sets.newHashSet();
  private Set<String> moduleReferencePath = Sets.newLinkedHashSet();
  private String outputDir;
  private final ResourceLoader resourceLoader;
  private BuildTarget rootBuildTarget;
  private ModuleDef rootModule;
  private final String rootModuleName;
  private String warDir;
  private BuildTargetOptions buildTargetOptions =
      new BuildTargetOptions() {

        @Override
        public Properties getFinalProperties() {
          return IncrementalBuilder.this.finalProperties;
        }

        @Override
        public String getGenDir() {
          return IncrementalBuilder.this.genDir;
        }

        @Override
        public String getOutputDir() {
          return IncrementalBuilder.this.outputDir;
        }

        @Override
        public ResourceLoader getResourceLoader() {
          return IncrementalBuilder.this.resourceLoader;
        }

        @Override
        public String getWarDir() {
          return IncrementalBuilder.this.warDir;
        }
      };

  public IncrementalBuilder(
      String rootModuleName,
      String warDir,
      String libDir,
      String genDir,
      ResourceLoader resourceLoader) {
    this.rootModuleName = rootModuleName;
    this.warDir = warDir;
    this.outputDir = libDir;
    this.genDir = genDir;
    this.resourceLoader = resourceLoader;
  }

  public BuildResultStatus build(TreeLogger logger) {
    try {
      logger = logger.branch(TreeLogger.INFO, "Performing an incremental build");

      CompilerContext compilerContext =
          new CompilerContext.Builder()
              .compileMonolithic(false)
              .libraryGroup(LibraryGroup.fromLibraries(Lists.<Library>newArrayList(), false))
              .build();
      long beforeLoadRootModuleMs = System.currentTimeMillis();
      rootModule =
          ModuleDefLoader.loadFromResources(
              logger, compilerContext, rootModuleName, resourceLoader, false);
      finalProperties = rootModule.getProperties();
      long loadRootModuleDurationMs = System.currentTimeMillis() - beforeLoadRootModuleMs;
      logger.log(
          TreeLogger.INFO,
          String.format(
              "%.3fs -- Parsing and loading root module definition in %s",
              loadRootModuleDurationMs / 1000d, rootModuleName));

      long beforeCreateTargetGraphMs = System.currentTimeMillis();
      rootBuildTarget = createBuildTarget(logger, rootModuleName);
      rootBuildTarget.setModule(rootModule);
      long createdTargetGraphDurationMs = System.currentTimeMillis() - beforeCreateTargetGraphMs;
      logger.log(
          TreeLogger.INFO,
          String.format(
              "%.3fs -- Creating target graph (%s targets)",
              createdTargetGraphDurationMs / 1000d, buildTargetsByCanonicalModuleName.size()));

      if (!circularReferenceModuleNameLoops.isEmpty()) {
        for (List<String> circularReferenceModuleNameLoop : circularReferenceModuleNameLoops) {
          logger.log(
              TreeLogger.ERROR, formatCircularModulePathMessage(circularReferenceModuleNameLoop));
        }
        throw new UnableToCompleteException();
      }
      logLoadedBuildTargetGraph(logger, buildTargetsByCanonicalModuleName);

      long beforeComputeOutputFreshnessMs = System.currentTimeMillis();
      ModuleDefLoader.clearModuleCache();
      rootBuildTarget.computeOutputFreshness(logger);
      long computeOutputFreshnessDurationMs =
          System.currentTimeMillis() - beforeComputeOutputFreshnessMs;
      logger.log(
          TreeLogger.INFO,
          String.format(
              "%.3fs -- Computing per-target output freshness",
              computeOutputFreshnessDurationMs / 1000d));

      TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling target graph");
      boolean success = rootBuildTarget.link(branch);
      return BuildResultStatus.get(success);
    } catch (UnableToCompleteException e) {
      // The real cause has been logged.
      return BuildResultStatus.FAILED;
    }
  }

  public String getRootModuleName() {
    if (rootModule == null) {
      return "UNKNOWN";
    }
    return rootModule.getName();
  }

  public boolean isRootModuleKnown() {
    return rootModule != null;
  }

  public BuildResultStatus rebuild(TreeLogger logger) {
    logger = logger.branch(TreeLogger.INFO, "Performing an incremental rebuild");

    ResourceOracleImpl.clearCache();
    ZipFileClassPathEntry.clearCache();
    ModuleDefLoader.clearModuleCache();
    ResourceGeneratorUtilImpl.clearGeneratedFilesByName();

    long beforeComputeOutputFreshnessMs = System.currentTimeMillis();
    forgetAllOutputFreshness();
    rootBuildTarget.computeOutputFreshness(logger);
    long computeOutputFreshnessDurationMs =
        System.currentTimeMillis() - beforeComputeOutputFreshnessMs;
    logger.log(
        TreeLogger.INFO,
        String.format(
            "%.3fs -- Computing per-target output freshness",
            computeOutputFreshnessDurationMs / 1000d));

    if (rootBuildTarget.isOutputFreshAndGood()) {
      logger.log(TreeLogger.INFO, NO_FILES_HAVE_CHANGED);
      return BuildResultStatus.SUCCESS_NO_CHANGES;
    }

    TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling target graph");
    boolean success = rootBuildTarget.link(branch);
    return BuildResultStatus.get(success);
  }

  public void setWarDir(String warDir) {
    this.warDir = warDir;
  }

  @VisibleForTesting
  void clean() {
    File[] files = new File(outputDir).listFiles();
    if (files == null) {
      // nothing to delete
      return;
    }
    for (File file : files) {
      file.delete();
    }
  }

  private BuildTarget createBuildTarget(String canonicalModuleName, BuildTarget... buildTargets) {
    if (!buildTargetsByCanonicalModuleName.containsKey(canonicalModuleName)) {
      buildTargetsByCanonicalModuleName.put(
          canonicalModuleName,
          new BuildTarget(canonicalModuleName, buildTargetOptions, buildTargets));
    }
    return buildTargetsByCanonicalModuleName.get(canonicalModuleName);
  }

  private BuildTarget createBuildTarget(TreeLogger logger, String moduleName)
      throws UnableToCompleteException {
    if (isCircularlyReferent(moduleName)) {
      // Allow the target graph creation to continue so that all of the circular reference loops can
      // be gathered.
      return null;
    }
    if (buildTargetsByCanonicalModuleName.containsKey(moduleName)) {
      return buildTargetsByCanonicalModuleName.get(moduleName);
    }

    logger.log(TreeLogger.SPAM, String.format("Adding target %s to build graph.", moduleName));
    moduleReferencePath.add(moduleName);

    List<BuildTarget> dependencyBuildTargets = Lists.newArrayList();
    for (String dependencyModuleName : rootModule.getDirectDependencies(moduleName)) {
      dependencyBuildTargets.add(createBuildTarget(logger, dependencyModuleName));
    }
    moduleReferencePath.remove(moduleName);

    return createBuildTarget(moduleName, dependencyBuildTargets.toArray(new BuildTarget[0]));
  }

  private void forgetAllOutputFreshness() {
    for (BuildTarget buildTarget : buildTargetsByCanonicalModuleName.values()) {
      buildTarget.setOutputFreshness(OutputFreshness.UNKNOWN);
    }
  }

  private boolean isCircularlyReferent(String potentialDuplicateModuleName) {
    if (knownCircularlyReferentModuleNames.contains(potentialDuplicateModuleName)) {
      return true;
    }
    if (!moduleReferencePath.contains(potentialDuplicateModuleName)) {
      return false;
    }

    List<String> circularModuleReferencePath = Lists.newArrayList(moduleReferencePath);

    // Attach the duplicate module name to the end of the loop.
    circularModuleReferencePath.add(potentialDuplicateModuleName);

    List<String> annotatedCircularModuleReferencePath = Lists.newArrayList();
    // The current module path only includes libraries but the connections between libraries might
    // be silently flowing through filesets. Add filesets to the path so that the output is more
    // readable.
    for (int moduleNameIndex = 0;
        moduleNameIndex < circularModuleReferencePath.size() - 1;
        moduleNameIndex++) {
      String thisModuleName = circularModuleReferencePath.get(moduleNameIndex);
      String nextModuleName = circularModuleReferencePath.get(moduleNameIndex + 1);

      annotatedCircularModuleReferencePath.add(
          thisModuleName + (thisModuleName.equals(potentialDuplicateModuleName) ? " <loop>" : ""));

      List<String> fileSetPath = rootModule.getFileSetPathBetween(thisModuleName, nextModuleName);
      if (fileSetPath != null) {
        for (String fileSetModuleName : fileSetPath) {
          annotatedCircularModuleReferencePath.add(fileSetModuleName + " <fileset>");
        }
      }
    }

    // Attach the duplicate module name to the end of the loop.
    annotatedCircularModuleReferencePath.add(potentialDuplicateModuleName + " <loop>");

    knownCircularlyReferentModuleNames.addAll(annotatedCircularModuleReferencePath);
    circularReferenceModuleNameLoops.add(annotatedCircularModuleReferencePath);
    return true;
  }

  private void logLoadedBuildTargetGraph(
      TreeLogger logger, Map<String, BuildTarget> buildTargetsByCanonicalModuleName) {
    logger.log(TreeLogger.SPAM, "Loaded build target graph:");
    for (String canonicalModuleName : buildTargetsByCanonicalModuleName.keySet()) {
      logger.log(TreeLogger.SPAM, "\t" + canonicalModuleName);
      BuildTarget gwtTarget = buildTargetsByCanonicalModuleName.get(canonicalModuleName);
      for (BuildTarget dependencyBuildTarget : gwtTarget.getDependencyBuildTargets()) {
        logger.log(TreeLogger.SPAM, "\t\t" + dependencyBuildTarget.getCanonicalModuleName());
      }
    }
  }
}
Ejemplo n.º 17
0
/** Root for the AST representing an entire Java program. */
public class JProgram extends JNode implements ArrayTypeCreator {

  /**
   * Encapsulates all information necessary to deal with native represented types in an generic
   * fashion used throughout GWT. This can be extended later to deal with say, unboxed Integer if
   * desired.
   */
  public enum DispatchType {
    // These this list can be extended by creating the appropriate fields/methods on Cast,
    // as well as extending the TypeCategory enum and updating EqualityNormalizer.
    // The order in which these native types appear is the inverse as the way they are
    // checked by devirtualized method.
    BOOLEAN(true),
    DOUBLE(true),
    STRING(true),

    // non-native represented type values.
    HAS_JAVA_VIRTUAL_DISPATCH(false),
    JAVA_ARRAY(false),
    JSO(false);

    private final String instanceOfMethod;
    private final String castMapField;
    private final TypeCategory typeCategory;
    private final String dynamicCastMethod;
    private final String className;

    DispatchType(boolean nativeType) {
      if (nativeType) {
        // These field are initialized to methods that are by-convention
        // The conventions are:
        // Cast.isJava[BoxedTypeName] for instanceof checks
        // Cast.[boxedTypeName]CastMap for cast map fields
        // Cast.dynamicCastTo[BoxedTypeName] for cast checks
        // TypedCategory.TYPE_JAVA_LANG_[BoxedTypeName]
        // If Cast or TypeCategory is edited, update this constructor
        this.instanceOfMethod = "Cast.isJava" + camelCase(name());
        this.castMapField = "Cast." + name().toLowerCase() + "CastMap";
        this.dynamicCastMethod = "Cast.dynamicCastTo" + camelCase(name());
        this.typeCategory = TypeCategory.valueOf("TYPE_JAVA_LANG_" + name());
        this.className = "java.lang." + camelCase(name());
      } else {
        this.instanceOfMethod = null;
        this.castMapField = null;
        this.typeCategory = null;
        this.dynamicCastMethod = null;
        this.className = null;
      }
    }

    private static String camelCase(String name) {
      return Character.toUpperCase(name.charAt(0)) + name.toLowerCase().substring(1);
    }

    public String getInstanceOfMethod() {
      return instanceOfMethod;
    }

    public String getCastMapField() {
      return castMapField;
    }

    public TypeCategory getTypeCategory() {
      return typeCategory;
    }

    public String getDynamicCastMethod() {
      return dynamicCastMethod;
    }

    public String getclassName() {
      return className;
    }
  }

  private static final class TreeStatistics extends JVisitor {
    private int nodeCount = 0;

    public int getNodeCount() {
      return nodeCount;
    }

    @Override
    public boolean visit(JNode x, Context ctx) {
      nodeCount++;
      return true;
    }
  }

  public static final Set<String> CODEGEN_TYPES_SET =
      Sets.newLinkedHashSet(
          Arrays.asList(
              "com.google.gwt.lang.Array",
              "com.google.gwt.lang.Cast",
              "com.google.gwt.lang.Exceptions",
              "com.google.gwt.lang.LongLib",
              "com.google.gwt.lang.Stats",
              "com.google.gwt.lang.Util",
              "java.lang.Object"));

  /*
   * Types which are not referenced by any Java code, but are required to exist
   * after Java optimizations have run in order to be used by backend
   * code-generation. These classes and their members, are considered live
   * by ControlFlowAnalysis, at all times. Immortal types always live in the
   * initial fragment and their definitions are hoisted to appear before all
   * other types. Only static methods and fields are allowed, and no clinits
   * are run. Field initializers must be primitives, literals, or one of
   * JSO.createObject() or JSO.createArray().
   *
   * Classes are inserted into the JsAST in the order they appear in the Set.
   */
  public static final Set<String> IMMORTAL_CODEGEN_TYPES_SET =
      Sets.newLinkedHashSet(
          Arrays.asList(
              "com.google.gwt.lang.CollapsedPropertyHolder",
              "com.google.gwt.lang.JavaClassHierarchySetupUtil",
              "com.google.gwt.lang.ModuleUtils"));

  public static final String JAVASCRIPTOBJECT = "com.google.gwt.core.client.JavaScriptObject";

  public static final String CLASS_LITERAL_HOLDER = "com.google.gwt.lang.ClassLiteralHolder";

  /** Types whose entire implementation is synthesized at compile time. */
  public static final Set<String> SYNTHETIC_TYPE_NAMES = Sets.newHashSet(CLASS_LITERAL_HOLDER);

  private static final Comparator<JArrayType> ARRAYTYPE_COMPARATOR =
      new Comparator<JArrayType>() {
        @Override
        public int compare(JArrayType o1, JArrayType o2) {
          int comp = o1.getDims() - o2.getDims();
          if (comp != 0) {
            return comp;
          }
          return o1.getName().compareTo(o2.getName());
        }
      };

  private static final Map<String, JPrimitiveType> primitiveTypes = Maps.newHashMap();

  @Deprecated
  private static final Map<String, JPrimitiveType> primitiveTypesDeprecated = Maps.newHashMap();

  static {
    if (System.getProperty("gwt.coverage") != null) {
      IMMORTAL_CODEGEN_TYPES_SET.add("com.google.gwt.lang.CoverageUtil");
    }
    CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET);

    primitiveTypes.put(JPrimitiveType.BOOLEAN.getName(), JPrimitiveType.BOOLEAN);
    primitiveTypes.put(JPrimitiveType.BYTE.getName(), JPrimitiveType.BYTE);
    primitiveTypes.put(JPrimitiveType.CHAR.getName(), JPrimitiveType.CHAR);
    primitiveTypes.put(JPrimitiveType.DOUBLE.getName(), JPrimitiveType.DOUBLE);
    primitiveTypes.put(JPrimitiveType.FLOAT.getName(), JPrimitiveType.FLOAT);
    primitiveTypes.put(JPrimitiveType.INT.getName(), JPrimitiveType.INT);
    primitiveTypes.put(JPrimitiveType.LONG.getName(), JPrimitiveType.LONG);
    primitiveTypes.put(JPrimitiveType.SHORT.getName(), JPrimitiveType.SHORT);
    primitiveTypes.put(JPrimitiveType.VOID.getName(), JPrimitiveType.VOID);

    primitiveTypesDeprecated.put(
        JPrimitiveType.BOOLEAN.getJsniSignatureName(), JPrimitiveType.BOOLEAN);
    primitiveTypesDeprecated.put(JPrimitiveType.BYTE.getJsniSignatureName(), JPrimitiveType.BYTE);
    primitiveTypesDeprecated.put(JPrimitiveType.CHAR.getJsniSignatureName(), JPrimitiveType.CHAR);
    primitiveTypesDeprecated.put(
        JPrimitiveType.DOUBLE.getJsniSignatureName(), JPrimitiveType.DOUBLE);
    primitiveTypesDeprecated.put(JPrimitiveType.FLOAT.getJsniSignatureName(), JPrimitiveType.FLOAT);
    primitiveTypesDeprecated.put(JPrimitiveType.INT.getJsniSignatureName(), JPrimitiveType.INT);
    primitiveTypesDeprecated.put(JPrimitiveType.LONG.getJsniSignatureName(), JPrimitiveType.LONG);
    primitiveTypesDeprecated.put(JPrimitiveType.SHORT.getJsniSignatureName(), JPrimitiveType.SHORT);
    primitiveTypesDeprecated.put(JPrimitiveType.VOID.getJsniSignatureName(), JPrimitiveType.VOID);
  }

  /** Helper to create an assignment, used to initialize fields, etc. */
  public static JExpressionStatement createAssignmentStmt(
      SourceInfo info, JExpression lhs, JExpression rhs) {
    return createAssignment(info, lhs, rhs).makeStatement();
  }

  public static JBinaryOperation createAssignment(
      SourceInfo info, JExpression lhs, JExpression rhs) {
    return new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
  }

  public static JLocal createLocal(
      SourceInfo info, String name, JType type, boolean isFinal, JMethodBody enclosingMethodBody) {
    assert (name != null);
    assert (type != null);
    assert (enclosingMethodBody != null);
    JLocal x = new JLocal(info, name, type, isFinal, enclosingMethodBody);
    enclosingMethodBody.addLocal(x);
    return x;
  }

  public static JParameter createParameter(
      SourceInfo info,
      String name,
      JType type,
      boolean isFinal,
      boolean isThis,
      JMethod enclosingMethod) {
    assert (name != null);
    assert (type != null);
    assert (enclosingMethod != null);

    JParameter x = new JParameter(info, name, type, isFinal, isThis, enclosingMethod);
    enclosingMethod.addParam(x);
    return x;
  }

  public static List<JDeclaredType> deserializeTypes(ObjectInputStream stream)
      throws IOException, ClassNotFoundException {
    @SuppressWarnings("unchecked")
    List<JDeclaredType> types = (List<JDeclaredType>) stream.readObject();
    for (JDeclaredType type : types) {
      type.readMembers(stream);
    }
    for (JDeclaredType type : types) {
      type.readMethodBodies(stream);
    }
    return types;
  }

  public static String getFullName(JMethod method) {
    return method.getEnclosingType().getName() + "." + method.getJsniSignature(false, true);
  }

  public static boolean isClinit(JMethod method) {
    JDeclaredType enclosingType = method.getEnclosingType();

    boolean isClinit = enclosingType != null && method == enclosingType.getClinitMethod();
    assert !isClinit || method.getName().equals(GwtAstBuilder.CLINIT_NAME);
    return isClinit;
  }

  public static boolean isInit(JMethod method) {
    JDeclaredType enclosingType = method.getEnclosingType();

    if (method.isStatic()) {
      // Hack, check the name.
      return method.getName().equals(GwtAstBuilder.STATIC_INIT_NAME);
    }

    boolean isInit = enclosingType != null && method == enclosingType.getInitMethod();
    assert !isInit || method.getName().equals(GwtAstBuilder.INIT_NAME);
    return isInit;
  }

  public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream)
      throws IOException {
    stream.writeObject(types);
    for (JDeclaredType type : types) {
      type.writeMembers(stream);
    }
    for (JDeclaredType type : types) {
      type.writeMethodBodies(stream);
    }
  }

  public final List<JClassType> codeGenTypes = Lists.newArrayList();

  public final List<JClassType> immortalCodeGenTypes = Lists.newArrayList();

  public final JTypeOracle typeOracle;

  /** Special serialization treatment. */
  // TODO(stalcup): make this a set, or take special care to make updates unique when lazily loading
  // in types. At the moment duplicates are accumulating.
  private transient List<JDeclaredType> allTypes = Lists.newArrayList();

  private final Map<JType, JArrayType> arrayTypes = Maps.newHashMap();

  private Map<JReferenceType, JCastMap> castMaps;

  private BiMap<JType, JField> classLiteralFieldsByType;

  private final List<JMethod> entryMethods = Lists.newArrayList();

  private final Map<String, JField> indexedFields = Maps.newHashMap();

  private final Map<String, JMethod> indexedMethods = Maps.newHashMap();

  /** An index of types, from type name to type instance. */
  private final Map<String, JDeclaredType> indexedTypes = Maps.newHashMap();

  /**
   * The set of names of types (beyond the basic INDEX_TYPES_SET) whose instance should be indexed
   * when seen.
   */
  private final Set<String> typeNamesToIndex = buildInitialTypeNamesToIndex();

  private final Map<JMethod, JMethod> instanceToStaticMap = Maps.newIdentityHashMap();

  // wrap up .add here, and filter out forced source
  private Set<String> referenceOnlyTypeNames = Sets.newHashSet();

  /** Filled in by ReplaceRunAsync, once the numbers are known. */
  private List<JRunAsync> runAsyncs = Lists.newArrayList();

  private LinkedHashSet<JRunAsync> initialAsyncSequence = Sets.newLinkedHashSet();

  private List<Integer> initialFragmentIdSequence = Lists.newArrayList();

  private final Map<JMethod, JMethod> staticToInstanceMap = Maps.newIdentityHashMap();

  private JClassType typeClass;

  private JClassType typeJavaLangObject;

  private final Map<String, JDeclaredType> typeNameMap = Maps.newHashMap();

  private Map<JField, JType> typesByClassLiteralField;

  private JClassType typeSpecialClassLiteralHolder;

  private JClassType typeSpecialJavaScriptObject;

  private JClassType typeString;

  private FragmentPartitioningResult fragmentPartitioningResult;

  private Map<JClassType, DispatchType> dispatchTypeByNativeType;

  /** Add a pinned method. */
  public void addPinnedMethod(JMethod method) {
    method.setInliningMode(InliningMode.DO_NOT_INLINE);
    method.disallowDevirtualization();
  }

  public JProgram(MinimalRebuildCache minimalRebuildCache) {
    super(SourceOrigin.UNKNOWN);
    typeOracle = new JTypeOracle(this, minimalRebuildCache);
  }

  public void addEntryMethod(JMethod entryPoint) {
    assert !entryMethods.contains(entryPoint);
    entryMethods.add(entryPoint);
  }

  /**
   * Adds the given type name to the set of type names (beyond the basic INDEX_TYPES_SET) whose
   * instance should be indexed when seen.
   */
  public void addIndexedTypeName(String typeName) {
    typeNamesToIndex.add(typeName);
  }

  public void addReferenceOnlyType(JDeclaredType type) {
    referenceOnlyTypeNames.add(type.getName());
  }

  public void addType(JDeclaredType type) {
    allTypes.add(type);
    String name = type.getName();
    putIntoTypeMap(name, type);

    if (CODEGEN_TYPES_SET.contains(name)) {
      codeGenTypes.add((JClassType) type);
    }

    if (IMMORTAL_CODEGEN_TYPES_SET.contains(name)) {
      immortalCodeGenTypes.add((JClassType) type);
    }

    if (!typeNamesToIndex.contains(name)) {
      return;
    }

    indexedTypes.put(type.getShortName(), type);
    for (JMethod method : type.getMethods()) {
      if (!method.isPrivate()) {
        indexedMethods.put(type.getShortName() + '.' + method.getName(), method);
      }
    }
    for (JField field : type.getFields()) {
      indexedFields.put(type.getShortName() + '.' + field.getName(), field);
    }
    switch (name) {
      case "java.lang.Object":
        typeJavaLangObject = (JClassType) type;
        break;
      case "java.lang.String":
        typeString = (JClassType) type;
        break;
      case "java.lang.Class":
        typeClass = (JClassType) type;
        break;
      case JAVASCRIPTOBJECT:
        typeSpecialJavaScriptObject = (JClassType) type;
        break;
      case CLASS_LITERAL_HOLDER:
        typeSpecialClassLiteralHolder = (JClassType) type;
        break;
    }
  }

  public static boolean isRepresentedAsNative(String className) {
    return FluentIterable.from(Arrays.asList(DispatchType.values()))
        .transform(
            new Function<DispatchType, String>() {
              @Override
              public String apply(DispatchType dispatchType) {
                return dispatchType.getclassName();
              }
            })
        .filter(Predicates.notNull())
        .toSet()
        .contains(className);
  }

  public boolean isRepresentedAsNativeJsPrimitive(JType type) {
    return getRepresentedAsNativeTypes().contains(type);
  }

  public Set<JClassType> getRepresentedAsNativeTypes() {
    return getRepresentedAsNativeTypesDispatchMap().keySet();
  }

  public Map<JClassType, DispatchType> getRepresentedAsNativeTypesDispatchMap() {
    if (dispatchTypeByNativeType == null) {
      ImmutableMap.Builder<JClassType, DispatchType> builder =
          new ImmutableMap.Builder<JClassType, DispatchType>();
      for (DispatchType dispatchType : DispatchType.values()) {
        if (dispatchType.getclassName() == null) {
          continue;
        }
        JClassType classType = (JClassType) getFromTypeMap(dispatchType.getclassName());
        assert classType != null : "Class " + dispatchType.getclassName() + " has not been loaded";
        builder.put(classType, dispatchType);
      }
      dispatchTypeByNativeType = builder.build();
    }
    return dispatchTypeByNativeType;
  }

  public EnumSet<DispatchType> getDispatchType(JReferenceType type) {
    if (!typeOracle.isInstantiatedType(type)) {
      return EnumSet.noneOf(DispatchType.class);
    }

    // Object methods can be dispatched to all four possible classes.
    if (type == getTypeJavaLangObject()) {
      return EnumSet.allOf(DispatchType.class);
    }

    EnumSet<DispatchType> dispatchSet = EnumSet.noneOf(DispatchType.class);
    DispatchType dispatchType = getRepresentedAsNativeTypesDispatchMap().get(type);
    if (dispatchType != null) {
      dispatchSet = EnumSet.of(dispatchType);
    } else if (typeOracle.isDualJsoInterface(type)) {
      // If it is an interface implemented both by JSOs and regular Java Objects;
      dispatchSet = EnumSet.of(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH, DispatchType.JSO);
    } else if (typeOracle.isSingleJsoImpl(type) || type.isJsoType()) {
      // If it is either an interface implemented by JSOs or JavaScriptObject or one of its
      // subclasses.
      dispatchSet = EnumSet.of(DispatchType.JSO);
    }

    for (JDeclaredType potentialNativeDispatchType : getRepresentedAsNativeTypes()) {
      if (potentialNativeDispatchType == type) {
        continue;
      }

      if (typeOracle.isInstantiatedType(potentialNativeDispatchType)
          && typeOracle.isSuperClassOrInterface(potentialNativeDispatchType, type)) {
        dispatchSet.add(getRepresentedAsNativeTypesDispatchMap().get(potentialNativeDispatchType));
        dispatchSet.add(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH);
      }
    }
    return dispatchSet;
  }

  /**
   * Return the greatest lower bound of two types. That is, return the largest type that is a
   * subtype of both inputs. If none exists return {@code thisType}.
   */
  public JReferenceType strengthenType(JReferenceType thisType, JReferenceType thatType) {
    if (thisType == thatType) {
      return thisType;
    }

    if (thisType.isNullType() || thatType.isNullType()) {
      return JReferenceType.NULL_TYPE;
    }

    if (thisType.canBeNull() != thatType.canBeNull()) {
      // If either is non-nullable, the result should be non-nullable.
      return strengthenType(thisType.strengthenToNonNull(), thatType.strengthenToNonNull());
    }

    if (typeOracle.castSucceedsTrivially(thisType, thatType)) {
      return thisType;
    }

    if (typeOracle.castSucceedsTrivially(thatType, thisType)) {
      return thatType;
    }

    // This types are incompatible; ideally this code should not be reached, but there are two
    // situations where this happens:
    //   1 - unrelated interfaces;
    //   2 - unsafe code.
    // The original type is preserved in this case.
    return thisType;
  }

  /**
   * Return a minimal upper bound of a set of types. That is, a type that is a supertype of all the
   * input types and is as close as possible to the input types.
   *
   * <p>NOTE: Ideally we would like to return the least upper bound but it does not exit as the Java
   * type hierarchy is not really a lattice.
   *
   * <p>Hence, this function depends on the collection order. E.g.
   *
   * <p>{@code I O |\ / \ | A B \ / \ / C }
   *
   * <p>where I is an interface an {O,A,B,C} are classes.
   *
   * <p>generalizeTypes({A,C}) could either be I or O.
   *
   * <p>In particular generalizeTypes({I,A,C}) = I and generalizeTypes({A,C,I}) = O.
   */
  public JReferenceType generalizeTypes(Iterable<JReferenceType> types) {
    Iterator<JReferenceType> it = types.iterator();
    if (!it.hasNext()) {
      return JReferenceType.NULL_TYPE;
    }
    JReferenceType curType = it.next();
    while (it.hasNext()) {
      curType = generalizeTypes(curType, it.next());
      if (curType == typeJavaLangObject) {
        break;
      }
    }
    return curType;
  }

  /**
   * Return the least upper bound of two types. That is, the "smallest" type that is a supertype of
   * both types. In this lattice there the smallest element might no exist, there might be multiple
   * minimal elements neither of which is smaller than the others. E.g.
   *
   * <p>{@code I J | \ /| | \ / | | x | | / \ | | / \ | A B }
   *
   * <p>where I and J are interfaces, A and B are classes and both A and B implement I and J. In
   * this case both I and J are generalizing the types A and B.
   */
  private JReferenceType generalizeTypes(JReferenceType thisType, JReferenceType thatType) {

    if (!thisType.canBeNull() && !thatType.canBeNull()) {
      // Nullability is an orthogonal property, so remove non_nullability and perform the
      // generalization on the nullable types, and if both were NOT nullable then strengthen the
      // result to NOT nullable.
      //
      // not_nullable(A) v not_nullable(B) = not_nullable(A v B)
      JReferenceType nulllableGeneralizer =
          generalizeTypes(thisType.weakenToNullable(), thatType.weakenToNullable());
      return nulllableGeneralizer.strengthenToNonNull();
    }
    thisType = thisType.weakenToNullable();
    thatType = thatType.weakenToNullable();

    // From here on nullability does not need to be considered.

    // Generalization for exact types is as follows.
    // exact(A) v null = exact(A)
    // A v null = A
    if (thatType.isNullType()) {
      return thisType;
    }

    // null v exact(A) = exact(A)
    // null v A = A
    if (thisType.isNullType()) {
      return thatType;
    }

    // exact(A) v exact(A)  = exact(A)
    // A v A  = A
    if (thisType == thatType) {
      return thisType;
    }

    // exact(A) v exact(B) = A v B
    // A v exact(B) = A v B
    // exact(A) v B = A v B
    // A v B = A v B
    return generalizeUnderlyingTypes(thisType.getUnderlyingType(), thatType.getUnderlyingType());
  }

  private JReferenceType generalizeUnderlyingTypes(
      JReferenceType thisType, JReferenceType thatType) {

    // We should not have any analysis properties from this point forward.
    assert thisType == thisType.getUnderlyingType() && thatType == thatType.getUnderlyingType();

    if (thisType == thatType) {
      return thisType;
    }

    if (thisType instanceof JInterfaceType && thatType instanceof JInterfaceType) {
      return generalizeInterfaces((JInterfaceType) thisType, (JInterfaceType) thatType);
    }

    if (thisType instanceof JArrayType && thatType instanceof JArrayType) {
      return generalizeArrayTypes((JArrayType) thisType, (JArrayType) thatType);
    }

    if (thisType instanceof JClassType && thatType instanceof JClassType) {
      return generalizeClasses((JClassType) thisType, (JClassType) thatType);
    }

    JInterfaceType interfaceType =
        thisType instanceof JInterfaceType
            ? (JInterfaceType) thisType
            : (thatType instanceof JInterfaceType ? (JInterfaceType) thatType : null);

    JReferenceType nonInterfaceType = interfaceType == thisType ? thatType : thisType;

    // See if the class or the array is castable to the interface type.
    if (interfaceType != null
        && typeOracle.castSucceedsTrivially(nonInterfaceType, interfaceType)) {
      return interfaceType;
    }

    // unrelated: the best commonality is Object
    return typeJavaLangObject;
  }

  private JReferenceType generalizeArrayTypes(JArrayType thisArrayType, JArrayType thatArrayType) {
    assert thisArrayType != thatArrayType;

    int thisDims = thisArrayType.getDims();
    int thatDims = thatArrayType.getDims();

    int minDims = Math.min(thisDims, thatDims);
    /*
     * At a bare minimum, any two arrays generalize to an Object array with
     * one less dim than the lesser of the two; that is, int[][][][] and
     * String[][][] generalize to Object[][]. If minDims is 1, then they
     * just generalize to Object.
     */
    JReferenceType minimalGeneralType =
        (minDims == 1) ? typeJavaLangObject : getOrCreateArrayType(typeJavaLangObject, minDims - 1);

    if (thisDims == thatDims) {

      // Try to generalize by leaf types
      JType thisLeafType = thisArrayType.getLeafType();
      JType thatLeafType = thatArrayType.getLeafType();

      if (!(thisLeafType instanceof JReferenceType) || !(thatLeafType instanceof JReferenceType)) {
        return minimalGeneralType;
      }

      /*
       * Both are reference types; the result is the generalization of the leaf types combined with
       * the number of dims; that is, Foo[] and Bar[] generalize to X[] where X is the
       * generalization of Foo and Bar.
       *
       * Never generalize arrays to arrays of {@link JAnalysisDecoratedType}. One of the reasons is
       * that array initialization is not accounted for in {@link TypeTightener}.
       */
      JReferenceType leafGeneralization =
          generalizeTypes((JReferenceType) thisLeafType, (JReferenceType) thatLeafType)
              .getUnderlyingType();
      return getOrCreateArrayType(leafGeneralization, thisDims);
    }

    // Different number of dims
    if (typeOracle.castSucceedsTrivially(thatArrayType, thisArrayType)) {
      return thisArrayType;
    }

    if (typeOracle.castSucceedsTrivially(thisArrayType, thatArrayType)) {
      return thatArrayType;
    }

    // Totally unrelated
    return minimalGeneralType;
  }

  private JReferenceType generalizeInterfaces(
      JInterfaceType thisInterface, JInterfaceType thatInterface) {
    if (typeOracle.castSucceedsTrivially(thisInterface, thatInterface)) {
      return thatInterface;
    }

    if (typeOracle.castSucceedsTrivially(thatInterface, thisInterface)) {
      return thisInterface;
    }

    // unrelated
    return typeJavaLangObject;
  }

  private JReferenceType generalizeClasses(JClassType thisClass, JClassType thatClass) {
    /*
     * see how far each type is from object; walk the one who's farther up
     * until they're even; then walk them up together until they meet (worst
     * case at Object)
     */
    int distance1 = countSuperTypes(thisClass);
    int distance2 = countSuperTypes(thatClass);
    for (; distance1 > distance2; --distance1) {
      thisClass = thisClass.getSuperClass();
    }

    for (; distance1 < distance2; --distance2) {
      thatClass = thatClass.getSuperClass();
    }

    while (thisClass != thatClass) {
      thisClass = thisClass.getSuperClass();
      thatClass = thatClass.getSuperClass();
    }

    return thisClass;
  }

  /**
   * Returns a sorted list of array types, so the returned set can be iterated over without
   * introducing nondeterminism.
   */
  public List<JArrayType> getAllArrayTypes() {
    List<JArrayType> result = Lists.newArrayList(arrayTypes.values());
    Collections.sort(result, ARRAYTYPE_COMPARATOR);
    return result;
  }

  /**
   * Returns an expression that evaluates to an array class literal at runtime.
   *
   * <p>Note: This version can only be called after {@link
   * com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields} has been run.
   */
  public JExpression createArrayClassLiteralExpression(
      SourceInfo sourceInfo, JClassLiteral leafTypeClassLiteral, int dimensions) {
    JField leafTypeClassLiteralField = leafTypeClassLiteral.getField();
    assert leafTypeClassLiteralField != null
        : "Array leaf type must have a class literal field; "
            + "either ImplementClassLiteralsAsField has not run yet or or there is an error computing"
            + "live class literals.";

    return new JMethodCall(
        sourceInfo,
        null,
        getIndexedMethod("Array.getClassLiteralForArray"),
        new JFieldRef(
            sourceInfo,
            null,
            leafTypeClassLiteralField,
            leafTypeClassLiteralField.getEnclosingType()),
        getLiteralInt(dimensions));
  }

  public Map<JReferenceType, JCastMap> getCastMap() {
    return Collections.unmodifiableMap(castMaps);
  }

  public JCastMap getCastMap(JReferenceType referenceType) {
    // ensure jsonCastableTypeMaps has been initialized
    // it might not have been if the ImplementCastsAndTypeChecks has not been run
    if (castMaps == null) {
      initTypeInfo(null);
    }
    return castMaps.get(referenceType);
  }

  public JField getClassLiteralField(JType type) {
    return classLiteralFieldsByType.get(type.isJsoType() ? getJavaScriptObject() : type);
  }

  public List<JDeclaredType> getDeclaredTypes() {
    return allTypes;
  }

  public List<JMethod> getEntryMethods() {
    return entryMethods;
  }

  public int getFragmentCount() {
    // Initial fragment is the +1.
    return runAsyncs.size() + 1;
  }

  public FragmentPartitioningResult getFragmentPartitioningResult() {
    return fragmentPartitioningResult;
  }

  // TODO(stalcup): this is a blatant bug. there's no unambiguous way to convert from binary name to
  // source name. JProgram needs to index types both ways.
  public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) {
    String srcTypeName = qualifiedBinaryOrSourceName.replace('$', '.');

    return typeNameMap.get(srcTypeName);
  }

  public JField getIndexedField(String string) {
    JField field = indexedFields.get(string);
    if (field == null) {
      throw new InternalCompilerException("Unable to locate index field: " + string);
    }
    return field;
  }

  public Collection<JField> getIndexedFields() {
    return Collections.unmodifiableCollection(indexedFields.values());
  }

  public JMethod getIndexedMethod(String string) {
    JMethod method = indexedMethods.get(string);
    if (method == null) {
      throw new InternalCompilerException("Unable to locate index method: " + string);
    }
    return method;
  }

  public Collection<JMethod> getIndexedMethods() {
    return Collections.unmodifiableCollection(indexedMethods.values());
  }

  public JMethod getIndexedMethodOrNull(String string) {
    return indexedMethods.get(string);
  }

  public JDeclaredType getIndexedType(String string) {
    JDeclaredType type = indexedTypes.get(string);
    if (type == null) {
      throw new InternalCompilerException("Unable to locate index type: " + string);
    }
    return type;
  }

  public Collection<JDeclaredType> getIndexedTypes() {
    return Collections.unmodifiableCollection(indexedTypes.values());
  }

  public LinkedHashSet<JRunAsync> getInitialAsyncSequence() {
    return initialAsyncSequence;
  }

  public List<Integer> getInitialFragmentIdSequence() {
    return initialFragmentIdSequence;
  }

  public JClassType getJavaScriptObject() {
    return typeSpecialJavaScriptObject;
  }

  public JLiteral getLiteral(Object value) {
    return getLiteral(SourceOrigin.UNKNOWN, value);
  }

  public JLiteral getLiteral(SourceInfo info, Object value) {
    if (value == null) {
      return getLiteralNull();
    }
    if (value instanceof String) {
      return getStringLiteral(info, (String) value);
    }
    if (value instanceof Integer) {
      return getLiteralInt((Integer) value);
    }
    if (value instanceof Long) {
      return getLiteralLong((Long) value);
    }
    if (value instanceof Character) {
      return getLiteralChar((Character) value);
    }
    if (value instanceof Boolean) {
      return getLiteralBoolean((Boolean) value);
    }
    if (value instanceof Double) {
      return getLiteralDouble((Double) value);
    }
    if (value instanceof Float) {
      return getLiteralFloat((Float) value);
    }
    throw new IllegalArgumentException("Unknown literal type for " + value);
  }

  public JBooleanLiteral getLiteralBoolean(boolean value) {
    return JBooleanLiteral.get(value);
  }

  public JCharLiteral getLiteralChar(char value) {
    return JCharLiteral.get(value);
  }

  public JDoubleLiteral getLiteralDouble(double d) {
    return JDoubleLiteral.get(d);
  }

  public JFloatLiteral getLiteralFloat(double f) {
    return JFloatLiteral.get(f);
  }

  public JIntLiteral getLiteralInt(int value) {
    return JIntLiteral.get(value);
  }

  public JLongLiteral getLiteralLong(long value) {
    return JLongLiteral.get(value);
  }

  public JNullLiteral getLiteralNull() {
    return JNullLiteral.INSTANCE;
  }

  public JStringLiteral getStringLiteral(SourceInfo sourceInfo, String s) {
    sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(Literal.STRING));
    return new JStringLiteral(sourceInfo, StringInterner.get().intern(s), typeString);
  }

  public List<JDeclaredType> getModuleDeclaredTypes() {
    List<JDeclaredType> moduleDeclaredTypes = Lists.newArrayList();
    for (JDeclaredType type : allTypes) {
      if (isReferenceOnly(type)) {
        continue;
      }
      moduleDeclaredTypes.add(type);
    }
    return moduleDeclaredTypes;
  }

  public int getNodeCount() {
    Event countEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "countNodes");
    TreeStatistics treeStats = new TreeStatistics();
    treeStats.accept(this);
    int numNodes = treeStats.getNodeCount();
    countEvent.end();
    return numNodes;
  }

  public JField getNullField() {
    return JField.NULL_FIELD;
  }

  public JMethod getNullMethod() {
    return JMethod.NULL_METHOD;
  }

  public List<JRunAsync> getRunAsyncs() {
    return runAsyncs;
  }

  public int getCommonAncestorFragmentId(int thisFragmentId, int thatFragmentId) {
    return fragmentPartitioningResult.getCommonAncestorFragmentId(thisFragmentId, thatFragmentId);
  }

  public Collection<JType> getSubclasses(JType type) {
    return Collections2.transform(
        typeOracle.getSubTypeNames(type.getName()),
        new Function<String, JType>() {
          @Override
          public JType apply(String typeName) {
            return getFromTypeMap(typeName);
          }
        });
  }

  public JMethod getStaticImpl(JMethod method) {
    JMethod staticImpl = instanceToStaticMap.get(method);
    assert staticImpl == null || staticImpl.getEnclosingType().getMethods().contains(staticImpl);
    return staticImpl;
  }

  public JArrayType getTypeArray(JType elementType) {
    JArrayType arrayType = arrayTypes.get(elementType);
    if (arrayType == null) {
      arrayType = new JArrayType(elementType);
      arrayTypes.put(elementType, arrayType);
    }
    return arrayType;
  }

  // TODO(dankurka): Why does JProgram synthezise array types on the fly
  // Look into refactoring JProgram to get rid of this responsibility
  @Override
  public JArrayType getOrCreateArrayType(JType leafType, int dimensions) {
    assert dimensions > 0;
    assert (!(leafType instanceof JArrayType));
    JArrayType result = getTypeArray(leafType);
    while (dimensions > 1) {
      result = getTypeArray(result);
      --dimensions;
    }
    return result;
  }

  public JType getTypeByClassLiteralField(JField field) {
    return typesByClassLiteralField.get(field);
  }

  public JClassType getTypeClassLiteralHolder() {
    return typeSpecialClassLiteralHolder;
  }

  /** Returns the JType corresponding to a JSNI type reference. */
  public JType getTypeFromJsniRef(String className) {
    int dim = 0;
    while (className.endsWith("[]")) {
      dim++;
      className = className.substring(0, className.length() - 2);
    }

    JType type = primitiveTypes.get(className);
    if (type == null) {
      type = getFromTypeMap(className);
    }
    // TODO(deprecation): remove support for this.
    if (type == null) {
      type = primitiveTypesDeprecated.get(className);
    }
    if (type == null || dim == 0) {
      return type;
    } else {
      return getOrCreateArrayType(type, dim);
    }
  }

  public JClassType getTypeJavaLangClass() {
    return typeClass;
  }

  public JClassType getTypeJavaLangObject() {
    return typeJavaLangObject;
  }

  public JClassType getTypeJavaLangString() {
    return typeString;
  }

  public Set<String> getTypeNamesToIndex() {
    return typeNamesToIndex;
  }

  public JPrimitiveType getTypePrimitiveBoolean() {
    return JPrimitiveType.BOOLEAN;
  }

  public JPrimitiveType getTypePrimitiveByte() {
    return JPrimitiveType.BYTE;
  }

  public JPrimitiveType getTypePrimitiveChar() {
    return JPrimitiveType.CHAR;
  }

  public JPrimitiveType getTypePrimitiveDouble() {
    return JPrimitiveType.DOUBLE;
  }

  public JPrimitiveType getTypePrimitiveFloat() {
    return JPrimitiveType.FLOAT;
  }

  public JPrimitiveType getTypePrimitiveInt() {
    return JPrimitiveType.INT;
  }

  public JPrimitiveType getTypePrimitiveLong() {
    return JPrimitiveType.LONG;
  }

  public JPrimitiveType getTypePrimitiveShort() {
    return JPrimitiveType.SHORT;
  }

  public JPrimitiveType getTypeVoid() {
    return JPrimitiveType.VOID;
  }

  public void initTypeInfo(Map<JReferenceType, JCastMap> castMapForType) {
    castMaps = castMapForType;
    if (castMaps == null) {
      castMaps = Maps.newIdentityHashMap();
    }
  }

  public boolean isJavaLangString(JType type) {
    assert type != null;
    return type.getUnderlyingType() == typeString;
  }

  public boolean isJavaLangObject(JType type) {
    assert type != null;
    return type.getUnderlyingType() == typeJavaLangObject;
  }

  public boolean isReferenceOnly(JDeclaredType type) {
    if (type != null) {
      return referenceOnlyTypeNames.contains(type.getName());
    }
    return false;
  }

  public boolean isStaticImpl(JMethod method) {
    return staticToInstanceMap.containsKey(method);
  }

  /**
   * If the type is a JSO or an array of JSOs it returns cggcc.JavaScriptObject or an array of
   * cggcc.JavaScriptObject respectively; otherwise returns {@code type}.
   */
  public JType normalizeJsoType(JType type) {
    type = type.getUnderlyingType();

    if (type instanceof JArrayType) {
      return getOrCreateArrayType(
          normalizeJsoType(((JArrayType) type).getLeafType()), ((JArrayType) type).getDims());
    }

    if (type.isJsoType()) {
      return getJavaScriptObject();
    }
    return type;
  }

  public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) {
    // Make it into a source type name.
    String srcTypeName = qualifiedBinaryName.replace('$', '.');
    typeNameMap.put(srcTypeName, type);
  }

  public void putStaticImpl(JMethod method, JMethod staticImpl) {
    instanceToStaticMap.put(method, staticImpl);
    staticToInstanceMap.put(staticImpl, method);
  }

  public void recordClassLiteralFields(Map<JType, JField> classLiteralFields) {
    this.classLiteralFieldsByType = HashBiMap.create(classLiteralFields);
    this.typesByClassLiteralField = classLiteralFieldsByType.inverse();
  }

  public void removeStaticImplMapping(JMethod staticImpl) {
    JMethod instanceMethod = staticToInstanceMap.remove(staticImpl);
    if (instanceMethod != null) {
      instanceToStaticMap.remove(instanceMethod);
    }
  }

  public void removeReferenceOnlyType(JDeclaredType type) {
    referenceOnlyTypeNames.remove(type.getName());
  }

  public void setFragmentPartitioningResult(FragmentPartitioningResult result) {
    fragmentPartitioningResult = result;
  }

  public void setInitialFragmentIdSequence(List<Integer> initialFragmentIdSequence) {
    this.initialFragmentIdSequence = initialFragmentIdSequence;
  }

  public void setRunAsyncs(List<JRunAsync> runAsyncs) {
    this.runAsyncs = ImmutableList.copyOf(runAsyncs);
  }

  public void setInitialAsyncSequence(LinkedHashSet<JRunAsync> initialAsyncSequence) {
    assert this.initialAsyncSequence.isEmpty();
    initialFragmentIdSequence = Lists.newArrayList();
    // TODO(rluble): hack for now the initial fragments correspond to the initial runAsyncIds.
    initialFragmentIdSequence.addAll(
        Collections2.transform(
            initialAsyncSequence,
            new Function<JRunAsync, Integer>() {
              @Override
              public Integer apply(JRunAsync runAsync) {
                return runAsync.getRunAsyncId();
              }
            }));
    this.initialAsyncSequence = initialAsyncSequence;
  }

  /**
   * If {@code method} is a static impl method, returns the instance method that {@code method} is
   * the implementation of. Otherwise, returns{@code null}.
   */
  public JMethod instanceMethodForStaticImpl(JMethod method) {
    return staticToInstanceMap.get(method);
  }

  @Override
  public void traverse(JVisitor visitor, Context ctx) {
    if (visitor.visit(this, ctx)) {
      visitModuleTypes(visitor);
    }
    visitor.endVisit(this, ctx);
  }

  /**
   * Builds the starter set of type names that should be indexed when seen during addType(). This
   * set is a thread safe instance variable and external logic is free to modify it as further
   * requirements are discovered.
   */
  private static Set<String> buildInitialTypeNamesToIndex() {
    Set<String> typeNamesToIndex = Sets.newHashSet();
    typeNamesToIndex.addAll(
        ImmutableList.of(
            "java.io.Serializable",
            "java.lang.Object",
            "java.lang.String",
            "java.lang.Class",
            "java.lang.CharSequence",
            "java.lang.Cloneable",
            "java.lang.Comparable",
            "java.lang.Enum",
            "java.lang.Iterable",
            "java.util.Iterator",
            "java.lang.AssertionError",
            "java.lang.Boolean",
            "java.lang.Byte",
            "java.lang.Character",
            "java.lang.Short",
            "java.lang.Integer",
            "java.lang.Long",
            "java.lang.Float",
            "java.lang.Double",
            "java.lang.Throwable",
            "com.google.gwt.core.client.GWT",
            JAVASCRIPTOBJECT,
            CLASS_LITERAL_HOLDER,
            "com.google.gwt.core.client.RunAsyncCallback",
            "com.google.gwt.core.client.impl.AsyncFragmentLoader",
            "com.google.gwt.core.client.impl.Impl",
            "com.google.gwt.core.client.prefetch.RunAsyncCode"));
    typeNamesToIndex.addAll(CODEGEN_TYPES_SET);
    return typeNamesToIndex;
  }

  public void visitAllTypes(JVisitor visitor) {
    visitor.accept(allTypes);
  }

  public void visitModuleTypes(JVisitor visitor) {
    for (JDeclaredType type : allTypes) {
      if (isReferenceOnly(type)) {
        continue;
      }
      visitor.accept(type);
    }
  }

  private int countSuperTypes(JClassType type) {
    int count = 0;
    while ((type = type.getSuperClass()) != null) {
      ++count;
    }
    return count;
  }

  /**
   * See notes in {@link #writeObject(ObjectOutputStream)}.
   *
   * @see #writeObject(ObjectOutputStream)
   */
  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    allTypes = deserializeTypes(stream);
    stream.defaultReadObject();
  }

  /**
   * Serializing the Java AST is a multi-step process to avoid blowing out the stack.
   *
   * <ol>
   *   <li>Write all declared types in a lightweight manner to establish object identity for types
   *   <li>Write all fields; write all methods in a lightweight manner to establish object identity
   *       for methods
   *   <li>Write all method bodies
   *   <li>Write everything else, which will mostly refer to already-serialized objects.
   *   <li>Write the bodies of the entry methods (unlike all other methods, these are not contained
   *       by any type.
   * </ol>
   *
   * The goal of this process to to avoid "running away" with the stack. Without special logic here,
   * lots of things would reference types, method body code would reference both types and other
   * methods, and really, really long recursion chains would result.
   */
  private void writeObject(ObjectOutputStream stream) throws IOException {
    serializeTypes(allTypes, stream);
    stream.defaultWriteObject();
  }
}
Ejemplo n.º 18
0
/** Translate a GWT JS AST to a Closure Compiler AST. */
public class ClosureJsAstTranslator {
  private static String getStringValue(double value) {
    long longValue = (long) value;

    // Return "1" instead of "1.0"
    if (longValue == value) {
      return Long.toString(longValue);
    } else {
      return Double.toString(value);
    }
  }

  private final Map<String, StaticSourceFile> sourceCache = new HashMap<String, StaticSourceFile>();

  private final boolean validate;
  private final Set<String> globalVars = Sets.newHashSet();
  private final Set<String> externalProperties = Sets.newHashSet();

  private final Set<String> externalVars = Sets.newHashSet();

  private final JsProgram program;

  ClosureJsAstTranslator(boolean validate, JsProgram program) {
    this.program = program;
    this.validate = validate;
  }

  public Node translate(JsProgramFragment fragment, InputId inputId, String source) {
    Node script = IR.script();
    script.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
    script.setInputId(inputId);
    script.setStaticSourceFile(getClosureSourceFile(source));
    for (JsStatement s : fragment.getGlobalBlock().getStatements()) {
      script.addChildToBack(transform(s));
    }
    // Validate the structural integrity of the AST.
    if (validate) {
      new AstValidator().validateScript(script);
    }
    return script;
  }

  Set<String> getExternalPropertyReferences() {
    return externalProperties;
  }

  Set<String> getExternalVariableReferences() {
    return externalVars;
  }

  Set<String> getGlobalVariableNames() {
    return globalVars;
  }

  private Node applyOriginalName(Node n, JsNode x) {
    /*
     * if (x instanceof HasSymbol) { Symbol symbol = ((HasSymbol)x).getSymbol(); if (symbol != null)
     * { String originalName = symbol.getOriginalSymbolName(); n.putProp(Node.ORIGINALNAME_PROP,
     * originalName); } }
     */
    return n;
  }

  private Node applySourceInfo(Node n, HasSourceInfo srcNode) {
    if (n != null && srcNode != null) {
      SourceInfo info = srcNode.getSourceInfo();
      if (info != null && info.getFileName() != null) {
        n.setStaticSourceFile(getClosureSourceFile(info.getFileName()));
        n.setLineno(info.getStartLine());
        n.setCharno(0);
      }
    }
    return n;
  }

  private StaticSourceFile getClosureSourceFile(String source) {
    StaticSourceFile closureSourceFile = sourceCache.get(source);
    if (closureSourceFile == null) {
      closureSourceFile = new SimpleSourceFile(source, false);
      sourceCache.put(source, closureSourceFile);
    }
    return closureSourceFile;
  }

  private String getName(JsName name) {
    return name.getShortIdent();
  }

  private String getName(JsNameRef name) {
    return name.getShortIdent();
  }

  private Node getNameNodeFor(HasName hasName) {
    Node n = IR.name(getName(hasName.getName()));
    applyOriginalName(n, (JsNode) hasName);
    return applySourceInfo(n, (HasSourceInfo) hasName);
  }

  private int getTokenForOp(JsBinaryOperator op) {
    switch (op) {
      case MUL:
        return Token.MUL;
      case DIV:
        return Token.DIV;
      case MOD:
        return Token.MOD;
      case ADD:
        return Token.ADD;
      case SUB:
        return Token.SUB;
      case SHL:
        return Token.LSH;
      case SHR:
        return Token.RSH;
      case SHRU:
        return Token.URSH;
      case LT:
        return Token.LT;
      case LTE:
        return Token.LE;
      case GT:
        return Token.GT;
      case GTE:
        return Token.GE;
      case INSTANCEOF:
        return Token.INSTANCEOF;
      case INOP:
        return Token.IN;
      case EQ:
        return Token.EQ;
      case NEQ:
        return Token.NE;
      case REF_EQ:
        return Token.SHEQ;
      case REF_NEQ:
        return Token.SHNE;
      case BIT_AND:
        return Token.BITAND;
      case BIT_XOR:
        return Token.BITXOR;
      case BIT_OR:
        return Token.BITOR;
      case AND:
        return Token.AND;
      case OR:
        return Token.OR;
      case ASG:
        return Token.ASSIGN;
      case ASG_ADD:
        return Token.ASSIGN_ADD;
      case ASG_SUB:
        return Token.ASSIGN_SUB;
      case ASG_MUL:
        return Token.ASSIGN_MUL;
      case ASG_DIV:
        return Token.ASSIGN_DIV;
      case ASG_MOD:
        return Token.ASSIGN_MOD;
      case ASG_SHL:
        return Token.ASSIGN_LSH;
      case ASG_SHR:
        return Token.ASSIGN_RSH;
      case ASG_SHRU:
        return Token.ASSIGN_URSH;
      case ASG_BIT_AND:
        return Token.ASSIGN_BITAND;
      case ASG_BIT_OR:
        return Token.ASSIGN_BITOR;
      case ASG_BIT_XOR:
        return Token.ASSIGN_BITXOR;
      case COMMA:
        return Token.COMMA;
    }
    return 0;
  }

  private int getTokenForOp(JsUnaryOperator op) {
    switch (op) {
      case BIT_NOT:
        return Token.BITNOT;
      case DEC:
        return Token.DEC;
      case DELETE:
        return Token.DELPROP;
      case INC:
        return Token.INC;
      case NEG:
        return Token.NEG;
      case POS:
        return Token.POS;
      case NOT:
        return Token.NOT;
      case TYPEOF:
        return Token.TYPEOF;
      case VOID:
        return Token.VOID;
    }
    throw new IllegalStateException();
  }

  private Node transform(JsArrayAccess x) {
    Node n = IR.getelem(transform(x.getArrayExpr()), transform(x.getIndexExpr()));
    return applySourceInfo(n, x);
  }

  private Node transform(JsArrayLiteral x) {
    Node n = IR.arraylit();
    for (Object element : x.getExpressions()) {
      JsExpression arg = (JsExpression) element;
      n.addChildToBack(transform(arg));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsBinaryOperation x) {
    JsBinaryOperator op = x.getOperator();
    Node n = new Node(getTokenForOp(op), transform(x.getArg1()), transform(x.getArg2()));
    return applySourceInfo(n, x);
  }

  private Node transform(JsBlock x) {
    Node n = IR.block();
    for (JsStatement s : x.getStatements()) {
      n.addChildToBack(transform(s));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsBooleanLiteral x) {
    Node n = x.getValue() ? IR.trueNode() : IR.falseNode();
    return applySourceInfo(n, x);
  }

  private Node transform(JsBreak x) {
    Node n;
    JsNameRef label = x.getLabel();
    if (label == null) {
      n = IR.breakNode();
    } else {
      n = IR.breakNode(transformLabel(label));
    }

    return applySourceInfo(n, x);
  }

  private Node transform(JsCase x) {
    Node expr = transform(x.getCaseExpr());
    Node body = IR.block();
    body.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
    applySourceInfo(body, x);

    for (Object element : x.getStmts()) {
      JsStatement stmt = (JsStatement) element;
      body.addChildToBack(transform(stmt));
    }

    Node n = IR.caseNode(expr, body);
    return applySourceInfo(n, x);
  }

  private Node transform(JsCatch x) {
    Node n = IR.catchNode(transformName(x.getParameter().getName()), transform(x.getBody()));
    Preconditions.checkState(x.getCondition() == null);
    return applySourceInfo(n, x);
  }

  private Node transform(JsConditional x) {
    Node n =
        IR.hook(
            transform(x.getTestExpression()),
            transform(x.getThenExpression()),
            transform(x.getElseExpression()));
    return applySourceInfo(n, x);
  }

  private Node transform(JsContinue x) {
    Node n;
    JsNameRef label = x.getLabel();
    if (label == null) {
      n = IR.continueNode();
    } else {
      n = IR.continueNode(transformLabel(label));
    }

    return applySourceInfo(n, x);
  }

  private Node transform(JsDebugger x) {
    Node n = new Node(Token.DEBUGGER);
    return applySourceInfo(n, x);
  }

  private Node transform(JsDefault x) {
    Node body = IR.block();
    body.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
    applySourceInfo(body, x);

    for (Object element : x.getStmts()) {
      JsStatement stmt = (JsStatement) element;
      body.addChildToBack(transform(stmt));
    }
    Node n = IR.defaultCase(body);
    return applySourceInfo(n, x);
  }

  private Node transform(JsDoWhile x) {
    Node n = IR.doNode(transformBody(x.getBody(), x), transform(x.getCondition()));
    return applySourceInfo(n, x);
  }

  private Node transform(JsEmpty x) {
    return IR.empty();
  }

  private Node transform(JsExpression x) {
    assert x != null;
    switch (x.getKind()) {
      case ARRAY:
        return transform((JsArrayLiteral) x);
      case ARRAY_ACCESS:
        return transform((JsArrayAccess) x);
      case BINARY_OP:
        return transform((JsBinaryOperation) x);
      case CONDITIONAL:
        return transform((JsConditional) x);
      case INVOKE:
        return transform((JsInvocation) x);
      case FUNCTION:
        return transform((JsFunction) x);
      case OBJECT:
        return transform((JsObjectLiteral) x);
      case BOOLEAN:
        return transform((JsBooleanLiteral) x);
      case NULL:
        return transform((JsNullLiteral) x);
      case NUMBER:
        if (x instanceof JsNumericEntry) {
          return transform((JsNumericEntry) x);
        }
        return transform((JsNumberLiteral) x);
      case REGEXP:
        return transform((JsRegExp) x);
      case STRING:
        return transform((JsStringLiteral) x);
      case THIS:
        return transform((JsThisRef) x);
      case NAME_OF:
        return transform((JsNameOf) x);
      case NAME_REF:
        return transform((JsNameRef) x);
      case NEW:
        return transform((JsNew) x);
      case POSTFIX_OP:
        return transform((JsPostfixOperation) x);
      case PREFIX_OP:
        return transform((JsPrefixOperation) x);
      default:
        throw new IllegalStateException(
            "Unexpected expression type: " + x.getClass().getSimpleName());
    }
  }

  private Node transform(JsExprStmt x) {
    // The GWT JS AST doesn't produce function declarations, instead
    // they are expressions statements:
    Node expr = transform(x.getExpression());
    if (!expr.isFunction()) {
      return IR.exprResult(expr);
    } else {
      return expr;
    }
  }

  private Node transform(JsFor x) {
    // The init expressions or var decl.
    //
    Node init;
    if (x.getInitExpr() != null) {
      init = transform(x.getInitExpr());
    } else if (x.getInitVars() != null) {
      init = transform(x.getInitVars());
    } else {
      init = IR.empty();
    }

    // The loop test.
    //
    Node cond;
    if (x.getCondition() != null) {
      cond = transform(x.getCondition());
    } else {
      cond = IR.empty();
    }

    // The incr expression.
    //
    Node incr;
    if (x.getIncrExpr() != null) {
      incr = transform(x.getIncrExpr());
    } else {
      incr = IR.empty();
    }

    Node body = transformBody(x.getBody(), x);
    Node n = IR.forNode(init, cond, incr, body);
    return applySourceInfo(n, x);
  }

  private Node transform(JsForIn x) {
    Node valueExpr;
    if (x.getIterVarName() != null) {
      valueExpr = new Node(Token.VAR, transformName(x.getIterVarName()));
    } else {
      // Just a name ref.
      //
      valueExpr = transform(x.getIterExpr());
    }

    Node n = IR.forIn(valueExpr, transform(x.getObjExpr()), transformBody(x.getBody(), x));
    return applySourceInfo(n, x);
  }

  private Node transform(JsFunction x) {
    Node name;
    if (x.getName() != null) {
      name = getNameNodeFor(x);
    } else {
      name = IR.name("");
    }
    applySourceInfo(name, x);

    Node params = IR.paramList();
    for (Object element : x.getParameters()) {
      JsParameter param = (JsParameter) element;
      params.addChildToBack(transform(param));
    }
    applySourceInfo(params, x);

    Node n = IR.function(name, params, transform(x.getBody()));
    if (name.getString().isEmpty()) {
      n.putProp(Node.ORIGINALNAME_PROP, "");
    } else {
      applyOriginalName(n, x);
    }

    /*
     * if (x.isConstructor()) { JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
     * builder.recordConstructor(); n.setJSDocInfo(builder.build(n)); }
     */

    return applySourceInfo(n, x);
  }

  private Node transform(JsIf x) {
    Node n = IR.ifNode(transform(x.getIfExpr()), transformBody(x.getThenStmt(), x));
    if (x.getElseStmt() != null) {
      n.addChildToBack(transformBody(x.getElseStmt(), x));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsInvocation x) {
    Node n = IR.call(transform(x.getQualifier()));
    for (Object element : x.getArguments()) {
      JsExpression arg = (JsExpression) element;
      n.addChildToBack(transform(arg));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsLabel x) {
    Node n = IR.label(transformLabel(x.getName()), transform(x.getStmt()));

    return applySourceInfo(n, x);
  }

  private Node transform(JsNameOf x) {
    Node n = transformNameAsString(x.getName().getShortIdent(), x);
    applyOriginalName(n, x);
    return applySourceInfo(n, x);
  }

  private Node transform(JsNameRef x) {
    Node n;
    JsName name = x.getName();
    boolean isExternal = name == null || !name.isObfuscatable();
    if (x.getQualifier() != null) {
      n = IR.getprop(transform(x.getQualifier()), transformNameAsString(x.getShortIdent(), x));
      if (isExternal) {
        this.externalProperties.add(x.getShortIdent());
      }
    } else {
      n = transformName(x.getShortIdent(), x);
      if (isExternal) {
        this.externalVars.add(x.getShortIdent());
      } else if (name.getEnclosing() == program.getScope()) {
        this.globalVars.add(x.getShortIdent());
      }
    }
    applyOriginalName(n, x);
    return applySourceInfo(n, x);
  }

  private Node transform(JsNew x) {
    Node n = IR.newNode(transform(x.getConstructorExpression()));
    for (Object element : x.getArguments()) {
      JsExpression arg = (JsExpression) element;
      n.addChildToBack(transform(arg));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsNullLiteral x) {
    return IR.nullNode();
  }

  private Node transform(JsNumericEntry x) {
    return IR.number(x.getValue());
  }

  private Node transform(JsNumberLiteral x) {
    return IR.number(x.getValue());
  }

  private Node transform(JsObjectLiteral x) {
    Node n = IR.objectlit();

    for (Object element : x.getPropertyInitializers()) {
      JsPropertyInitializer propInit = (JsPropertyInitializer) element;
      Node key;
      if (propInit.getLabelExpr().getKind() == NodeKind.NUMBER) {
        key = transformNumberAsString((JsNumberLiteral) propInit.getLabelExpr());
        key.putBooleanProp(Node.QUOTED_PROP, true);
      } else if (propInit.getLabelExpr().getKind() == NodeKind.NAME_REF) {
        key =
            transformNameAsString(
                ((JsNameRef) propInit.getLabelExpr()).getShortIdent(), propInit.getLabelExpr());
      } else {
        key = transform(propInit.getLabelExpr());
      }
      Preconditions.checkState(key.isString(), key);
      key.setType(Token.STRING_KEY);
      // Set as quoted as the rhino version we use does not distinguish one from the other.
      // Closure assumes unquoted property names are obfuscatable, but since there is no way to
      // distinguish between them at this point they have to be assumed quoted, hence not
      // obfuscatable.
      // TODO(rluble): Make sure this is handled correctly once rhino is upgraded.
      key.putBooleanProp(Node.QUOTED_PROP, true);
      n.addChildToBack(IR.propdef(key, transform(propInit.getValueExpr())));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsParameter x) {
    return getNameNodeFor(x);
  }

  private Node transform(JsPositionMarker x) {
    return IR.empty();
  }

  private Node transform(JsPostfixOperation x) {
    Node n = new Node(getTokenForOp(x.getOperator()), transform(x.getArg()));
    n.putBooleanProp(Node.INCRDECR_PROP, true);
    return applySourceInfo(n, x);
  }

  private Node transform(JsPrefixOperation x) {
    Node n = new Node(getTokenForOp(x.getOperator()), transform(x.getArg()));
    return applySourceInfo(n, x);
  }

  private Node transform(JsRegExp x) {
    String flags = x.getFlags();
    Node n =
        IR.regexp(
            Node.newString(x.getPattern()), Node.newString(flags != null ? x.getFlags() : ""));
    return applySourceInfo(n, x);
  }

  private Node transform(JsReturn x) {
    Node n = IR.returnNode();
    JsExpression result = x.getExpr();
    if (result != null) {
      n.addChildToBack(transform(x.getExpr()));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsStatement x) {
    switch (x.getKind()) {
      case BLOCK:
        return transform((JsBlock) x);
      case BREAK:
        return transform((JsBreak) x);
      case CONTINUE:
        return transform((JsContinue) x);
      case DEBUGGER:
        return transform((JsDebugger) x);
      case DO:
        return transform((JsDoWhile) x);
      case EMPTY:
        return transform((JsEmpty) x);
      case EXPR_STMT:
        return transform((JsExprStmt) x);
      case FOR:
        return transform((JsFor) x);
      case FOR_IN:
        return transform((JsForIn) x);
      case IF:
        return transform((JsIf) x);
      case LABEL:
        return transform((JsLabel) x);
      case POSITION_MARKER:
        return transform((JsPositionMarker) x);
      case RETURN:
        return transform((JsReturn) x);
      case SWITCH:
        return transform((JsSwitch) x);
      case THROW:
        return transform((JsThrow) x);
      case TRY:
        return transform((JsTry) x);
      case VARS:
        return transform((JsVars) x);
      case WHILE:
        return transform((JsWhile) x);
      default:
        throw new IllegalStateException(
            "Unexpected statement type: " + x.getClass().getSimpleName());
    }
  }

  private Node transform(JsStringLiteral x) {
    return IR.string(x.getValue());
  }

  private Node transform(JsSwitch x) {
    Node n = IR.switchNode(transform(x.getExpr()));
    for (JsSwitchMember member : x.getCases()) {
      n.addChildToBack(transform(member));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsSwitchMember x) {
    switch (x.getKind()) {
      case CASE:
        return transform((JsCase) x);
      case DEFAULT:
        return transform((JsDefault) x);
      default:
        throw new IllegalStateException(
            "Unexpected switch member type: " + x.getClass().getSimpleName());
    }
  }

  private Node transform(JsThisRef x) {
    Node n = new Node(Token.THIS);
    return applySourceInfo(n, x);
  }

  private Node transform(JsThrow x) {
    Node n = IR.throwNode(transform(x.getExpr()));
    return applySourceInfo(n, x);
  }

  private Node transform(JsTry x) {
    Node n = new Node(Token.TRY, transform(x.getTryBlock()));

    Node catches = new Node(Token.BLOCK);
    for (JsCatch catchBlock : x.getCatches()) {
      catches.addChildToBack(transform(catchBlock));
    }
    n.addChildToBack(catches);

    JsBlock finallyBlock = x.getFinallyBlock();
    if (finallyBlock != null) {
      n.addChildToBack(transform(finallyBlock));
    }

    return applySourceInfo(n, x);
  }

  private Node transform(JsVar x) {
    Node n = getNameNodeFor(x);
    JsExpression initExpr = x.getInitExpr();
    if (initExpr != null) {
      n.addChildToBack(transform(initExpr));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsVars x) {
    Node n = new Node(Token.VAR);
    for (JsVar var : x) {
      n.addChildToBack(transform(var));
    }
    return applySourceInfo(n, x);
  }

  private Node transform(JsWhile x) {
    Node n =
        IR.forNode(
            IR.empty(), transform(x.getCondition()), IR.empty(), transformBody(x.getBody(), x));
    return applySourceInfo(n, x);
  }

  private Node transformBody(JsStatement x, HasSourceInfo parent) {
    Node n = transform(x);
    if (!n.isBlock()) {
      Node stmt = n;
      n = IR.block();
      if (!stmt.isEmpty()) {
        n.addChildToBack(stmt);
      }
      applySourceInfo(n, parent);
    }
    return n;
  }

  private Node transformLabel(JsName label) {
    Node n = IR.labelName(getName(label));
    return applySourceInfo(n, label.getStaticRef());
  }

  private Node transformLabel(JsNameRef label) {
    Node n = IR.labelName(getName(label));
    return applySourceInfo(n, label);
  }

  private Node transformName(JsName name) {
    Node n = IR.name(getName(name));
    return applySourceInfo(n, name.getStaticRef());
  }

  private Node transformName(String name, HasSourceInfo info) {
    Node n = IR.name(name);
    return applySourceInfo(n, info);
  }

  private Node transformNameAsString(String name, HasSourceInfo info) {
    Node n = IR.string(name);
    return applySourceInfo(n, info);
  }

  private Node transformNumberAsString(JsNumberLiteral literalNode) {
    Node irNode = Node.newString(getStringValue(literalNode.getValue()));
    return irNode;
  }
}
Ejemplo n.º 19
0
  public void testWritesTargetLibraryProperties() throws UnableToCompleteException {
    compilerContext = compilerContextBuilder.compileMonolithic(false).build();
    ModuleDef libraryOneModule =
        ModuleDefLoader.loadFromClassPath(
            TreeLogger.NULL,
            compilerContext,
            "com.google.gwt.dev.cfg.testdata.separate.libraryone.LibraryOne",
            false);

    // Library one sees all defined values for the "libraryTwoProperty" binding property and knows
    // which one was defined in this target library.
    for (BindingProperty bindingProperty :
        libraryOneModule.getProperties().getBindingProperties()) {
      if (!bindingProperty.getName().equals("libraryTwoProperty")) {
        continue;
      }
      assertEquals(
          Sets.newHashSet(bindingProperty.getDefinedValues()),
          Sets.newHashSet("yes", "no", "maybe"));
      assertEquals(
          Sets.newHashSet(bindingProperty.getTargetLibraryDefinedValues()),
          Sets.newHashSet("maybe"));
    }

    // Library one added a new defined value of "maybe" for the "libraryTwoProperty" binding
    // property.
    assertEquals(
        Sets.newHashSet(
            mockLibraryWriter.getNewBindingPropertyValuesByName().get("libraryTwoProperty")),
        Sets.newHashSet("maybe"));

    // Library one sees all defined values for the "libraryTwoConfigProperty" property and knows
    // which one was defined in this target library.
    for (ConfigurationProperty configurationProperty :
        libraryOneModule.getProperties().getConfigurationProperties()) {
      if (!configurationProperty.getName().equals("libraryTwoConfigProperty")) {
        continue;
      }
      assertEquals(Sets.newHashSet(configurationProperty.getValues()), Sets.newHashSet("false"));
      assertEquals(
          Sets.newHashSet(configurationProperty.getTargetLibraryValues()),
          Sets.newHashSet("false"));
    }

    // Library one added a new defined value of "maybe" for the "libraryTwoConfigProperty"
    // property.
    assertEquals(
        Sets.newHashSet(
            mockLibraryWriter
                .getNewConfigurationPropertyValuesByName()
                .get("libraryTwoConfigProperty")),
        Sets.newHashSet("false"));
  }
  // VisibleForTesting
  class LibraryPrecompiler extends Precompiler {

    private Set<String> badRebindCombinations = Sets.newHashSet();
    private SetMultimap<String, String> generatorNamesByPreviouslyReboundTypeName =
        HashMultimap.create();
    private Set<String> previouslyReboundTypeNames = Sets.newHashSet();

    public LibraryPrecompiler(RebindPermutationOracle rpo) {
      super(rpo);
    }

    @Override
    protected void beforeUnifyAst(Set<String> allRootTypes) throws UnableToCompleteException {
      runGeneratorsToFixedPoint(rpo);

      Set<JDeclaredType> reboundTypes = gatherReboundTypes(rpo);
      buildFallbackRuntimeRebindRules(reboundTypes);
      buildSimpleRuntimeRebindRules(module.getRules());

      buildRuntimeRebindRegistrator(allRootTypes);
      buildPropertyProviderRegistrator(
          allRootTypes,
          module.getProperties().getBindingProperties(),
          module.getProperties().getConfigurationProperties());
    }

    @Override
    protected void checkEntryPoints(String[] entryPointTypeNames, String[] additionalRootTypes) {
      // Library construction does not need to care whether their are or are not any entry points.
    }

    @Override
    protected void createJProgram() {
      jprogram = new JProgram(options.shouldLink());
    }

    @Override
    protected JMethodCall createReboundModuleLoad(
        SourceInfo info,
        JDeclaredType reboundEntryType,
        String originalMainClassName,
        JDeclaredType enclosingType)
        throws UnableToCompleteException {
      return null;
    }

    // VisibleForTesting
    protected JDeclaredType ensureFullTypeLoaded(JDeclaredType type) {
      String resourcePath = LibraryGroupUnitCache.typeSourceNameToResourcePath(type.getName());
      CompilationUnit compilationUnit = compilerContext.getUnitCache().find(resourcePath);
      type = compilationUnit.getTypeByName(type.getName());
      return type;
    }

    // VisibleForTesting
    protected Set<JDeclaredType> gatherReboundTypes(RebindPermutationOracle rpo) {
      Collection<CompilationUnit> compilationUnits =
          rpo.getCompilationState().getCompilationUnits();
      Set<JDeclaredType> reboundTypes = Sets.newLinkedHashSet();
      for (CompilationUnit compilationUnit : compilationUnits) {
        for (JDeclaredType type : compilationUnit.getTypes()) {
          ReboundTypeRecorder.exec(type, reboundTypes);
        }
      }
      return reboundTypes;
    }

    protected StandardGeneratorContext getGeneratorContext() {
      return rpo.getGeneratorContext();
    }

    // VisibleForTesting
    protected Set<String> getTypeNames(Set<JDeclaredType> types) {
      Set<String> typeNames = Sets.newHashSet();
      for (JDeclaredType type : types) {
        typeNames.add(type.getName());
      }
      return typeNames;
    }

    @Override
    protected void populateEntryPointRootTypes(
        String[] entryPointTypeNames, Set<String> allRootTypes) {
      Collections.addAll(allRootTypes, entryPointTypeNames);
    }

    @Override
    protected void rebindEntryPoint(
        SourceInfo info,
        JMethod bootStrapMethod,
        JBlock block,
        String mainClassName,
        JDeclaredType mainType)
        throws UnableToCompleteException {
      JMethodCall onModuleLoadCall =
          createReboundModuleLoad(
              info, mainType, mainClassName, bootStrapMethod.getEnclosingType());
      block.addStmt(onModuleLoadCall.makeStatement());
    }

    /**
     * Runs a particular generator on the provided set of rebound types. Takes care to guard against
     * duplicate work during reruns as generation approaches a fixed point.
     *
     * @return whether a fixed point was reached.
     */
    // VisibleForTesting
    protected boolean runGenerator(RuleGenerateWith generatorRule, Set<String> reboundTypeNames)
        throws UnableToCompleteException {
      boolean fixedPoint = true;
      StandardGeneratorContext generatorContext = getGeneratorContext();
      removePreviouslyReboundCombinations(generatorRule.getName(), reboundTypeNames);
      reboundTypeNames.removeAll(previouslyReboundTypeNames);

      for (String reboundTypeName : reboundTypeNames) {
        if (badRebindCombinations.contains(generatorRule.getName() + "-" + reboundTypeName)) {
          continue;
        }
        generatorNamesByPreviouslyReboundTypeName.put(reboundTypeName, generatorRule.getName());
        reboundTypeName = reboundTypeName.replace("$", ".");
        generatorRule.generate(logger, module.getProperties(), generatorContext, reboundTypeName);

        if (generatorContext.isDirty()) {
          fixedPoint = false;
          previouslyReboundTypeNames.add(reboundTypeName);
          // Ensure that cascading generations rerun properly.
          for (String generatedTypeName : generatorContext.getGeneratedUnitMap().keySet()) {
            generatorNamesByPreviouslyReboundTypeName.removeAll(generatedTypeName);
          }
          generatorContext.finish(logger);
        } else {
          badRebindCombinations.add(generatorRule.getName() + "-" + reboundTypeName);
        }
      }

      return fixedPoint;
    }

    // VisibleForTesting
    protected void runGeneratorsToFixedPoint(RebindPermutationOracle rpo)
        throws UnableToCompleteException {
      boolean fixedPoint;
      do {
        compilerContext
            .getLibraryWriter()
            .setReboundTypeSourceNames(getTypeNames(gatherReboundTypes(rpo)));

        fixedPoint = runGenerators();
      } while (!fixedPoint);

      // This is a horribly dirty hack to work around the fact that CssResourceGenerator uses a
      // completely nonstandard resource creation and caching mechanism that ignores the
      // GeneratorContext infrastructure. It and GenerateCssAst need to be fixed.
      for (Entry<String, File> entry :
          ResourceGeneratorUtilImpl.getGeneratedFilesByName().entrySet()) {
        String resourcePath = entry.getKey();
        File resourceFile = entry.getValue();
        compilerContext
            .getLibraryWriter()
            .addBuildResource(new FileResource(null, resourcePath, resourceFile));
      }
    }

    // VisibleForTesting
    void buildFallbackRuntimeRebindRules(Set<JDeclaredType> reboundTypes)
        throws UnableToCompleteException {
      // Create fallback rebinds.
      for (JDeclaredType reboundType : reboundTypes) {
        // It's possible for module A to declare rebind rules about types that were defined in
        // module B. While processing module A these types might not be loaded in their full form,
        // which would cause their instantiability analysis to be wrong. So, make sure the full
        // version of each such type has been loaded.
        // TODO(stalcup) find a way to check if a type is instantiable without having to have the
        // full version of the type loaded.
        reboundType = ensureFullTypeLoaded(reboundType);
        if (!reboundType.isInstantiable()) {
          continue;
        }
        RuleReplaceWithFallback fallbackRule =
            new RuleReplaceWithFallback(reboundType.getName().replace("$", "."));
        fallbackRule.generateRuntimeRebindClasses(logger, module, getGeneratorContext());
      }
    }

    // VisibleForTesting
    void buildPropertyProviderRegistrator(
        Set<String> allRootTypes,
        SortedSet<BindingProperty> bindingProperties,
        SortedSet<ConfigurationProperty> configurationProperties)
        throws UnableToCompleteException {
      PropertyProviderRegistratorGenerator propertyProviderRegistratorGenerator =
          new PropertyProviderRegistratorGenerator(bindingProperties, configurationProperties);
      StandardGeneratorContext generatorContext = getGeneratorContext();
      String propertyProviderRegistratorTypeName =
          propertyProviderRegistratorGenerator.generate(logger, generatorContext, module.getName());
      // Ensures that unification traverses and keeps the class.
      allRootTypes.add(propertyProviderRegistratorTypeName);
      // Ensures that JProgram knows to index this class's methods so that later bootstrap
      // construction code is able to locate the FooPropertyProviderRegistrator.register() function.
      jprogram.addIndexedTypeName(propertyProviderRegistratorTypeName);
      jprogram.setPropertyProviderRegistratorTypeSourceName(propertyProviderRegistratorTypeName);
      generatorContext.finish(logger);
    }

    // VisibleForTesting
    void buildRuntimeRebindRegistrator(Set<String> allRootTypes) throws UnableToCompleteException {
      RuntimeRebindRegistratorGenerator runtimeRebindRegistratorGenerator =
          new RuntimeRebindRegistratorGenerator();
      StandardGeneratorContext generatorContext = getGeneratorContext();
      String runtimeRebindRegistratorTypeName =
          runtimeRebindRegistratorGenerator.generate(logger, generatorContext, module.getName());
      // Ensures that unification traverses and keeps the class.
      allRootTypes.add(runtimeRebindRegistratorTypeName);
      // Ensures that JProgram knows to index this class's methods so that later bootstrap
      // construction code is able to locate the FooRuntimeRebindRegistrator.register() function.
      jprogram.addIndexedTypeName(runtimeRebindRegistratorTypeName);
      jprogram.setRuntimeRebindRegistratorTypeName(runtimeRebindRegistratorTypeName);
      generatorContext.finish(logger);
    }

    // VisibleForTesting
    void buildSimpleRuntimeRebindRules(Rules rules) throws UnableToCompleteException {
      // Create rebinders for rules specified in the module.
      Iterator<Rule> iterator = rules.iterator();
      while (iterator.hasNext()) {
        Rule rule = iterator.next();
        if (rule instanceof RuleGenerateWith) {
          continue;
        }
        rule.generateRuntimeRebindClasses(logger, module, getGeneratorContext());
      }
    }

    private boolean relevantPropertiesHaveChanged(RuleGenerateWith generatorRule) {
      // Gather binding and configuration property values that have been changed in the part of
      // the library dependency tree on which this generator has not yet run.
      Multimap<String, String> newConfigurationPropertyValues =
          compilerContext.gatherNewConfigurationPropertyValuesForGenerator(generatorRule.getName());
      Multimap<String, String> newBindingPropertyValues =
          compilerContext.gatherNewBindingPropertyValuesForGenerator(generatorRule.getName());

      return generatorRule.caresAboutProperties(newConfigurationPropertyValues.keySet())
          || generatorRule.caresAboutProperties(newBindingPropertyValues.keySet());
    }

    /**
     * Generator output can create opportunities for further generator execution, so runGenerators()
     * is repeated to a fixed point. But previously handled generator/reboundType pairs should be
     * ignored.
     */
    private void removePreviouslyReboundCombinations(
        final String generatorName, Set<String> newReboundTypeNames) {
      newReboundTypeNames.removeAll(
          Sets.newHashSet(
              Sets.filter(
                  newReboundTypeNames,
                  new Predicate<String>() {
                    @Override
                    public boolean apply(@Nullable String newReboundTypeName) {
                      return generatorNamesByPreviouslyReboundTypeName.containsEntry(
                          newReboundTypeName, generatorName);
                    }
                  })));
    }

    /**
     * Figures out which generators should run based on the current state and runs them. Generator
     * execution can create new opportunities for further generator execution so this function
     * should be invoked repeatedly till a fixed point is reached.<br>
     * Returns whether a fixed point was reached.
     */
    private boolean runGenerators() throws UnableToCompleteException {
      boolean fixedPoint = true;
      boolean globalCompile = compilerContext.getOptions().shouldLink();
      Set<Rule> generatorRules = Sets.newHashSet(module.getGeneratorRules());

      for (Rule rule : generatorRules) {
        RuleGenerateWith generatorRule = (RuleGenerateWith) rule;
        String generatorName = generatorRule.getName();

        if (generatorRule.contentDependsOnTypes() && !globalCompile) {
          // Type unstable generators can only be safely run in the global phase.
          // TODO(stalcup): modify type unstable generators such that their output is no longer
          // unstable.
          continue;
        }

        // Run generator for new rebound types.
        Set<String> newReboundTypeNames =
            compilerContext.gatherNewReboundTypeNamesForGenerator(generatorName);
        fixedPoint &= runGenerator(generatorRule, newReboundTypeNames);

        // If the content of generator output varies when some relevant properties change and some
        // relevant properties have changed.
        if (generatorRule.contentDependsOnProperties()
            && relevantPropertiesHaveChanged(generatorRule)) {
          // Rerun the generator on old rebound types to replace old stale output.
          Set<String> oldReboundTypeNames =
              compilerContext.gatherOldReboundTypeNamesForGenerator(generatorName);
          fixedPoint &= runGenerator(generatorRule, oldReboundTypeNames);
        }

        compilerContext.getLibraryWriter().addRanGeneratorName(generatorName);
      }

      return fixedPoint;
    }
  }
Ejemplo n.º 21
0
/**
 * Remove globally unreferenced classes, interfaces, methods, parameters, and fields from the AST.
 * This algorithm is based on having known "entry points" into the application which serve as the
 * root(s) from which reachability is determined and everything else is rescued. Pruner determines
 * reachability at a global level based on method calls and new operations; it does not perform any
 * local code flow analysis. But, a local code flow optimization pass that can eliminate method
 * calls would allow Pruner to prune additional nodes.
 *
 * <p>Note: references to pruned types may still exist in the tree after this pass runs, however, it
 * should only be in contexts that do not rely on any code generation for the pruned type. For
 * example, it's legal to have a variable of a pruned type, or to try to cast to a pruned type.
 * These will cause natural failures at run time; or later optimizations might be able to hard-code
 * failures at compile time.
 *
 * <p>Note: this class is limited to pruning parameters of static methods only.
 */
public class Pruner {
  /**
   * Remove assignments to pruned fields, locals and params. Nullify the return type of methods
   * declared to return a globally uninstantiable type. Replace references to pruned variables and
   * methods by references to the null field and null method, assignments to pruned variables, and
   * nullify the type of variable whose type is a pruned type.
   */
  private class CleanupRefsVisitor extends JModVisitorWithTemporaryVariableCreation {
    private final Stack<JExpression> lValues = new Stack<JExpression>();
    private final ListMultimap<JMethod, JParameter> priorParametersByMethod;
    private final Set<? extends JNode> referencedNonTypes;

    {
      // Initialize a sentinel value to avoid having to check for empty stack.
      lValues.push(null);
    }

    public CleanupRefsVisitor(
        Set<? extends JNode> referencedNodes,
        ListMultimap<JMethod, JParameter> priorParametersByMethod,
        OptimizerContext optimizerCtx) {
      super(optimizerCtx);
      this.referencedNonTypes = referencedNodes;
      this.priorParametersByMethod = priorParametersByMethod;
    }

    @Override
    public void endVisit(JBinaryOperation x, Context ctx) {
      if (x.getOp() != JBinaryOperator.ASG) {
        return;
      }
      // The LHS of assignments may have been pruned.
      lValues.pop();
      JExpression lhs = x.getLhs();
      if (!(lhs instanceof JVariableRef)) {
        return;
      }

      JVariableRef variableRef = (JVariableRef) lhs;
      if (isVariablePruned(variableRef.getTarget())) {
        // TODO: better null tracking; we might be missing some NPEs here.
        JExpression replacement =
            makeReplacementForAssignment(x.getSourceInfo(), variableRef, x.getRhs());
        ctx.replaceMe(replacement);
      }
    }

    @Override
    public void endVisit(JDeclarationStatement x, Context ctx) {
      super.endVisit(x, ctx);
      lValues.pop();
      // The variable may have been pruned.
      if (isVariablePruned(x.getVariableRef().getTarget())) {
        JExpression replacement =
            makeReplacementForAssignment(x.getSourceInfo(), x.getVariableRef(), x.getInitializer());
        ctx.replaceMe(replacement.makeStatement());
      }
    }

    @Override
    public void endVisit(JFieldRef x, Context ctx) {
      // Handle l-values at a higher level.
      if (lValues.peek() == x) {
        return;
      }

      if (isPruned(x.getField())) {
        // The field is gone; replace x by a null field reference.
        JFieldRef fieldRef = transformToNullFieldRef(x, program);
        ctx.replaceMe(fieldRef);
      }
    }

    @Override
    public void exit(JMethod x, Context ctx) {
      JType type = x.getType();
      if (type instanceof JReferenceType
          && !program.typeOracle.isInstantiatedType((JReferenceType) type)) {
        x.setType(JReferenceType.NULL_TYPE);
      }
      Predicate<JMethod> isPruned =
          new Predicate<JMethod>() {
            @Override
            public boolean apply(JMethod method) {
              return isPruned(method);
            }
          };
      Iterables.removeIf(x.getOverriddenMethods(), isPruned);
      Iterables.removeIf(x.getOverridingMethods(), isPruned);
    }

    @Override
    public void endVisit(JMethodCall x, Context ctx) {
      JMethod method = x.getTarget();

      // Is the method pruned entirely?
      if (isPruned(method)) {
        /*
         * We assert that method must be non-static, otherwise it would have
         * been rescued.
         */
        ctx.replaceMe(transformToNullMethodCall(x, program));
        return;
      }

      maybeReplaceForPrunedParameters(x, ctx);
    }

    @Override
    public void endVisit(JNameOf x, Context ctx) {
      HasName node = x.getNode();
      boolean pruned;
      if (node instanceof JField) {
        pruned = isPruned((JField) node);
      } else if (node instanceof JMethod) {
        pruned = isPruned((JMethod) node);
      } else if (node instanceof JReferenceType) {
        pruned = !program.typeOracle.isInstantiatedType((JReferenceType) node);
      } else {
        throw new InternalCompilerException("Unhandled JNameOf node: " + node);
      }

      if (pruned) {
        ctx.replaceMe(program.getLiteralNull());
      }
    }

    @Override
    public void endVisit(JNewInstance x, Context ctx) {
      maybeReplaceForPrunedParameters(x, ctx);
    }

    @Override
    public void endVisit(JRuntimeTypeReference x, Context ctx) {
      if (!program.typeOracle.isInstantiatedType(x.getReferredType())) {
        ctx.replaceMe(program.getLiteralNull());
      }
    }

    @Override
    public void endVisit(JsniFieldRef x, Context ctx) {
      if (isPruned(x.getField())) {
        String ident = x.getIdent();
        JField nullField = program.getNullField();
        JsniFieldRef nullFieldRef =
            new JsniFieldRef(
                x.getSourceInfo(), ident, nullField, x.getEnclosingType(), x.isLvalue());
        ctx.replaceMe(nullFieldRef);
      }
    }

    @Override
    public void endVisit(JsniMethodRef x, Context ctx) {
      // Redirect JSNI refs to uninstantiable types to the null method.
      if (isPruned(x.getTarget())) {
        String ident = x.getIdent();
        JMethod nullMethod = program.getNullMethod();
        JsniMethodRef nullMethodRef =
            new JsniMethodRef(x.getSourceInfo(), ident, nullMethod, program.getJavaScriptObject());
        ctx.replaceMe(nullMethodRef);
      }
    }

    @Override
    public void exit(JVariable x, Context ctx) {
      JType type = x.getType();
      if (type instanceof JReferenceType
          && !program.typeOracle.isInstantiatedType((JReferenceType) type)) {
        x.setType(JReferenceType.NULL_TYPE);
        madeChanges();
      }
    }

    @Override
    public boolean visit(JBinaryOperation x, Context ctx) {
      if (x.getOp() == JBinaryOperator.ASG) {
        lValues.push(x.getLhs());
      }
      return true;
    }

    @Override
    public boolean visit(JDeclarationStatement x, Context ctx) {
      super.visit(x, ctx);
      lValues.push(x.getVariableRef());
      return true;
    }

    private <T extends HasEnclosingType & CanBeStatic> boolean isPruned(T node) {
      if (!referencedNonTypes.contains(node)) {
        return true;
      }
      JReferenceType enclosingType = node.getEnclosingType();
      return !node.isStatic()
          && enclosingType != null
          && !program.typeOracle.isInstantiatedType(enclosingType);
    }

    private boolean isVariablePruned(JVariable variable) {
      if (variable instanceof JField) {
        return isPruned((JField) variable);
      }
      return !referencedNonTypes.contains(variable);
    }

    private JExpression makeReplacementForAssignment(
        SourceInfo info, JVariableRef variableRef, JExpression rhs) {
      // Replace with a multi, which may wind up empty.
      JMultiExpression multi = new JMultiExpression(info);

      // If the lhs is a field ref, evaluate it first.
      if (variableRef instanceof JFieldRef) {
        JFieldRef fieldRef = (JFieldRef) variableRef;
        JExpression instance = fieldRef.getInstance();
        if (instance != null) {
          multi.addExpressions(instance);
        }
      }

      // If there is an rhs, evaluate it second.
      if (rhs != null) {
        multi.addExpressions(rhs);
      }
      if (multi.getNumberOfExpressions() == 1) {
        return multi.getExpression(0);
      } else {
        return multi;
      }
    }

    // Arguments for pruned parameters will be pushed right into a multiexpression that will be
    // evaluated with the next arg, e.g. m(arg1, (prunnedArg2, prunnedArg3, arg4)).
    private void maybeReplaceForPrunedParameters(JMethodCall x, Context ctx) {
      if (!priorParametersByMethod.containsKey(x.getTarget())) {
        // No parameter was pruned.
        return;
      }

      JMethodCall replacementCall = x.cloneWithoutParameters();

      assert !x.getTarget().canBePolymorphic();
      List<JParameter> originalParams = priorParametersByMethod.get(x.getTarget());

      // The method and the call agree in the number of parameters.
      assert originalParams.size() == x.getArgs().size();

      // Traverse the call arguments left to right.
      SourceInfo sourceInfo = x.getSourceInfo();
      JMultiExpression unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo);
      List<JExpression> args = x.getArgs();
      for (int currentArgumentIndex = 0;
          currentArgumentIndex < args.size();
          ++currentArgumentIndex) {
        JExpression arg = args.get(currentArgumentIndex);

        // If the parameter was not pruned .
        if (referencedNonTypes.contains(originalParams.get(currentArgumentIndex))) {
          // Add the current argument to the list of unevaluated arguments and pass the multi
          // expression to the call.
          unevaluatedArgumentsForPrunedParameters.addExpressions(arg);
          replacementCall.addArg(unevaluatedArgumentsForPrunedParameters);
          // Reset the accumulating multi expression.
          unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo);
        } else if (arg.hasSideEffects()) {
          // If the argument was pruned and has sideffects accumulate it; otherwise discard.
          unevaluatedArgumentsForPrunedParameters.addExpressions(arg);
        }
      }

      if (unevaluatedArgumentsForPrunedParameters.isEmpty()) {
        // We are done, all (side effectful) parameters have been evaluated.
        ctx.replaceMe(replacementCall);
        return;
      }

      // If the last few parameters where pruned, we need to evaluate the (side effectful) arguments
      // for those parameters.
      if (replacementCall.getArgs().isEmpty()) {
        // All parameters have been pruned, replace by (prunedArg1, ..., prunedArgn, m()).
        unevaluatedArgumentsForPrunedParameters.addExpressions(replacementCall);
        ctx.replaceMe(unevaluatedArgumentsForPrunedParameters);
        return;
      }
      // Some parameters have been pruned from the end, replace by
      // m(arg1,..., (lastArg = lastUnprunedArg, remainingArgs, lastArg))
      JExpression lastArg = Iterables.getLast(replacementCall.getArgs());
      JLocal tempVar =
          createTempLocal(
              sourceInfo,
              Iterables.getLast(Iterables.filter(originalParams, Predicates.in(referencedNonTypes)))
                  .getType());
      unevaluatedArgumentsForPrunedParameters.addExpressions(
          0,
          JProgram.createAssignment(
              lastArg.getSourceInfo(), new JLocalRef(sourceInfo, tempVar), lastArg));
      unevaluatedArgumentsForPrunedParameters.addExpressions(new JLocalRef(sourceInfo, tempVar));
      replacementCall.setArg(
          replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters);
      ctx.replaceMe(replacementCall);
    }

    @Override
    protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
      // The name can be reused a later pass will make sure each instance of JLocal in a method
      // has a different name.
      return "lastArg";
    }
  }

  /**
   * Remove any unreferenced classes and interfaces from JProgram. Remove any unreferenced methods
   * and fields from their containing classes.
   */
  private class PruneVisitor extends JChangeTrackingVisitor {
    private final ListMultimap<JMethod, JParameter> priorParametersByMethod =
        ArrayListMultimap.create();
    private final Set<? extends JNode> referencedNonTypes;
    private final Set<? extends JReferenceType> referencedTypes;

    public PruneVisitor(
        Set<? extends JReferenceType> referencedTypes,
        Set<? extends JNode> referencedNodes,
        OptimizerContext optimizerCtx) {
      super(optimizerCtx);
      this.referencedTypes = referencedTypes;
      this.referencedNonTypes = referencedNodes;
    }

    public ListMultimap<JMethod, JParameter> getPriorParametersByMethod() {
      return priorParametersByMethod;
    }

    @Override
    public boolean visit(JDeclaredType type, Context ctx) {
      assert referencedTypes.contains(type);
      Predicate<JNode> notReferenced = Predicates.not(Predicates.in(referencedNonTypes));
      removeFields(notReferenced, type);
      removeMethods(notReferenced, type);

      for (JMethod method : type.getMethods()) {
        accept(method);
      }

      return false;
    }

    @Override
    public boolean enter(JMethod x, Context ctx) {
      if (!x.canBePolymorphic()) {
        /*
         * Don't prune parameters on unreferenced methods. The methods might not
         * be reachable through the current method traversal routines, but might
         * be used or checked elsewhere.
         *
         * Basically, if we never actually checked if the method parameters were
         * used or not, don't prune them. Doing so would leave a number of
         * dangling JParameterRefs that blow up in later optimizations.
         */
        if (!referencedNonTypes.contains(x)) {
          return true;
        }

        /*
         * We cannot prune parameters from staticImpls that still have a live
         * instance method, because doing so would screw up any subsequent
         * devirtualizations. If the instance method has been pruned, then it's
         * okay. Also, it's okay on the final pass (saveCodeTypes == false)
         * since no more devirtualizations will occur.
         *
         * TODO: prune params; MakeCallsStatic smarter to account for it.
         */
        JMethod instanceMethod = program.instanceMethodForStaticImpl(x);
        // Unless the instance method has already been pruned, of course.
        if (saveCodeGenTypes
            && instanceMethod != null
            && referencedNonTypes.contains(instanceMethod)) {
          // instance method is still live
          return true;
        }

        priorParametersByMethod.putAll(x, x.getParams());

        for (int i = 0; i < x.getParams().size(); ++i) {
          JParameter param = x.getParams().get(i);
          if (!referencedNonTypes.contains(param)) {
            x.removeParam(i);
            madeChanges();
            --i;
          }
        }
      }

      return true;
    }

    @Override
    public boolean visit(JMethodBody x, Context ctx) {
      for (int i = 0; i < x.getLocals().size(); ++i) {
        if (!referencedNonTypes.contains(x.getLocals().get(i))) {
          x.removeLocal(i--);
          madeChanges();
        }
      }
      return false;
    }

    @Override
    public boolean visit(JProgram program, Context ctx) {
      for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext(); ) {
        JDeclaredType type = it.next();
        if (referencedTypes.contains(type)) {
          accept(type);
        } else {
          prunedMethods.addAll(type.getMethods());
          methodsWereRemoved(type.getMethods());
          fieldsWereRemoved(type.getFields());
          it.remove();
          madeChanges();
        }
      }
      return false;
    }

    private void removeFields(Predicate<JNode> shouldRemove, JDeclaredType type) {
      for (int i = 0; i < type.getFields().size(); ++i) {
        JField field = type.getFields().get(i);
        if (!shouldRemove.apply(field)) {
          continue;
        }
        wasRemoved(field);
        type.removeField(i);
        madeChanges();
        --i;
      }
    }

    private void removeMethods(Predicate<JNode> shouldRemove, JDeclaredType type) {
      // Skip method 0 which is clinit and is assumed to exist.
      assert type.getMethods().get(0) == type.getClinitMethod();
      for (int i = 1; i < type.getMethods().size(); ++i) {
        JMethod method = type.getMethods().get(i);
        if (!shouldRemove.apply(method)) {
          continue;
        }
        prunedMethods.add(method);
        wasRemoved(method);
        type.removeMethod(i);
        program.removeStaticImplMapping(method);
        madeChanges();
        --i;
      }
    }
  }

  private static final String NAME = Pruner.class.getSimpleName();

  public static OptimizerStats exec(
      JProgram program, boolean noSpecialTypes, OptimizerContext optimizerCtx) {
    Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME);
    OptimizerStats stats = new Pruner(program, noSpecialTypes).execImpl(optimizerCtx);
    optimizeEvent.end("didChange", "" + stats.didChange());
    return stats;
  }

  public static OptimizerStats exec(JProgram program, boolean noSpecialTypes) {
    return exec(program, noSpecialTypes, OptimizerContext.NULL_OPTIMIZATION_CONTEXT);
  }

  /**
   * Transform a reference to a pruned instance field into a reference to the null field, which will
   * be used to replace <code>x</code>.
   */
  public static JFieldRef transformToNullFieldRef(JFieldRef x, JProgram program) {
    JExpression instance = x.getInstance();

    /*
     * We assert that field must be non-static if it's an rvalue, otherwise it
     * would have been rescued.
     */
    // assert !x.getField().isStatic();
    /*
     * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with
     * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to
     * never be rescured. This in turn causes invalid references to static
     * methods, which violates otherwise good assumptions about compiler
     * operation.
     *
     * TODO: Remove this when ControlFlowAnalyzer doesn't special-case
     * CLH.clinit().
     */
    if (x.getField().isStatic() && instance == null) {
      instance = program.getLiteralNull();
    }

    assert instance != null;
    if (!instance.hasSideEffects()) {
      instance = program.getLiteralNull();
    }

    JFieldRef fieldRef =
        new JFieldRef(
            x.getSourceInfo(),
            instance,
            program.getNullField(),
            x.getEnclosingType(),
            primitiveTypeOrNullTypeOrArray(program, x.getType()));
    return fieldRef;
  }

  /**
   * Transform a call to a pruned instance method (or static impl) into a call to the null method,
   * which will be used to replace <code>x</code>.
   */
  public static JMethodCall transformToNullMethodCall(JMethodCall x, JProgram program) {
    JExpression instance = x.getInstance();
    List<JExpression> args = x.getArgs();
    if (program.isStaticImpl(x.getTarget())) {
      instance = args.get(0);
      args = args.subList(1, args.size());
    } else {
      /*
       * We assert that method must be non-static, otherwise it would have been
       * rescued.
       */
      // assert !x.getTarget().isStatic();
      /*
       * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with
       * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to
       * never be rescured. This in turn causes invalid references to static
       * methods, which violates otherwise good assumptions about compiler
       * operation.
       *
       * TODO: Remove this when ControlFlowAnalyzer doesn't special-case
       * CLH.clinit().
       */
      if (x.getTarget().isStatic() && instance == null) {
        instance = program.getLiteralNull();
      }
    }
    assert (instance != null);
    if (!instance.hasSideEffects()) {
      instance = program.getLiteralNull();
    }

    JMethodCall newCall =
        new JMethodCall(
            x.getSourceInfo(),
            instance,
            program.getNullMethod(),
            primitiveTypeOrNullTypeOrArray(program, x.getType()));
    // Retain the original arguments, they will be evaluated for side effects.
    for (JExpression arg : args) {
      if (arg.hasSideEffects()) {
        newCall.addArg(arg);
      }
    }
    return newCall;
  }

  /** Return the smallest type that is is a subtype of the argument. */
  static JType primitiveTypeOrNullTypeOrArray(JProgram program, JType type) {
    if (type instanceof JArrayType) {
      JType leafType = primitiveTypeOrNullTypeOrArray(program, ((JArrayType) type).getLeafType());
      return program.getOrCreateArrayType(leafType, ((JArrayType) type).getDims());
    }
    if (type instanceof JPrimitiveType) {
      return type;
    }
    return JReferenceType.NULL_TYPE;
  }

  private final JProgram program;

  private final boolean saveCodeGenTypes;

  private final Set<JMethod> prunedMethods = Sets.newLinkedHashSet();

  private Pruner(JProgram program, boolean saveCodeGenTypes) {
    this.program = program;
    this.saveCodeGenTypes = saveCodeGenTypes;
  }

  private OptimizerStats execImpl(OptimizerContext optimizerCtx) {
    OptimizerStats stats = new OptimizerStats(NAME);

    ControlFlowAnalyzer livenessAnalyzer = new ControlFlowAnalyzer(program);
    livenessAnalyzer.setForPruning();

    // SPECIAL: Immortal codegen types are never pruned
    traverseTypes(livenessAnalyzer, program.immortalCodeGenTypes);

    if (saveCodeGenTypes) {
      /*
       * SPECIAL: Some classes contain methods used by code generation later.
       * Unless those transforms have already been performed, we must rescue all
       * contained methods for later user.
       */
      traverseTypes(livenessAnalyzer, program.codeGenTypes);
    }
    livenessAnalyzer.traverseEverything();

    program.typeOracle.setInstantiatedTypes(livenessAnalyzer.getInstantiatedTypes());

    PruneVisitor pruner =
        new PruneVisitor(
            livenessAnalyzer.getReferencedTypes(),
            livenessAnalyzer.getLiveFieldsAndMethods(),
            optimizerCtx);
    pruner.accept(program);
    stats.recordModified(pruner.getNumMods());

    if (!pruner.didChange()) {
      return stats;
    }
    CleanupRefsVisitor cleaner =
        new CleanupRefsVisitor(
            livenessAnalyzer.getLiveFieldsAndMethods(),
            pruner.getPriorParametersByMethod(),
            optimizerCtx);
    cleaner.accept(program.getDeclaredTypes());
    optimizerCtx.incOptimizationStep();
    optimizerCtx.syncDeletedSubCallGraphsSince(
        optimizerCtx.getLastStepFor(NAME) + 1, prunedMethods);
    JavaAstVerifier.assertProgramIsConsistent(program);
    return stats;
  }

  /** Traverse from all methods starting from a set of types. */
  private void traverseTypes(ControlFlowAnalyzer livenessAnalyzer, List<JClassType> types) {
    for (JClassType type : types) {
      livenessAnalyzer.traverseFromReferenceTo(type);
      for (JMethod method : type.getMethods()) {
        if (method instanceof JConstructor) {
          livenessAnalyzer.traverseFromInstantiationOf(type);
        }
        livenessAnalyzer.traverseFrom(method);
      }
    }
  }
}