Enum Ordinalization Optimization (revised) Review at http://gwt-code-reviews.appspot.com/1133801 Review by: cromwellian@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9273 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index 7917969..e795adb 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -65,6 +65,7 @@ import com.google.gwt.dev.jjs.impl.ArrayNormalizer; import com.google.gwt.dev.jjs.impl.AssertionNormalizer; import com.google.gwt.dev.jjs.impl.AssertionRemover; +import com.google.gwt.dev.jjs.impl.AstDumper; import com.google.gwt.dev.jjs.impl.BuildTypeMap; import com.google.gwt.dev.jjs.impl.CastNormalizer; import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer; @@ -72,6 +73,7 @@ import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder; import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer; import com.google.gwt.dev.jjs.impl.DeadCodeElimination; +import com.google.gwt.dev.jjs.impl.EnumOrdinalizer; import com.google.gwt.dev.jjs.impl.EqualityNormalizer; import com.google.gwt.dev.jjs.impl.Finalizer; import com.google.gwt.dev.jjs.impl.FixAssignmentToUnbox; @@ -98,7 +100,6 @@ import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs; import com.google.gwt.dev.jjs.impl.ResolveRebinds; import com.google.gwt.dev.jjs.impl.SameParameterValueOptimizer; -import com.google.gwt.dev.jjs.impl.SourceGenerationVisitor; import com.google.gwt.dev.jjs.impl.TypeLinker; import com.google.gwt.dev.jjs.impl.TypeMap; import com.google.gwt.dev.jjs.impl.TypeTightener; @@ -124,11 +125,9 @@ import com.google.gwt.dev.js.ast.JsBlock; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsProgram; -import com.google.gwt.dev.util.AbstractTextOutput; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.dev.util.Empty; import com.google.gwt.dev.util.Memory; -import com.google.gwt.dev.util.TextOutput; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.arg.OptionOptimize; import com.google.gwt.dev.util.collect.Lists; @@ -146,10 +145,8 @@ import org.xml.sax.SAXException; import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Collection; @@ -574,8 +571,8 @@ allTypeDeclarations = null; Memory.maybeDumpMemory("AstOnly"); - maybeDumpAST(jprogram); - + AstDumper.maybeDumpAST(jprogram); + // See if we should run the EnumNameObfuscator if (module != null) { ConfigurationProperty enumNameObfuscationProp = (ConfigurationProperty) module.getProperties().find( @@ -621,7 +618,7 @@ /* * 4) Possibly optimize some. * - * Don't optimizing early if this is a draft compile, or if there's only + * Don't optimize early if this is a draft compile, or if there's only * one permutation. */ if (options.getOptimizationLevel() > OptionOptimize.OPTIMIZE_LEVEL_DRAFT @@ -708,7 +705,7 @@ optimizeEvent.end(); throw new InterruptedException(); } - maybeDumpAST(jprogram); + AstDumper.maybeDumpAST(jprogram); OptimizerStats stats = optimizeLoop("Pass " + counter, jprogram, options.isAggressivelyOptimize()); allOptimizerStats.add(stats); @@ -825,6 +822,13 @@ // remove same parameters value stats.add(SameParameterValueOptimizer.exec(jprogram).recordVisits( numNodes)); + + /* + * enum ordinalization + * TODO(jbrosenberg): graduate this out of the 'isAggressivelyOptimize' + * block, over time. + */ + stats.add(EnumOrdinalizer.exec(jprogram).recordVisits(numNodes)); } // prove that any types that have been culled from the main tree are @@ -1278,28 +1282,6 @@ return result; } - private static void maybeDumpAST(JProgram jprogram) { - String dumpFile = System.getProperty("gwt.jjs.dumpAst"); - if (dumpFile != null) { - try { - FileOutputStream os = new FileOutputStream(dumpFile, true); - final PrintWriter pw = new PrintWriter(os); - TextOutput out = new AbstractTextOutput(false) { - { - setPrintWriter(pw); - } - }; - SourceGenerationVisitor v = new SourceGenerationVisitor(out); - v.accept(jprogram); - pw.flush(); - pw.close(); - } catch (IOException e) { - System.out.println("Could not dump AST"); - e.printStackTrace(); - } - } - } - /** * Open an emitted artifact and gunzip its contents. */
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java index a5eac08..9c0f5b5 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -77,6 +77,7 @@ "java.util.Iterator", "java.lang.AssertionError", "java.lang.Boolean", "java.lang.Byte", "java.lang.Character", "java.lang.Short", "java.lang.Integer", "java.lang.Float", "java.lang.Double", + "java.lang.Throwable", "com.google.gwt.core.client.GWT", "com.google.gwt.core.client.JavaScriptObject", "com.google.gwt.lang.ClassLiteralHolder", @@ -780,6 +781,10 @@ return returnMap; } + public String getClassLiteralName(JType type) { + return type.getJavahSignatureName() + "_classLit"; + } + public CorrelationFactory getCorrelator() { return correlator; } @@ -875,8 +880,8 @@ JClassLiteral.computeClassObjectAllocation(this,info, type); // Create a field in the class literal holder to hold the object. - JField field = new JField(info, type.getJavahSignatureName() - + "_classLit", typeSpecialClassLiteralHolder, getTypeJavaLangClass(), + JField field = new JField(info, getClassLiteralName(type), + typeSpecialClassLiteralHolder, getTypeJavaLangClass(), true, Disposition.FINAL); typeSpecialClassLiteralHolder.addField(field);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/AstDumper.java b/dev/core/src/com/google/gwt/dev/jjs/impl/AstDumper.java new file mode 100644 index 0000000..9897530 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/AstDumper.java
@@ -0,0 +1,104 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.util.AbstractTextOutput; +import com.google.gwt.dev.util.TextOutput; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * A simple utility to dump a JProgram to a temp file, which can be called + * sequentially during a compilation/optimization run, so intermediate steps + * can be compared. + * + * It uses the system property "gwt.jjs.dumpAst" to determine the name + * (or prefix) of the file to dump the AST to. + * + * TODO(jbrosenberg): Add proper logging and/or exception handling for the + * potential IOException that might occur when writing the file. + */ +public class AstDumper { + + private static int autoVersionNumber = 0; + + /** + * Appends a new version of the AST at the end of the file, each time it's called. + */ + public static void maybeDumpAST(JProgram jprogram) { + maybeDumpAST(jprogram, null, true); + } + + /** + * Writes the AST to the file with a versioned extension, using an + * auto-incrementing version number (starting from 1), each time it's called. + * Any previous contents of the file written to will be overwritten. + */ + public static void maybeDumpAST(JProgram jprogram, boolean autoIncrementVersion) { + if (!autoIncrementVersion) { + maybeDumpAST(jprogram); + } else { + maybeDumpAST(jprogram, autoVersionNumber++); + } + } + + /** + * Writes the AST to the file with the provided version number extension. + * Any previous contents of the file written to will be overwritten. + */ + public static void maybeDumpAST(JProgram jprogram, int versionNumber) { + String fileExtension = "." + versionNumber; + maybeDumpAST(jprogram, fileExtension, false); + } + + /** + * Writes the AST to the file with the provided version string extension. + * Any previous contents of the file written to will be overwritten. + */ + public static void maybeDumpAST(JProgram jprogram, String versionString) { + String fileExtension = "." + versionString; + maybeDumpAST(jprogram, fileExtension, false); + } + + private static void maybeDumpAST(JProgram jprogram, + String fileExtension, boolean append) { + String dumpFile = System.getProperty("gwt.jjs.dumpAst"); + if (dumpFile != null) { + if (fileExtension != null) { + dumpFile += fileExtension; + } + try { + FileOutputStream os = new FileOutputStream(dumpFile, append); + final PrintWriter pw = new PrintWriter(os); + TextOutput out = new AbstractTextOutput(false) { + { + setPrintWriter(pw); + } + }; + SourceGenerationVisitor v = new SourceGenerationVisitor(out); + v.accept(jprogram); + pw.close(); + } catch (IOException e) { + System.out.println("Could not dump AST"); + e.printStackTrace(); + } + } + } +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/EnumOrdinalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/EnumOrdinalizer.java new file mode 100644 index 0000000..715786e --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/EnumOrdinalizer.java
@@ -0,0 +1,1000 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.dev.jjs.ast.Context; +import com.google.gwt.dev.jjs.ast.JArrayType; +import com.google.gwt.dev.jjs.ast.JCastOperation; +import com.google.gwt.dev.jjs.ast.JClassLiteral; +import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JDeclarationStatement; +import com.google.gwt.dev.jjs.ast.JEnumField; +import com.google.gwt.dev.jjs.ast.JEnumType; +import com.google.gwt.dev.jjs.ast.JExpression; +import com.google.gwt.dev.jjs.ast.JField; +import com.google.gwt.dev.jjs.ast.JFieldRef; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JMethodBody; +import com.google.gwt.dev.jjs.ast.JMethodCall; +import com.google.gwt.dev.jjs.ast.JModVisitor; +import com.google.gwt.dev.jjs.ast.JNode; +import com.google.gwt.dev.jjs.ast.JPrimitiveType; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JReferenceType; +import com.google.gwt.dev.jjs.ast.JStatement; +import com.google.gwt.dev.jjs.ast.JType; +import com.google.gwt.dev.jjs.ast.JVariableRef; +import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; +import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; +import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; +import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; +import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.TreeSet; + +/** + * This optimizer replaces enum constants with their ordinal value (a simple + * int) when possible. We call this process "ordinalization". + * + * Generally, this can be done for enums that are only ever referred to by + * reference, or by their ordinal value. For the specific set of conditions + * under which ordinalization can proceed, see the notes for the nested class + * {@link EnumOrdinalizer.CannotBeOrdinalAnalyzer} below. + * + * This optimizer modifies enum classes to change their field constants to ints, + * and to remove initialization of those constants in the clinit method. An + * ordinalized enum class will not be removed from the AST by this optimizer, + * but as long as all references to it are replaced (which is one of the + * requirements for ordinalization), then the enum class itself will be pruned + * by subsequent optimizer passes. + * + * Regardless of whether an ordinalized enum class ends up being completely + * pruned away, the AST is expected to be in a coherent and usable state after + * any pass of this optimizer. Thus, this optimizer should be considered to be + * stateless. + * + * The process is broken up into 3 separate passes over the AST, each + * implemented as a separate visitor class. The first visitor, + * {@link EnumOrdinalizer.CannotBeOrdinalAnalyzer} compiles information + * about each enum class in the AST, and looks for reasons not to ordinalize + * each enum. Thus, it prepares a "black-list" of enum classes that cannot be + * ordinalized (and it follows that all enums that don't get entered in the + * black-list, will be allowed to be ordinalized. The set of conditions which + * cause an enum to be black-listed are outlined below. + * + * If there are enum classes that didn't get black-listed remaining, the + * subsequent passes of the optimizer will be invoked. The first, + * {@link EnumOrdinalizer.ReplaceEnumTypesWithInteger}, replaces the type info + * for each field or expression involved with a target enum constant with an + * integer. The final visitor, descriptively named + * {@link EnumOrdinalizer.ReplaceOrdinalFieldAndMethodRefsWithOrdinal}, will do + * the job of replacing the value for references to the Enum ordinal field or + * method of an enum constant that has been ordinalized. + */ +public class EnumOrdinalizer { + public static String NAME = EnumOrdinalizer.class.getSimpleName(); + public static Tracker tracker = null; + + public static OptimizerStats exec(JProgram program) { + Event optimizeEvent = SpeedTracerLogger.start( + CompilerEventType.OPTIMIZE, "optimizer", NAME); + OptimizerStats stats = new EnumOrdinalizer(program).execImpl(); + optimizeEvent.end("didChange", "" + stats.didChange()); + return stats; + } + + public static Tracker getTracker() { + return tracker; + } + + public static void startTracker() { + tracker = new Tracker(); + } + + public static void stopTracker() { + tracker = null; + } + + private final JProgram program; + private final JType classLiteralHolderType; + private final JType nullType; + private final JType javaScriptObjectType; + private final JType enumType; + private final JField enumOrdinalField; + private final JMethod classCreateForEnumMethod; + private final JMethod enumCreateValueOfMapMethod; + private final JMethod enumSuperConstructor; + private final JMethod enumOrdinalMethod; + private final Set<JEnumType> ordinalizationBlackList = new HashSet<JEnumType>(); + private final Map<String, JEnumType> enumsVisited = new HashMap<String, JEnumType>(); + + public EnumOrdinalizer(JProgram program) { + this.program = program; + this.classLiteralHolderType = program.getIndexedType("ClassLiteralHolder"); + this.nullType = program.getTypeNull(); + this.javaScriptObjectType = program.getJavaScriptObject(); + this.enumType = program.getIndexedType("Enum"); + this.enumOrdinalField = program.getIndexedField("Enum.ordinal"); + this.classCreateForEnumMethod = program.getIndexedMethod("Class.createForEnum"); + this.enumCreateValueOfMapMethod = program.getIndexedMethod("Enum.createValueOfMap"); + this.enumOrdinalMethod = program.getIndexedMethod("Enum.ordinal"); + this.enumSuperConstructor = program.getIndexedMethod("Enum.Enum"); + } + + /** + * A visitor which keeps track of the enums which cannot be ordinalized. It + * does this by keeping track of a "black-list" for ordinals which violate the + * conditions for ordinalization, below. + * + * An enum cannot be ordinalized, if it: + * is implicitly upcast. + * is implicitly cast to from a nullType. + * is implicitly cast to or from a javaScriptObject type. + * is explicitly cast to another type (or vice-versa). + * it's class literal is used explicitly. + * it has an artificial rescue recorded for it. + * has any field referenced, except for: + * one of it's enum constants + * Enum.ordinal + * has any method called, except for: + * ordinal() + * Enum.ordinal() + * Enum() super constructor + * Enum.createValueOfMap() + * + * This visitor extends the ImplicitUpcastAnalyzer, which encapsulates all + * the conditions where implicit upcasting can occur in an AST. The rest of + * the logic for checking ordinalizability is encapsulated in this sub-class. + * + * It also keeps track of all enums encountered, so we can know if we need to + * continue with the other visitors of the optimizer after this visitor runs. + * + * We make special allowances not to check any code statements that appear + * within the ClassLiteralHolder class, which can contain a reference to all + * enum class literals in the program, even after ordinalization occurs. + * + * Also, we ignore visiting the getClass() method for any enum subclass, since + * it will also cause a visit to the enum's class literal, and we don't + * necessarily want to prevent ordinalization in that case. + * + * Special checking is needed to detect a class literal reference that occurs + * within a JSNI method body. We don't get a visit to JClassLiteral in that + * case, so we need to inspect visits to JsniFieldRef for the possibility it + * might be a reference to a class literal. + * + * We also skip any checking in a method call to Enum.createValueOfMap(), + * since this is generated for any enum class initially within the extra + * enumClass$Map class, and this call contains an implicit upcast in the + * method call args (as well as a reference to the static enumClass$VALUES + * field), which we want to ignore. The enumClass$Map class will not get + * pruned as long as the enumClass is not ordinalized, and so we need to + * ignore it's presence in the consideration for whether an enum class is + * ordinalizable. + */ + private class CannotBeOrdinalAnalyzer extends ImplicitUpcastAnalyzer { + + private final Set<String> jsniClassLiteralsVisited = new HashSet<String>(); + private final Stack<JCastOperation> castOpsToIgnore = + new Stack<JCastOperation>(); + + public CannotBeOrdinalAnalyzer(JProgram program) { + super(program); + } + + /* + * After program is visited, post-process remaining tasks from accumulated data. + */ + public void afterVisitor() { + // black-list any Jsni enum ClassLiteralsVisited + for (String classLiteralName : jsniClassLiteralsVisited) { + JEnumType enumFromLiteral = enumsVisited.get(classLiteralName); + if (enumFromLiteral != null) { + addToBlackList(enumFromLiteral); + } + } + } + + @Override + public void endVisit(JCastOperation x, Context ctx) { + // see if we've previously marked this castOp to be exempted + if (!castOpsToIgnore.empty() && + castOpsToIgnore.peek() == x) { + castOpsToIgnore.pop(); + return; + } + + // check for explicit cast (check both directions) + blackListIfEnumCast(x.getExpr().getType(), x.getCastType()); + blackListIfEnumCast(x.getCastType(), x.getExpr().getType()); + } + + @Override + public void endVisit(JClassLiteral x, Context ctx) { + /* + * Check for references to an enum's class literal. We need to + * black-list classes in this case, since there could be a call + * to Enum.valueOf(someEnum.class,"name"), etc. + * + * Note: we won't get here for class literals that occur in the + * ClassLiteralHolder class, or within the getClass method of an + * enum class (see comments above). + */ + JEnumType type = getEnumType(x.getRefType()); + if (type != null) { + blackListIfEnum(type); + } + } + + @Override + public void endVisit(JClassType x, Context ctx) { + // black-list any artificially rescued classes recorded for this class + List<JNode> rescues = x.getArtificialRescues(); + if (rescues != null && rescues.size() > 0) { + for (JNode rescueNode : rescues) { + if (rescueNode instanceof JType) { + blackListIfEnum((JType) rescueNode); + } + } + } + + // keep track of all enum classes visited + JEnumType maybeEnum = x.isEnumOrSubclass(); + if (maybeEnum != null) { + enumsVisited.put(program.getClassLiteralName(maybeEnum), maybeEnum); + } + } + + @Override + public void endVisit(JFieldRef x, Context ctx) { + // don't need to check Enum.ordinal + if (x.getField() == enumOrdinalField) { + return; + } + + if (x.getInstance() != null) { + // check any instance field reference other than ordinal + blackListIfEnumExpression(x.getInstance()); + } else if (x.getField().isStatic()) { + // check static field references + + /* + * Need to exempt static fieldRefs to the special $VALUES array that + * gets generated for all enum classes, if the reference occurs + * within the enum class itself (such as happens in the clinit() or + * values() method for all enums). + */ + if (x.getField().getName().equals("$VALUES") && + this.currentMethod.getEnclosingType() == + x.getField().getEnclosingType()) { + if (getEnumType(x.getField().getEnclosingType()) != null) { + return; + } + } + + /* + * Need to exempt static fieldRefs for enum constants themselves. + * Detect these as final fields, that have the same enum type as + * their enclosing type. + */ + if (x.getField().isFinal() && + (x.getField().getEnclosingType() == + getEnumType(x.getField().getType()))) { + return; + } + + /* + * Check any other refs to static fields of an enum class. This + * includes references to $VALUES that might occur outside of the enum + * class itself. This can occur when a call to the values() method gets + * inlined, etc. Also check here for any user defined static fields. + */ + blackListIfEnum(x.getField().getEnclosingType()); + } + } + + @Override + public void endVisit(JsniFieldRef x, Context ctx) { + /* + * Can't do the same thing as for JFieldRef, + * all JsniFieldRefs are cast to JavaScriptObjects. + * Need to check both the field type and the type of the instance + * or enclosing class referencing the field. + */ + + // check the field type + blackListIfEnum(x.getField().getType()); + + // check the referrer + if (x.getInstance() != null) { + blackListIfEnumExpression(x.getInstance()); + } else { + blackListIfEnum(x.getField().getEnclosingType()); + } + + /* + * need to also check JsniFieldRef's for a possible reference to a + * class literal, since we don't get a visit to JClassLiteral when + * it occurs within Jsni (shouldn't it?). + */ + if (x.getField().getEnclosingType() == classLiteralHolderType) { + // see if it has an initializer with a method call to "createForEnum" + JExpression initializer = x.getField().getInitializer(); + if (initializer instanceof JMethodCall) { + if (((JMethodCall) initializer).getTarget() == classCreateForEnumMethod) { + jsniClassLiteralsVisited.add(x.getField().getName()); + } + } + } + } + + @Override + public void endVisit(JMethodCall x, Context ctx) { + // exempt calls to certain methods on the Enum super class + if (x.getTarget() == enumCreateValueOfMapMethod || + x.getTarget() == enumSuperConstructor || + x.getTarget() == enumOrdinalMethod) { + return; + } + + // any other method on an enum class should cause it to be black-listed + if (x.getInstance() != null) { + blackListIfEnumExpression(x.getInstance()); + } else if (x.getTarget().isStatic()) { + /* + * need to exempt static methodCalls for an enum class if it occurs + * within the enum class itself (such as in $clinit() or values()) + */ + if (this.currentMethod.getEnclosingType() != + x.getTarget().getEnclosingType()) { + blackListIfEnum(x.getTarget().getEnclosingType()); + } + } + + // defer to ImplicitUpcastAnalyzer to check method call args & params + super.endVisit(x, ctx); + } + + @Override + public void endVisit(JsniMethodRef x, Context ctx) { + // no enum methods are exempted if occur within a JsniMethodRef + if (x.getInstance() != null) { + blackListIfEnumExpression(x.getInstance()); + } else if (x.getTarget().isStatic()) { + /* + * need to exempt static methodCalls for an enum class if it occurs + * within the enum class itself (such as in $clinit() or values()) + */ + if (this.currentMethod.getEnclosingType() != + x.getTarget().getEnclosingType()) { + blackListIfEnum(x.getTarget().getEnclosingType()); + } + } + + // defer to ImplicitUpcastAnalyzer to check method call args & params + super.endVisit(x, ctx); + } + + @Override + public boolean visit(JClassType x, Context ctx) { + /* + * Don't want to visit the large ClassLiteralHolder class, it doesn't + * contain references to actual usage of enum class literals. It's also + * a time savings to not traverse this class. + */ + if (x == classLiteralHolderType) { + return false; + } + return true; + } + + @Override + public boolean visit(JFieldRef x, Context ctx) { + /* + * If we have a field ref of Enum.ordinal, then we want to allow a + * cast operation from enum subclass to Enum on the instance. Other + * optimizers have a tendency to convert things like: + * + * 'switch(enumObj)' to + * 'switch((Enum)enumObj).ordinal' + * + * We don't want to blacklist enumObj in that case, so we push this castOp + * on a stack and check it in the subsequent call to endVisit for + * JCastOperation. We can't simply return false and prevent + * the visit of the JCastOperation altogether, since we do need to visit + * the JCastOperation's sub-expression. Since the sub-expression could + * potentially also contain similar cast operations, we use a stack to + * keep track of 'castOpsToIgnore'. + */ + if (x.getField() == enumOrdinalField) { + if (x.getInstance() != null && + x.getInstance() instanceof JCastOperation) { + JCastOperation castOp = (JCastOperation) x.getInstance(); + if (getPossiblyUnderlyingType(castOp.getCastType()) == enumType) { + JEnumType fromType = getEnumType(castOp.getExpr().getType()); + if (fromType != null) { + castOpsToIgnore.push(castOp); + } + } + } + } + return true; + } + + @Override + public boolean visit(JMethod x, Context ctx) { + /* + * Don't want to visit the generated getClass() method on an enum, since + * it contains a reference to an enum's class literal that we don't want + * to consider. Make sure this is not a user overloaded version + * (i.e. check that it has no params). + */ + if (getEnumType(x.getEnclosingType()) != null && + x.getName().equals("getClass") && + (x.getOriginalParamTypes() == null || x.getOriginalParamTypes().size() == 0)) { + return false; + } + + // defer to parent method on ImplicitCastAnalyzer + return super.visit(x, ctx); + } + + @Override + public boolean visit(JMethodCall x, Context ctx) { + /* + * skip all calls to Enum.createValueOfMap, since they'd get falsely + * flagged for referencing $VALUES and for implicitly upcasting an + * array of an enum class, in the arg passing. This method is only + * used by the enumClass$Map class that gets generated for every enum + * class. Once ordinalization proceeds, this $Map class should be pruned. + */ + if (x.getTarget() == enumCreateValueOfMapMethod) { + return false; + } + + /* + * If we have a method call of Enum.ordinal(), then we want to allow a + * cast operation from enum subclass to Enum on the instance. Other + * optimizers have a tendency to convert things like: + * + * 'switch(enumObj.ordinal())' to + * 'switch((Enum)enumObj).ordinal' + * + * We don't want to blacklist enumObj in that case, so we push this castOp + * on a stack and and check it in the subsequent call to endVisit for + * JCastOperation (above). We can't simply return false and prevent + * the visit of the JCastOperation altogether, since we do need to visit + * the castOperation's sub-expression. + */ + if (x.getTarget() == enumOrdinalMethod) { + if (x.getInstance() != null && + x.getInstance() instanceof JCastOperation) { + JCastOperation castOp = (JCastOperation) x.getInstance(); + if (getPossiblyUnderlyingType(castOp.getCastType()) == enumType) { + JEnumType fromType = getEnumType(castOp.getExpr().getType()); + if (fromType != null) { + castOpsToIgnore.push(castOp); + } + } + } + } + + // ok to visit + return true; + } + + /* + * Override for the method called from ImplicitUpcastAnalyzer, + * which will be called for any implicit upcast. + */ + @Override + protected void processImplicitUpcast(JType fromType, JType destType) { + if (fromType == nullType) { + // handle case where a nullType is cast to an enum + blackListIfEnum(destType); + } else if (fromType == javaScriptObjectType) { + // handle case where a javaScriptObject is cast to an enum + blackListIfEnum(destType); + } else { + blackListIfEnumCast(fromType, destType); + } + } + + private void addToBlackList(JEnumType enumType) { + ordinalizationBlackList.add(enumType); + } + + private void blackListIfEnum(JType maybeEnum) { + JEnumType actualEnum = getEnumType(maybeEnum); + if (actualEnum != null) { + addToBlackList(actualEnum); + } + } + + private void blackListIfEnumCast(JType maybeEnum, JType destType) { + JEnumType actualEnum = getEnumType(maybeEnum); + JEnumType actualDestType = getEnumType(destType); + if (actualEnum != null) { + if (actualDestType != actualEnum) { + addToBlackList(actualEnum); + } + return; + } + + // check JArrayTypes of enums + actualEnum = getEnumTypeFromArrayLeafType(maybeEnum); + actualDestType = getEnumTypeFromArrayLeafType(destType); + if (actualEnum != null) { + if (actualDestType != actualEnum) { + addToBlackList(actualEnum); + } + } + } + + private void blackListIfEnumExpression(JExpression instance) { + if (instance != null) { + blackListIfEnum(instance.getType()); + } + } + } + + /** + * A visitor which replaces enum types with an integer. + * + * It sub-classes TypeRemapper, which encapsulates all the locations for a + * settable type. The overridden remap() method will be called in each + * instance, and it will test whether the type is a candidate for replacement, + * and if so, return the new type to set (JPrimitiveType.INT). + * + * Any reference to an enum field constant in an expression is replaced + * with integer. + * + * This will also explicitly replace an enum's field constants with its + * ordinal int values, and remove initialization of enum constants and the + * $VALUES array in the clinit method for the enum. + */ + private class ReplaceEnumTypesWithInteger extends TypeRemapper { + + @Override + public boolean visit(JClassType x, Context ctx) { + // don't waste time visiting the large ClassLiteralHolder class + if (x == classLiteralHolderType) { + return false; + } + + // cleanup clinit method for ordinalizable enums + if (canBeOrdinal(x)) { + // method 0 is always the clinit + updateClinit(x.getMethods().get(0)); + } + return true; + } + + @Override + public boolean visit(JField x, Context ctx) { + /* + * Replace an enum field constant, with it's integer valued ordinal. + */ + if (x instanceof JEnumField && canBeOrdinal(x.getEnclosingType())) { + int ordinal = ((JEnumField) x).ordinal(); + x.setInitializer(new JDeclarationStatement(x.getSourceInfo(), + new JFieldRef(x.getSourceInfo(), null, x, + x.getEnclosingType()), program.getLiteralInt(ordinal))); + } + return true; + } + + @Override + public boolean visit(JFieldRef x, Context ctx) { + /* + * Replace an enum field ref with it's integer valued ordinal. + */ + JField field = x.getField(); + if (field instanceof JEnumField && canBeOrdinal(field.getEnclosingType())) { + int ordinal = ((JEnumField) field).ordinal(); + ctx.replaceMe(program.getLiteralInt(ordinal)); + } + return true; + } + + /* + * Remap enum types with JPrimitiveType.INT. + * Also handle case for arrays, replace enum leaftype with JPrimitive.INT. + * This is an override implementation called from TypeRemapper. + */ + @Override + protected JType remap(JType type) { + JType remappedType = getOrdinalizedType(type); + if (remappedType != null) { + return remappedType; + } else { + return type; + } + } + + private boolean canBeOrdinal(JType type) { + JType uType = getPossiblyUnderlyingType(type); + return uType instanceof JEnumType && + !ordinalizationBlackList.contains(uType); + } + + private JType getOrdinalizedType(JType type) { + if (canBeOrdinal(type)) { + return JPrimitiveType.INT; + } + + JType uType = getPossiblyUnderlyingType(type); + if (uType instanceof JArrayType) { + JArrayType aType = (JArrayType) uType; + JType leafType = aType.getLeafType(); + if (canBeOrdinal(leafType)) { + JArrayType newAType = program.getTypeArray( + JPrimitiveType.INT, aType.getDims()); + return program.getNonNullType(newAType); + } + } + + return null; + } + + /* + * Remove initialization of enum constants, and the $VALUES array, in the + * clinit for an ordinalizable enum. + */ + private void updateClinit(JMethod method) { + List<JStatement> stmts = ((JMethodBody) method.getBody()).getStatements(); + Iterator<JStatement> it = stmts.iterator(); + // look for statements of the form EnumValueField = ... + while (it.hasNext()) { + JStatement stmt = it.next(); + if (stmt instanceof JDeclarationStatement) { + JVariableRef ref = ((JDeclarationStatement) stmt).getVariableRef(); + if (ref instanceof JFieldRef) { + JFieldRef enumRef = (JFieldRef) ref; + if (enumRef.getField().getEnclosingType() == method.getEnclosingType()) { + // see if LHS is a field ref that corresponds to the class of this method + if (enumRef.getField() instanceof JEnumField) { + it.remove(); + } else if (enumRef.getField().getName().equals("$VALUES")) { + it.remove(); + } + } + } + } + } + } + } + + /** + * A visitor which will replace references to the ordinal field and + * ordinal method refs with the appropropriate ordinal integer value. + * + * Note, this visitor must run after the ReplaceEnumTypesWithInteger visitor, + * since it depends on detecting the locations where the enumOrdinalField or + * enumOrdinalMethod have had their types changed to integer. + */ + private class ReplaceOrdinalFieldAndMethodRefsWithOrdinal extends JModVisitor { + @Override + public boolean visit(JClassType x, Context ctx) { + // don't waste time visiting the large ClassLiteralHolder class + if (x == classLiteralHolderType) { + return false; + } + return true; + } + + @Override + public boolean visit(JFieldRef x, Context ctx) { + /* + * Check field refs that refer to Enum.ordinal, but which have already + * been ordinalized by the previous pass. This is implemented with visit + * instead of endVisit since, in the case of an underlying cast operation, + * we don't want to traverse the instance expression before we replace it. + */ + if (x.getField() == enumOrdinalField) { + if (x.getInstance() != null) { + JType type = x.getInstance().getType(); + if (type == JPrimitiveType.INT) { + /* + * See if this fieldRef was converted to JPrimitiveType.INT, but + * still points to the Enum.ordinal field. If so, replace the field + * ref with the ordinalized int itself. + */ + ctx.replaceMe(x.getInstance()); + } else if (x.getInstance() instanceof JCastOperation) { + /* + * See if this reference to Enum.ordinal is via a cast from an enum + * sub-class, that we've already ordinalized to JPrimitiveType.INT. + * If so, replace the whole cast operation. + * (see JFieldRef visit method in CannotBeOrdinalAnalyzer above). + */ + JCastOperation castOp = (JCastOperation) x.getInstance(); + if (getPossiblyUnderlyingType(castOp.getType()) == enumType) { + if (castOp.getExpr().getType() == JPrimitiveType.INT) { + ctx.replaceMe(castOp.getExpr()); + } + } + } + } + } + return true; + } + + @Override + public boolean visit(JMethodCall x, Context ctx) { + /* + * See if this methodCall was converted to JPrimitiveType.INT, but still + * points to the Enum.ordinal() method. If so, replace the method call with + * the ordinal expression itself. Implement with visit (and not endVisit), + * so we don't traverse the method call itself unnecessarily. + */ + if (x.getTarget() == enumOrdinalMethod) { + if (x.getInstance() != null) { + JType type = x.getInstance().getType(); + if (type == JPrimitiveType.INT) { + /* + * See if this instance was converted to JPrimitiveType.INT, but still + * points to the Enum.ordinal() method. If so, replace the method call + * with the ordinalized int itself. + */ + ctx.replaceMe(x.getInstance()); + } else if (x.getInstance() instanceof JCastOperation) { + /* + * See if this reference to Enum.ordinal() is via a cast from an enum + * sub-class, that we've already ordinalized to JPrimitiveType.INT. + * If so, replace the whole cast operation. + * (see JMethodCall visit method in CannotBeOrdinalAnalyzer above). + */ + JCastOperation castOp = (JCastOperation) x.getInstance(); + if (getPossiblyUnderlyingType(castOp.getType()) == enumType) { + if (castOp.getExpr().getType() == JPrimitiveType.INT) { + ctx.replaceMe(castOp.getExpr()); + } + } + } + } + } + return true; + } + } + + private OptimizerStats execImpl() { + OptimizerStats stats = new OptimizerStats(NAME); + + if (tracker != null) { + tracker.incrementRunCount(); + tracker.maybeDumpAST(program, 0); + } + + // Create black list of enum refs which can't be converted to an ordinal ref + CannotBeOrdinalAnalyzer ordinalAnalyzer = new CannotBeOrdinalAnalyzer(program); + ordinalAnalyzer.accept(program); + ordinalAnalyzer.afterVisitor(); + + if (tracker != null) { + for (JEnumType type : enumsVisited.values()) { + tracker.addVisited(type.getName()); + if (!ordinalizationBlackList.contains(type)) { + tracker.addOrdinalized(type.getName()); + } + } + } + + // Bail if we don't need to do any ordinalization + if (enumsVisited.size() == ordinalizationBlackList.size()) { + return stats; + } + + // Replace enum type refs + ReplaceEnumTypesWithInteger replaceEnums = + new ReplaceEnumTypesWithInteger(); + replaceEnums.accept(program); + stats.recordModified(replaceEnums.getNumMods()); + + if (tracker != null) { + tracker.maybeDumpAST(program, 1); + } + + // Replace enum field and method refs + ReplaceOrdinalFieldAndMethodRefsWithOrdinal replaceOrdinalRefs = + new ReplaceOrdinalFieldAndMethodRefsWithOrdinal(); + replaceOrdinalRefs.accept(program); + stats.recordModified(replaceOrdinalRefs.getNumMods()); + + if (tracker != null) { + tracker.maybeDumpAST(program, 2); + } + + return stats; + } + + private JEnumType getEnumType(JType type) { + type = getPossiblyUnderlyingType(type); + if (type instanceof JClassType) { + return ((JClassType) type).isEnumOrSubclass(); + } + return null; + } + + private JEnumType getEnumTypeFromArrayLeafType(JType type) { + type = getPossiblyUnderlyingType(type); + if (type instanceof JArrayType) { + type = ((JArrayType) type).getLeafType(); + return getEnumType(type); + } + return null; + } + + private JType getPossiblyUnderlyingType(JType type) { + if (type instanceof JReferenceType) { + return ((JReferenceType) type).getUnderlyingType(); + } + return type; + } + + /** + * A simple Tracker class for compiling lists of enum classes processed by + * this optimizer. If enabled, the results can be logged as debug output, and + * the results can be tested after running with a given input. + */ + public static class Tracker { + private final Set<String> allEnumsVisited; + private final Set<String> allEnumsOrdinalized; + private final List<Set<String>> enumsVisitedPerPass; + private final List<Set<String>> enumsOrdinalizedPerPass; + private int runCount = -1; + + // use TreeSets, for nice sorted iteration for output + public Tracker() { + allEnumsVisited = new TreeSet<String>(); + allEnumsOrdinalized = new TreeSet<String>(); + enumsVisitedPerPass = new ArrayList<Set<String>>(); + enumsOrdinalizedPerPass = new ArrayList<Set<String>>(); + + // add entry for initial pass + enumsVisitedPerPass.add(new TreeSet<String>()); + enumsOrdinalizedPerPass.add(new TreeSet<String>()); + } + + public void addOrdinalized(String ordinalized) { + enumsOrdinalizedPerPass.get(runCount).add(ordinalized); + allEnumsOrdinalized.add(ordinalized); + } + + public void addVisited(String visited) { + enumsVisitedPerPass.get(runCount).add(visited); + allEnumsVisited.add(visited); + } + + public int getNumOrdinalized() { + return allEnumsOrdinalized.size(); + } + + public int getNumVisited() { + return allEnumsVisited.size(); + } + + public void incrementRunCount() { + runCount++; + enumsVisitedPerPass.add(new TreeSet<String>()); + enumsOrdinalizedPerPass.add(new TreeSet<String>()); + } + + public boolean isOrdinalized(String className) { + return allEnumsOrdinalized.contains(className); + } + + public boolean isVisited(String className) { + return allEnumsVisited.contains(className); + } + + public void logEnumsNotOrdinalized(TreeLogger logger, TreeLogger.Type logType) { + if (logger != null) { + logger = logger.branch(logType, "Enums Not Ordinalized:"); + for (String enumVisited : allEnumsVisited) { + if (!isOrdinalized(enumVisited)) { + logger.branch(logType, enumVisited); + } + } + } + } + + public void logEnumsOrdinalized(TreeLogger logger, TreeLogger.Type logType) { + if (logger != null) { + logger = logger.branch(logType, "Enums Ordinalized:"); + for (String enumOrdinalized : allEnumsOrdinalized) { + logger.branch(logType, enumOrdinalized); + } + } + } + + public void logEnumsOrdinalizedPerPass(TreeLogger logger, TreeLogger.Type logType) { + if (logger != null) { + if (allEnumsOrdinalized.size() == 0) { + return; + } + logger = logger.branch(logType, "Enums Ordinalized per Optimization Pass:"); + int pass = 0; + for (Set<String> enumsOrdinalized : enumsOrdinalizedPerPass) { + pass++; + if (enumsOrdinalized.size() > 0) { + TreeLogger subLogger = logger.branch(logType, "Pass " + pass + ": " + + enumsOrdinalized.size() + " ordinalized"); + for (String enumOrdinalized : enumsOrdinalized) { + subLogger.branch(logType, enumOrdinalized); + } + } + } + } + } + + public void logEnumsVisitedPerPass(TreeLogger logger, TreeLogger.Type logType) { + if (logger != null) { + if (allEnumsVisited.size() == 0) { + return; + } + logger = logger.branch(logType, "Enums Visited per Optimization Pass:"); + int pass = 0; + for (Set<String> enumsVisited : enumsVisitedPerPass) { + pass++; + if (enumsVisited.size() > 0) { + TreeLogger subLogger = logger.branch(logType, "Pass " + pass + ": " + + enumsVisited.size() + " visited"); + for (String enumVisited : enumsVisited) { + subLogger.branch(logType, enumVisited); + } + } + } + } + } + + public void logResults(TreeLogger logger, TreeLogger.Type logType) { + logger = logResultsSummary(logger, logType); + logEnumsOrdinalized(logger, logType); + logEnumsNotOrdinalized(logger, logType); + } + + public void logResultsDetailed(TreeLogger logger, TreeLogger.Type logType) { + logger = logResultsSummary(logger, logType); + logEnumsOrdinalizedPerPass(logger, logType); + // logEnumsVisitedPerPass(logger, logType); + logEnumsNotOrdinalized(logger, logType); + } + + public TreeLogger logResultsSummary(TreeLogger logger, TreeLogger.Type logType) { + if (logger != null) { + logger = logger.branch(logType, "EnumOrdinalizer Results:"); + logger.branch(logType, "After pass " + (runCount + 1)); + logger.branch(logType, allEnumsOrdinalized.size() + " of " + + allEnumsVisited.size() + " ordinalized"); + return logger; + } + return null; + } + + public void maybeDumpAST(JProgram program, int stage) { + AstDumper.maybeDumpAST(program, NAME + "_" + (runCount + 1) + "_" + stage); + } + } +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplicitUpcastAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplicitUpcastAnalyzer.java new file mode 100644 index 0000000..27e3b98 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplicitUpcastAnalyzer.java
@@ -0,0 +1,202 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.dev.jjs.ast.Context; +import com.google.gwt.dev.jjs.ast.JBinaryOperation; +import com.google.gwt.dev.jjs.ast.JBinaryOperator; +import com.google.gwt.dev.jjs.ast.JConditional; +import com.google.gwt.dev.jjs.ast.JDeclarationStatement; +import com.google.gwt.dev.jjs.ast.JExpression; +import com.google.gwt.dev.jjs.ast.JField; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JMethodCall; +import com.google.gwt.dev.jjs.ast.JParameter; +import com.google.gwt.dev.jjs.ast.JPrimitiveType; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JReferenceType; +import com.google.gwt.dev.jjs.ast.JReturnStatement; +import com.google.gwt.dev.jjs.ast.JThrowStatement; +import com.google.gwt.dev.jjs.ast.JType; +import com.google.gwt.dev.jjs.ast.JVisitor; +import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; + +import java.util.List; + +/** + * This class will identify instances of an implicit upcast between non-primitive + * types, and call the overridable processImplicitUpcast method. + * + * TODO(jbrosenberg): Consider extending to handle implicit upcasts between primitive types. + * This is not as straightforward as for reference types, because primitives + * can be boxed and unboxed implicitly as well. + */ +public class ImplicitUpcastAnalyzer extends JVisitor { + + protected JMethod currentMethod; + private final JType throwableType; + private final JType javaScriptObjectType; + private final JType nullType; + + public ImplicitUpcastAnalyzer(JProgram program) { + this.throwableType = program.getIndexedType("Throwable"); + this.javaScriptObjectType = program.getJavaScriptObject(); + this.nullType = program.getTypeNull(); + } + + @Override + public void endVisit(JBinaryOperation x, Context ctx) { + if (x.isAssignment()) { + processIfTypesNotEqual(x.getRhs().getType(), x.getLhs().getType()); + } else if (x.getRhs().getType() == nullType) { + processIfTypesNotEqual(nullType, x.getLhs().getType()); + } else if (x.getLhs().getType() == nullType) { + processIfTypesNotEqual(nullType, x.getRhs().getType()); + } else if (x.getOp() == JBinaryOperator.CONCAT || + x.getOp() == JBinaryOperator.EQ || + x.getOp() == JBinaryOperator.NEQ) { + /* + * Since we are not attempting to handle detection of upcasts between + * primitive types, we limit handling here to CONCAT, EQ and NEQ. + * Need to do both directions. + */ + processIfTypesNotEqual(x.getLhs().getType(), x.getRhs().getType()); + processIfTypesNotEqual(x.getRhs().getType(), x.getLhs().getType()); + } + } + + @Override + public void endVisit(JConditional x, Context ctx) { + processIfTypesNotEqual(x.getThenExpr().getType(), x.getType()); + processIfTypesNotEqual(x.getElseExpr().getType(), x.getType()); + } + + @Override + public void endVisit(JDeclarationStatement x, Context ctx) { + if (x.getInitializer() != null) { + processIfTypesNotEqual(x.getInitializer().getType(), + x.getVariableRef().getType()); + } + } + + @Override + public void endVisit(JField x, Context ctx) { + if (x.getInitializer() == null && !x.isFinal()) { + if (!(x.getType() instanceof JPrimitiveType)) { + // if it is declared without an initial value, it defaults to null + processIfTypesNotEqual(nullType, x.getType()); + } + } + } + + @Override + public void endVisit(JMethod x, Context ctx) { + // check for upcast in return type as compared to an overridden method + List<JMethod> overrides = x.getOverrides(); + if (overrides != null && overrides.size() > 0) { + // only check the first one, since other ones will be checked when those + // overridden methods are visited, don't want to do redundant work + processIfTypesNotEqual(x.getType(), overrides.get(0).getType()); + } + + if (x.getBody() != null && x.getBody().isNative()) { + /* + * Check if this method has a native (jsni) method body, in which case + * all arguments passed in are implicitly cast to JavaScriptObject + */ + List<JParameter> params = x.getParams(); + for (int i = 0; i < params.size(); i++) { + processIfTypesNotEqual(params.get(i).getType(), javaScriptObjectType); + } + + /* + * Check if this method has a non-void return type, in which case + * it will be implicitly cast from JavaScriptObject + */ + if (x.getType() != JPrimitiveType.VOID) { + processIfTypesNotEqual(javaScriptObjectType, x.getType()); + } + } + } + + @Override + public void endVisit(JMethodCall x, Context ctx) { + // check for upcast in argument passing + List<JExpression> args = x.getArgs(); + List<JParameter> params = x.getTarget().getParams(); + + for (int i = 0; i < args.size(); i++) { + // make sure the param wasn't pruned + if (i < params.size()) { + processIfTypesNotEqual(args.get(i).getType(), params.get(i).getType()); + } + } + } + + @Override + public void endVisit(JsniMethodRef x, Context ctx) { + // the return type of this method ref will be cast to JavaScriptObject + if (x.getTarget().getType() != JPrimitiveType.VOID) { + processIfTypesNotEqual(x.getTarget().getType(), javaScriptObjectType); + } + + // check referenced method's params, which are passed as JavaScriptObjects + List<JParameter> params = x.getTarget().getParams(); + for (int i = 0; i < params.size(); i++) { + processIfTypesNotEqual(javaScriptObjectType, params.get(i).getType()); + } + } + + @Override + public void endVisit(JReturnStatement x, Context ctx) { + if (x.getExpr() != null) { + // check against the current method return type + processIfTypesNotEqual(x.getExpr().getType(), currentMethod.getType()); + } + } + + @Override + public void endVisit(JThrowStatement x, Context ctx) { + // all things thrown are upcast to a Throwable + JType type = x.getExpr().getType(); + if (type instanceof JReferenceType) { + type = ((JReferenceType) type).getUnderlyingType(); + } + processIfTypesNotEqual(type, throwableType); + } + + @Override + public boolean visit(JMethod x, Context ctx) { + // save this, so can use it later for checking JReturnStatement + currentMethod = x; + return true; + } + + /** + * An overriding method will be called for each detected implicit upcast. + * @param fromType + * @param destType + */ + protected void processImplicitUpcast(JType fromType, JType destType) { + // override + } + + private void processIfTypesNotEqual(JType fromType, JType destType) { + if (fromType != destType) { + processImplicitUpcast(fromType, destType); + } + } +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeRemapper.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeRemapper.java new file mode 100644 index 0000000..d8298d6 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeRemapper.java
@@ -0,0 +1,95 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.dev.jjs.ast.Context; +import com.google.gwt.dev.jjs.ast.JBinaryOperation; +import com.google.gwt.dev.jjs.ast.JCastOperation; +import com.google.gwt.dev.jjs.ast.JConditional; +import com.google.gwt.dev.jjs.ast.JConstructor; +import com.google.gwt.dev.jjs.ast.JGwtCreate; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JModVisitor; +import com.google.gwt.dev.jjs.ast.JNewArray; +import com.google.gwt.dev.jjs.ast.JNonNullType; +import com.google.gwt.dev.jjs.ast.JType; +import com.google.gwt.dev.jjs.ast.JVariable; + +/** + * This class is a visitor which can find all sites where a JType can be updated + * from one type to another, and calls an overridable remap method for each + * instance. An extending class can override the remap and return a new type + * where it deems it necessary, such as to replace the type of all references + * of a class. + */ +public class TypeRemapper extends JModVisitor { + + @Override + public void endVisit(JBinaryOperation x, Context ctx) { + x.setType(remap(x.getType())); + } + + @Override + public void endVisit(JCastOperation x, Context ctx) { + // JCastOperation doesn't have a settable castType method, so need to + // create a new one and do a replacement. + JType remapCastType = remap(x.getCastType()); + if (remapCastType != x.getCastType()) { + JCastOperation newX = new JCastOperation(x.getSourceInfo(), + remapCastType, x.getExpr()); + ctx.replaceMe(newX); + } + } + + @Override + public void endVisit(JConditional x, Context ctx) { + x.setType(remap(x.getType())); + } + + @Override + public void endVisit(JConstructor x, Context ctx) { + x.setType(remap(x.getType())); + } + + @Override + public void endVisit(JGwtCreate x, Context ctx) { + x.setType(remap(x.getType())); + } + + @Override + public void endVisit(JMethod x, Context ctx) { + x.setType(remap(x.getType())); + } + + @Override + public void endVisit(JNewArray x, Context ctx) { + x.setType((JNonNullType) remap(x.getType())); + } + + @Override + public void endVisit(JVariable x, Context ctx) { + x.setType(remap(x.getType())); + } + + /** + * An overriding method will be called for each detected JType element. + * @param type + */ + protected JType remap(JType type) { + // override to possibly return an different type + return type; + } +}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java index 51e913f..133e356 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java +++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -64,22 +64,57 @@ return code; } }; + public static final MockJavaResource CAST = new MockJavaResource( + "com.google.gwt.lang.Cast") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package com.google.gwt.lang;\n"); + code.append("public final class Cast {\n"); + code.append(" public static Object dynamicCast(Object src, int dstId) { return src;}\n"); + code.append(" public static boolean isNull(Object a) { return false;}\n"); + code.append(" public static boolean isNotNull(Object a) { return false;}\n"); + code.append(" public static boolean jsEquals(Object a, Object b) { return false;}\n"); + code.append(" public static boolean jsNotEquals(Object a, Object b) { return false;}\n"); + code.append("}\n"); + return code; + } + }; public static final MockJavaResource CLASS = new MockJavaResource( "java.lang.Class") { @Override protected CharSequence getContent() { + // This has extra code in the createForEnum method, to keep it from being inlined StringBuffer code = new StringBuffer(); code.append("package java.lang;\n"); code.append("import com.google.gwt.core.client.JavaScriptObject;\n"); code.append("public final class Class<T> {\n"); - code.append(" static <T> Class<T> createForArray(String packageName, String className, String seedName, Class<?> componentType) { return new Class<T>(); }\n"); - code.append(" static <T> Class<T> createForClass(String packageName, String className, String seedName, Class<? super T> superclass) { return new Class<T>(); }\n"); - code.append(" static <T> Class<T> createForEnum(String packageName, String className, String seedName, Class<? super T> superclass, JavaScriptObject enumConstantsFunc, JavaScriptObject enumValueOfFunc) { return new Class<T>(); }\n"); - code.append(" static <T> Class<T> createForInterface(String packageName, String className) { return new Class<T>(); }\n"); - code.append(" static <T> Class<T> createForPrimitive(String packageName, String className, String jni) { return new Class<T>(); }\n"); + code.append(" static <T> Class<T> createForArray(String packageName,\n"); + code.append(" String className, String seedName, Class<?> componentType) {\n"); + code.append(" return new Class<T>(); }\n"); + code.append(" static <T> Class<T> createForClass(String packageName,\n"); + code.append(" String className, String seedName, Class<? super T> superclass) {\n"); + code.append(" return new Class<T>(); }\n"); + code.append(" static <T> Class<T> createForEnum(String packageName,\n"); + code.append(" String className, String seedName, Class<? super T> superclass,\n"); + code.append(" JavaScriptObject enumConstantsFunc, JavaScriptObject enumValueOfFunc) {\n"); + code.append(" Class<T> newClass = new Class<T>();\n"); + code.append(" newClass.className = className;\n"); + code.append(" newClass.packageName = packageName;\n"); + code.append(" newClass.seedName = seedName;\n"); + code.append(" return newClass;}\n"); + code.append(" static <T> Class<T> createForInterface(String packageName, String className) {\n"); + code.append(" return new Class<T>(); }\n"); + code.append(" static <T> Class<T> createForPrimitive(String packageName,\n"); + code.append(" String className, String jni) {\n"); + code.append(" return new Class<T>(); }\n"); code.append(" static boolean isClassMetadataEnabled() { return true; }\n"); code.append(" public boolean desiredAssertionStatus() { return true; }\n"); - code.append(" public String getName() { return null; }\n"); + code.append(" public String getName() { return className; }\n"); + code.append(" public T[] getEnumConstants() { return null; }\n"); + code.append(" private String packageName = null;"); + code.append(" private String className = null;"); + code.append(" private String seedName = null;"); code.append("}\n"); return code; } @@ -104,9 +139,20 @@ code.append("import java.io.Serializable;\n"); code.append("import com.google.gwt.core.client.JavaScriptObject;\n"); code.append("public abstract class Enum<E extends Enum<E>> implements Serializable {\n"); - code.append(" protected Enum(String name, int ordinal) {}\n"); - code.append(" protected static <T extends Enum<T>> JavaScriptObject createValueOfMap(T[] enumConstants) { return null; }\n"); + code.append(" public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { \n"); + code.append(" for (T enumConstant : enumType.getEnumConstants()) {\n"); + code.append(" if (enumConstant.name() != null) {\n"); + code.append(" return enumConstant;}}\n"); + code.append(" return null;}\n"); + code.append(" protected Enum(String name, int ordinal) { \n"); + code.append(" this.name = name;\n"); + code.append(" this.ordinal = ordinal;}\n"); + code.append(" protected static <T extends Enum<T>> JavaScriptObject createValueOfMap(T[] enumConstants) { return null;}\n"); code.append(" protected static <T extends Enum<T>> T valueOf(JavaScriptObject map, String name) { return null; }\n"); + code.append(" private final String name;\n"); + code.append(" private final int ordinal;\n"); + code.append(" public final String name() { return name; }\n"); + code.append(" public final int ordinal() { return ordinal; }\n"); code.append("}\n"); return code; } @@ -240,7 +286,7 @@ // Replace the basic Class and Enum with a compiler-specific one. result.remove(JavaResourceBase.CLASS); result.remove(JavaResourceBase.ENUM); - Collections.addAll(result, ARRAY, CLASS, CLASSLITERALHOLDER, ENUM, GWT, + Collections.addAll(result, ARRAY, CAST, CLASS, CLASSLITERALHOLDER, ENUM, GWT, RUNASYNCCALLBACK); return result.toArray(new MockJavaResource[result.size()]); }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java new file mode 100644 index 0000000..8980d43 --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java
@@ -0,0 +1,1017 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JProgram; + +/** + * A set of tests for the conditions under which ordinalization is and is not + * allowed. The ordinalization is performed when allowed. + * + * A complete test of the resulting ordinalization is not performed. However, + * the CastNormalizer and the EqualityNormalizer are run after the EnumOrdinalizer, + * to help ensure the integrity of the AST, such that there are no partially + * mismatched type assignments or comparisons, and that no binary operations + * between a primitive type and null have been added. Typically, such errors + * introduced by the EnumOrdinalizer are caught by these normalizers, so it + * makes sense to test the output in this way. Thus, we provide confidence + * that the AST is left in a coherent state, but it is not a complete test that + * ordinalization has completed correctly in every respec. + * + * TODO(jbrosenberg): Provide a test to assert that ordinalization has completed + * correctly, by inspecting the AST in detail, specifically for ordinal + * replacements, after the EnumOrdinalizer completes. + */ +public class EnumOrdinalizerTest extends OptimizerTestBase { + @Override + protected void setUp() throws Exception { + super.setUp(); + + // don't need to runDeadCodeElimination, it's after we're done anyway + runDeadCodeElimination = false; + + // defaults, can be overridden by individual test cases + runTypeTightener = false; + runMethodCallTightener = false; + } + + public void testOrdinalizeBasicAssignment() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit apple = Fruit.APPLE;", + "Fruit orange = Fruit.ORANGE;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeNewArrayAndAssignmentLocalRef() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit[] fruits = new Fruit[] {Fruit.APPLE, Fruit.ORANGE, Fruit.APPLE};", + "if (fruits[0] == Fruit.APPLE) {", + " fruits[0] = Fruit.ORANGE;", + "}"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeNewArrayOfArrayAndAssignmentLocalRef() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit[][] fruits = new Fruit[][] ", + " {{Fruit.APPLE, Fruit.ORANGE},{Fruit.APPLE, Fruit.ORANGE}};", + "if (fruits[0][1] == Fruit.APPLE) {", + " fruits[0][1] = Fruit.ORANGE;", + "}"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeNewArrayAndAssignmentFieldRef() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("private final Fruit[] fruits = new Fruit[] ", + " {Fruit.APPLE, Fruit.ORANGE, Fruit.APPLE};"); + optimize("void", "EntryPoint ep = new EntryPoint();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizableFinalFieldUninitializedByDefault() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("private final Fruit uninitializedFinalFruit;", + "public EntryPoint() {", + " uninitializedFinalFruit = Fruit.ORANGE;", + "}"); + optimize("void", "EntryPoint ep = new EntryPoint();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeSwitchStatement() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + setupFruitSwitchMethod(); + optimize("void", "String apple = fruitSwitch(Fruit.APPLE);", + "String orange = fruitSwitch(Fruit.ORANGE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeIfStatement() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl( + "public static String fruitIf(Fruit fruit) {", + " if (fruit == Fruit.APPLE) {", + " return \"Apple\";", + " } else if (fruit == Fruit.ORANGE) {", + " return \"Orange\";", + " } else {", + " return \"Unknown\";", + " }", + "}"); + optimize("void", "String apple = fruitIf(Fruit.APPLE);", + "String orange = fruitIf(Fruit.ORANGE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeConditional() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit = (true) ? Fruit.APPLE : Fruit.ORANGE;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeFieldRefOrdinalMethodCall() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "int i = Fruit.APPLE.ordinal();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeVariableRefOrdinalMethodCall() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "int i = fruit.ordinal();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizeMethodCallExpressionOrdinalFieldRef() + throws UnableToCompleteException { + /* + * the switch expression gets transformed to + * 'switch ((Enum) getResolvedFruit(fruit)).ordinal)' + * by the time the EnumOrdinalizer sees it. So this test is testing the + * logic to replace an ordinal field ref expression with the expression + * itself. + */ + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static Fruit getResolvedFruit(Fruit fruit) {", + " if (fruit == Fruit.APPLE) {", + " return Fruit.ORANGE;", + " } else { ", + " return Fruit.APPLE;", + " }", + "}"); + addSnippetClassDecl("public static int switchMethodCall(Fruit fruit) {", + " int retVal = 0;", + " switch (getResolvedFruit(fruit)) {", + " case APPLE: retVal = 12; break;", + " case ORANGE:retVal = 73; break;", + " }", + " return retVal;", + "}"); + optimize("void", "int i = switchMethodCall(Fruit.APPLE);", + "Fruit fruit = Fruit.ORANGE;", + "int j = switchMethodCall(fruit);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableClassLiteralReference() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Class clazz = Fruit.class;", + "String clazzStr = clazz.toString();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableEnumValueOfWithClassLiteralArg() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Object Carrot = Enum.valueOf(Fruit.class, \"APPLE\");", + "String carrot = Carrot.toString();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableGetClassMethodCall() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Class clazz = Fruit.APPLE.getClass();", + "String clazzStr = clazz.toString();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableExplicitCastToEnumClass() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Object obj = new Object();", + "Fruit fruit = (Fruit) obj;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableExplicitCastToArrayOfEnumClass() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Enum[] enumArray = new Enum[10];", + "Fruit[] fruitArray = (Fruit[]) enumArray;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableExplicitCastFromArrayOfEnumClass() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit[] fruitArray = new Fruit[10];", + "Enum[] enumArray = (Enum[]) fruitArray;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableExplicitCastToArrayOfArrayOfEnumClass() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Enum[][] enumArray = new Enum[10][10];", + "Fruit[][] fruitArray = (Fruit[][]) enumArray;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableExplicitCastFromArrayOfArrayOfEnumClass() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit[][] fruitArray = new Fruit[10][10];", + "Enum[][] enumArray = (Enum[][]) fruitArray;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableExplicitCastFromEnumClass() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Enum Carrot = (Enum) Fruit.APPLE;", + "String carrot = Carrot.toString();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableOrdinalMethodRefFromExplicitCastWithBlackListableSubExpression() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "int ord = " + + "((Fruit) Enum.valueOf(Fruit.class,\"APPLE\")).ordinal();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableInstanceFieldRef() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + // this will cause an instance field ref in the enum constructor + setupFruitEnumWithInstanceField(); + optimize("void"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableStaticFieldRef() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + // this will cause a static field ref in the enum clinit + setupFruitEnumWithStaticField(); + optimize("void"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableStaticMethodCallValues() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + // this ends up calling Fruit.clinit() first (which is a static method call) + setupFruitEnum(); + optimize("void", "Fruit[] fruits = Fruit.values();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableJsniFieldRef() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static Fruit instanceFruit;"); + addSnippetClassDecl("public static native void jsniMethod() /*-{", + " var x = @test.EntryPoint::instanceFruit", + "}-*/"); + optimize("void", "instanceFruit = Fruit.APPLE;", + "jsniMethod();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableJsniFieldRefStatic() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static native void jsniMethod() /*-{", + " var x = @test.EntryPoint.Fruit::APPLE", + "}-*/"); + optimize("void", "jsniMethod();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableJsniFieldRefClassLiteral() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static native void jsniMethod() /*-{", + " var x = @test.EntryPoint.Fruit::class", + "}-*/"); + optimize("void", "jsniMethod();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpAssignment() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Enum tomato;", + "tomato = Fruit.APPLE;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastFieldInitializedWithNullByDefault() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("static private Fruit uninitializedFruitAsNull;"); + optimize("void", "if (uninitializedFruitAsNull != Fruit.APPLE) {", + " uninitializedFruitAsNull = Fruit.ORANGE;", + "}"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpEquals() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + setupVegetableEnum(); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "Enum carrot = (Enum) Vegetable.CARROT;", + "boolean test = (fruit == carrot);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isVisited("test.EntryPoint$Vegetable")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Vegetable")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpNotEquals() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + setupVegetableEnum(); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "Enum carrot = (Enum) Vegetable.CARROT;", + // do in opposite order from OpEquals test + "boolean test = (carrot != fruit);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isVisited("test.EntryPoint$Vegetable")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Vegetable")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpEqualsNull() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static boolean testIsNull(Fruit fruit) {", + " if (fruit == null) {", + " return true;", + " } else {", + " return false;", + " }", + "}"); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "boolean isNull = testIsNull(fruit) || testIsNull(Fruit.ORANGE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpNotEqualsNull() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static boolean testIsNull(Fruit fruit) {", + " if (fruit != null) {", + " return true;", + " } else {", + " return false;", + " }", + "}"); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "boolean isNull = testIsNull(fruit) || testIsNull(Fruit.ORANGE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcat() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "String str = \"A string followed by \" + fruit;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcat2() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "String str = fruit + \" followed by a string\";"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcatAssignment() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit = Fruit.APPLE;", + "String str = \"A string concatenated with: \";", + "str += fruit;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastDeclarationToNull() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit = null;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastAssignmentToNull() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Fruit fruit;", + "fruit = null;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastDeclaration() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + optimize("void", "Enum tomato = Fruit.APPLE;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastConditional() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + setupVegetableEnum(); + optimize("void", "Enum tomato = null;", + "tomato = (true) ? Fruit.APPLE : Vegetable.CARROT;"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isVisited("test.EntryPoint$Vegetable")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Vegetable")); + } + + public void testNotOrdinalizableImplicitUpcastOverriddenMethodReturnType() + throws UnableToCompleteException { + + // this test depends on the tighteners running + runTypeTightener = true; + runMethodCallTightener = true; + + EnumOrdinalizer.startTracker(); + + /* + * Create methods with covariant return type, which the MethodTypeTightener + * will optimize to no longer be covariant, so make sure we check original + * overridden method type. + */ + addSnippetClassDecl("public interface EnumInterface {", + " String name();", + "}"); + addSnippetClassDecl("public abstract class AbstractClass<T extends EnumInterface> {", + " public abstract T getEnumClass();", + "}"); + addSnippetClassDecl("public class CustomClass1 extends AbstractClass<EnumClass1> {", + " public EnumClass1 getEnumClass() { return EnumClass1.CONST1; }", + "}"); + addSnippetClassDecl("public class CustomClass2 extends AbstractClass<EnumClass2> {", + " public EnumClass2 getEnumClass() { return EnumClass2.CONST2; }", + "}"); + addSnippetClassDecl("public enum EnumClass1 implements EnumInterface {", + " CONST1;", + "}"); + addSnippetClassDecl("public enum EnumClass2 implements EnumInterface {", + " CONST2;", + "}"); + addSnippetClassDecl("public static void testEnumClass(AbstractClass abstractClass) {", + " EnumInterface enumClass = abstractClass.getEnumClass();", + "}"); + optimize("void", "EntryPoint ep = new EntryPoint();", + "AbstractClass abstractClass1 = ep.new CustomClass1();", + "AbstractClass abstractClass2 = ep.new CustomClass2();", + "testEnumClass(abstractClass1);", + "testEnumClass(abstractClass2);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$EnumClass1")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$EnumClass1")); + assertTrue(tracker.isVisited("test.EntryPoint$EnumClass2")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$EnumClass2")); + } + + public void testNotOrdinalizableImplicitUpcastMethodCallArgs() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static String getEnumString(Enum myEnum) {", + // make sure this method does something so not inlined + " int ord = myEnum.ordinal();", + " String retString = \"\";", + " for (int i = 0;i<ord;i++) {", + " retString += \"-\";", + " }", + " retString += myEnum.name();", + " return retString;", + "}"); + optimize("void", "String stringApple = getEnumString(Fruit.APPLE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastJsniMethodBodyParams() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static native void passEnumToJsniMethod(Fruit myEnum) /*-{", + "}-*/"); + optimize("void", "passEnumToJsniMethod(Fruit.APPLE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastJsniMethodBodyReturnType() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static native Fruit returnFruitViaJsni() /*-{", + " var myJso;", + " return myJso;", + "}-*/"); + optimize("void", "Fruit fruit = returnFruitViaJsni();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastJsniMethodRefParams() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + setupFruitSwitchMethod(); + addSnippetClassDecl("public static native void fruitSwitchViaJsni() /*-{", + " var myJso;", + " var result = @test.EntryPoint::fruitSwitch(Ltest/EntryPoint$Fruit;)(myJso);", + "}-*/"); + optimize("void", "fruitSwitchViaJsni();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastJsniMethodRefReturnType() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + addSnippetClassDecl("public static Fruit returnSomeFruit() {", + " return Fruit.APPLE;", + "}"); + addSnippetClassDecl("public static native void jsniMethodRefWithEnumReturn() /*-{", + " var result = @test.EntryPoint::returnSomeFruit()();", + "}-*/"); + optimize("void", "jsniMethodRefWithEnumReturn();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableImplicitUpcastReturnStatement() + throws UnableToCompleteException { + EnumOrdinalizer.startTracker(); + + setupFruitEnum(); + setupVegetableEnum(); + addSnippetClassDecl("public static Enum returnAsEnum(int mode) {", + " if (mode == 0) {", + " return Fruit.APPLE;", + " } else {", + " return Vegetable.CARROT;", + " }", + "}"); + optimize("void", "Enum myEnum = returnAsEnum(0);", + // do a second one, to prevent inlining + "Enum myOtherEnum = returnAsEnum(1);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + EnumOrdinalizer.stopTracker(); + + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isVisited("test.EntryPoint$Vegetable")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Vegetable")); + } + + private void setupFruitEnum() { + addSnippetClassDecl("public enum Fruit {APPLE, ORANGE}"); + setupExtraDummyEnum(); + } + + private void setupFruitEnumWithInstanceField() { + addSnippetClassDecl("public enum Fruit {APPLE(\"a\"), ORANGE(\"b\");", + " public final String instanceField;", + " private Fruit(String str) {", + " instanceField = str;", + " }", + "}"); + setupExtraDummyEnum(); + } + + private void setupFruitEnumWithStaticField() { + addSnippetClassDecl("public enum Fruit {APPLE, ORANGE;", + " public static final String staticField = \"STATIC\";", + "}"); + setupExtraDummyEnum(); + } + + private void setupVegetableEnum() { + addSnippetClassDecl("public enum Vegetable {CARROT, SPINACH}"); + } + + private void setupExtraDummyEnum() { + /* + * Assure there are at least more 2 enums in the program, so inlining or + * tightening doesn't push a single enum sub-class into the methods of the + * Enum super-class itself (which prevents ordinalization in most cases). + * TODO(jbrosenberg): Make ordinalization work if there's only 1 enum in + * a program. + */ + addSnippetClassDecl("public enum DummyEnum {DUMMY}"); + } + + private void setupFruitSwitchMethod() { + addSnippetClassDecl("public static String fruitSwitch(Fruit fruit) {", + " switch(fruit) {", + " case APPLE: return \"Apple\";", + " case ORANGE: return \"Orange\";", + " default: return \"Unknown\";", + " }", + "}"); + } + + /* + * Always run CastNormalizer and EqualityNormalizer, even in cases where we + * are testing that ordinalization cannot occur, since there may be other + * enums (such as DummyEnum) which do get ordinalized, and we want to test + * that all is well regardless. + */ + private final boolean runCastNormalizer = true; + private final boolean runEqualityNormalizer = true; + + /* + * EnumOrdinalizer depends MakeCallsStatic and MethodInliner running before + * it runs, since they cleanup the internal structure of an enum class to + * inline instance methods like $init. + * TODO(jbrosenberg): Update EnumOrdinalizer to be able to succeed + * irrespective of the ordering and interaction with other optimizers. + */ + private final boolean runMakeCallsStatic = true; + private final boolean runMethodInliner = true; + + // these can be enabled where needed + private boolean runMethodCallTightener = false; + private boolean runTypeTightener = false; + + @Override + protected boolean optimizeMethod(JProgram program, JMethod method) { + /* + * EnumOrdinalizer depends MakeCallsStatic and MethodInliner running before + * it runs, since they cleanup the internal structure of an enum class to + * inline instance methods like $init. + * + * TypeTightener and methodCallTightener are necessary to test some cases + * involving implicit casts on overridden method call return types. + * + * These are a subset of the actual optimizers run in JJS.optimizeLoop(). + */ + boolean didChange = false; + AstDumper.maybeDumpAST(program, "EnumOrdinalizerTest_start"); + + if (runMakeCallsStatic) { + didChange = MakeCallsStatic.exec(program).didChange() || didChange; + AstDumper.maybeDumpAST(program, + "EnumOrdinalizerTest_after_makeCallsStatic"); + } + if (runTypeTightener) { + didChange = TypeTightener.exec(program).didChange() || didChange; + AstDumper.maybeDumpAST(program, + "EnumOrdinalizerTest_after_typeTightener"); + } + if (runMethodCallTightener) { + didChange = MethodCallTightener.exec(program).didChange() || didChange; + AstDumper.maybeDumpAST(program, + "EnumOrdinalizerTest_after_methodCallTightener"); + } + if (runMethodInliner) { + didChange = MethodInliner.exec(program).didChange() || didChange; + AstDumper.maybeDumpAST(program, + "EnumOrdinalizerTest_after_methodInliner"); + } + + didChange = EnumOrdinalizer.exec(program).didChange() || didChange; + AstDumper.maybeDumpAST(program, + "EnumOrdinalizerTest_after_EnumOrdinalizer"); + + /* + * Run these normalizers to sanity check the AST. If there are any + * dangling type substitutions, the CastNormalizer will generally find it. + * If there are any introduced binary ops between an int and a null, the + * EqualityNormalizer will find it. + */ + if (runCastNormalizer) { + CastNormalizer.exec(program, false); + } + if (runEqualityNormalizer) { + EqualityNormalizer.exec(program); + } + + return didChange; + } +}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java index 1dd26ca..98a4d13 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
@@ -34,17 +34,17 @@ protected final class Result { private final String returnType; private final String originalCode; - private final boolean madeChages; + private final boolean madeChanges; private final JProgram optimizedProgram; private final String methodName; public Result(JProgram optimizedProgram, String returnType, - String methodName, String originalCode, boolean madeChages) { + String methodName, String originalCode, boolean madeChanges) { this.optimizedProgram = optimizedProgram; this.returnType = returnType; this.methodName = methodName; this.originalCode = originalCode; - this.madeChages = madeChages; + this.madeChanges = madeChanges; } public void into(String... expected) throws UnableToCompleteException { @@ -77,7 +77,7 @@ } public void noChange() { - assertFalse(madeChages); + assertFalse(madeChanges); } public JMethod findMethod(String methodName) {
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTest.java b/user/test/com/google/gwt/user/client/rpc/EnumsTest.java index 704b656..5a3d40e 100644 --- a/user/test/com/google/gwt/user/client/rpc/EnumsTest.java +++ b/user/test/com/google/gwt/user/client/rpc/EnumsTest.java
@@ -18,6 +18,8 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.EnumsTestService.Basic; import com.google.gwt.user.client.rpc.EnumsTestService.Complex; +import com.google.gwt.user.client.rpc.EnumsTestService.FieldEnum; +import com.google.gwt.user.client.rpc.EnumsTestService.FieldEnumWrapper; import com.google.gwt.user.client.rpc.EnumsTestService.Subclassing; /** @@ -120,4 +122,32 @@ } }); } + + /** + * Test that enums as fields in a wrapper class can be passed over RPC. + */ + public void testFieldEnumWrapperClass() { + delayTestFinishForRpc(); + + FieldEnumWrapper wrapper = new FieldEnumWrapper(); + wrapper.setFieldEnum(FieldEnum.X); + getService().echo(wrapper, new AsyncCallback<FieldEnumWrapper>() { + public void onFailure(Throwable caught) { + rethrowException(caught); + } + + public void onSuccess(FieldEnumWrapper result) { + assertNotNull("Was null", result); + FieldEnum fieldEnum = result.getFieldEnum(); + /* + * Don't want to do assertEquals(FieldEnum.X, fieldEnum) here, + * since it will force an implicit upcast on FieldEnum -> Object, + * which will bias the test. We want to assert that the + * EnumOrdinalizer properly prevents ordinalization of FieldEnum. + */ + assertTrue(FieldEnum.X == fieldEnum); + finishTest(); + } + }); + } }
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java b/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java index 6bd7f1d..54bb24c 100644 --- a/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java +++ b/user/test/com/google/gwt/user/client/rpc/EnumsTestService.java
@@ -15,6 +15,8 @@ */ package com.google.gwt.user.client.rpc; +import java.io.Serializable; + /** * RemoteService used to test the use of enums over RPC. */ @@ -82,7 +84,30 @@ public abstract String value(); } + /** + * Enum to be used as a field in a wrapper class + */ + public enum FieldEnum { + X, Y, Z + } + + /** + * Wrapper class containing an enum field + */ + public class FieldEnumWrapper implements Serializable { + private FieldEnum fieldEnum = FieldEnum.Z; + + public FieldEnum getFieldEnum() { + return this.fieldEnum; + } + + public void setFieldEnum(FieldEnum fieldEnum) { + this.fieldEnum = fieldEnum; + } + } + Basic echo(Basic value); Complex echo(Complex value) throws EnumStateModificationException; Subclassing echo(Subclassing value); + FieldEnumWrapper echo(FieldEnumWrapper value); }
diff --git a/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java index d011d4d..d434986 100644 --- a/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java +++ b/user/test/com/google/gwt/user/client/rpc/EnumsTestServiceAsync.java
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.rpc.EnumsTestService.Basic; import com.google.gwt.user.client.rpc.EnumsTestService.Complex; +import com.google.gwt.user.client.rpc.EnumsTestService.FieldEnumWrapper; import com.google.gwt.user.client.rpc.EnumsTestService.Subclassing; /** @@ -26,4 +27,5 @@ void echo(Basic value, AsyncCallback<Basic> callback); void echo(Complex value, AsyncCallback<Complex> callback); void echo(Subclassing value, AsyncCallback<Subclassing> callback); + void echo(FieldEnumWrapper value, AsyncCallback<FieldEnumWrapper> callback); }
diff --git a/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java index dda7444..711d9fc 100644 --- a/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java +++ b/user/test/com/google/gwt/user/server/rpc/EnumsTestServiceImpl.java
@@ -39,4 +39,8 @@ public Subclassing echo(Subclassing value) { return value; } + + public FieldEnumWrapper echo(FieldEnumWrapper value) { + return value; + } }