/** * 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); } }))); }
/** * 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 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; }
/** * 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; }
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()); }
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()); }
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")); }
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; }
/** 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; } } }
/** 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); } }
/** 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(); } }
/** 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; } }
/** 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()); } } } }
/** 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(); } }
/** 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; } }
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; } }
/** * 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); } } } }