Fix EnumOrdinalizer. EnumOrdinalizer incorrectly selects enums for ordinalization (replacement of the enum values by integer ordinals) under a few circumstances. Also: - fixed quite a few of the units tests. - allow ordinalization even with access to values(). Bug: issue 8846. Change-Id: I9c22c1576a238d5f6b3a5f52b25b2ca697b83fad
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 index 2d988f5..4232ab8 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/EnumOrdinalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/EnumOrdinalizer.java
@@ -37,6 +37,7 @@ 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.JParameter; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JStatement; @@ -387,22 +388,6 @@ if (x.getInstance() != null) { // check any instance field reference other than ordinal blackListIfEnumExpression(x.getInstance()); - } else if (x.getField().isStatic()) { - /* - * Black list if the $VALUES static field is referenced, unless it's - * within the auto-generated clinit or values method, within the enum - * class itself. - * - * TODO (jbrosenberg): Investigate further whether referencing the - * $VALUES array (as well as the values() method) should not block - * ordinalization. Instead, convert $VALUES to an array of int. - */ - if (x.getField().getName().equals(JEnumType.VALUES_ARRAY_NAME) - && ((this.currentMethod.getEnclosingType() != x.getField().getEnclosingType()) || - (!this.currentMethod.getName().equals("values") && - !this.currentMethod.getName().equals("$clinit")))) { - blackListIfEnum(x.getField().getEnclosingType(), x.getSourceInfo()); - } } } @@ -429,9 +414,14 @@ } else if (x.getTarget().isStatic()) { // black-list static method calls on an enum class only for valueOf() // and values() - String methodName = x.getTarget().getName(); - if (methodName.equals("valueOf") || methodName.equals("values")) { - blackListIfEnum(x.getTarget().getEnclosingType(), x.getSourceInfo()); + JMethod target = x.getTarget(); + maybeBlackListDueToStaticCall(x.getSourceInfo(), target); + } + + if (x.getTarget().isNative()) { + // Black list enum types declared in parameters of native functions. + for (JParameter parameter :x.getTarget().getParams()) { + blackListIfEnum(parameter.getType(), x.getSourceInfo()); } } @@ -478,15 +468,12 @@ 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(), x.getSourceInfo()); - } + maybeBlackListDueToStaticCall(x.getSourceInfo(), x.getTarget()); } + // Black list enums returned to JSNI. + blackListIfEnum(x.getTarget().getType(), x.getSourceInfo()); + // defer to ImplicitUpcastAnalyzer to check method call args & params super.endVisit(x, ctx); } @@ -608,7 +595,18 @@ blackListIfEnum(instance.getType(), instance.getSourceInfo()); } } + + /** + * Blacklist the enum if there is a call to either MyEnum.valueOf() or MyEnum.values(). + */ + private void maybeBlackListDueToStaticCall(SourceInfo info, JMethod target) { + if (target.getEnclosingType().isEnumOrSubclass() != null && + (target.getName().equals("valueOf") || target.getName().equals("values"))) { + blackListIfEnum(target.getEnclosingType(), info); + } + } } + /** * A visitor which replaces enum types with an integer. * @@ -730,20 +728,20 @@ int removeIndex = 0; // Make a copy to avoid concurrent modification. for (JStatement stmt : new ArrayList<JStatement>(block.getStatements())) { - if (stmt instanceof JDeclarationStatement) { - JVariableRef ref = ((JDeclarationStatement) stmt).getVariableRef(); - if (ref instanceof JFieldRef) { - JFieldRef enumRef = (JFieldRef) ref; - // See if LHS is a field ref to the class being initialized. - JField field = enumRef.getField(); - if (field.isStatic() && field.getEnclosingType() == enclosingType) { - if (field instanceof JEnumField || - field.getName().equals(JEnumType.VALUES_ARRAY_NAME)) { - block.removeStmt(removeIndex--); - field.setInitializer(null); - } - } - } + if (!(stmt instanceof JDeclarationStatement)) { + continue; + } + JVariableRef ref = ((JDeclarationStatement) stmt).getVariableRef(); + if (!(ref instanceof JFieldRef)) { + continue; + } + + // See if LHS is a field ref to the class being initialized. + JField field = ((JFieldRef) ref).getField(); + if (field.isStatic() && field.getEnclosingType() == enclosingType && + field instanceof JEnumField) { + block.removeStmt(removeIndex--); + field.setInitializer(null); } ++removeIndex; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 1bc9168..d6957e3 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -2189,7 +2189,7 @@ } private JField createEnumValuesField(JEnumType type) { - // $VALUES = new E[]{A,B,B}; + // $VALUES = new E[]{A,B,C}; JArrayType enumArrayType = new JArrayType(type); JField valuesField = new JField(type.getSourceInfo(), JEnumType.VALUES_ARRAY_NAME, type, enumArrayType, true, Disposition.FINAL);
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 b2f1795..fa09708 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java +++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -167,14 +167,17 @@ "import com.google.gwt.core.client.JavaScriptObject;", "public abstract class Enum<E extends Enum<E>> implements Serializable {", " public static native <T extends Enum<T>> T valueOf(Class<T> enumType,", - " String name) /*-{ }-*/;", + " String name) /*-{ return enumType + name; }-*/;", + " public static native <T extends Enum<T>> T valueOf(JavaScriptObject enumType,", + " String name) /*-{ return enumType + name; }-*/;", " protected static native <T extends Enum<T>> JavaScriptObject createValueOfMap(", " T[] enumConstants) /*-{ }-*/;", - " protected static native <T extends Enum<T>> T valueOf(JavaScriptObject map,", - " String name) /*-{ }-*/;", - " protected Enum(String name, int ordinal) { ", " this.name = name;", - " this.ordinal = ordinal;}", " private final String name;", - " private final int ordinal;", " public final String name() { return name; }", + " protected Enum(String name, int ordinal) { ", + " this.name = name;", + " this.ordinal = ordinal;}", + " private final String name;", + " private final int ordinal;", + " public final String name() { return name; }", " public final int ordinal() { return ordinal; }", "}" );
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 index 2d4710d..a653128 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java
@@ -21,20 +21,34 @@ 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 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 ImplementCastsAndTypeChecks 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. + * A complete test of the resulting ordinalization is not performed. However, the + * ImplementCastsAndTypeChecks 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. */ public class EnumOrdinalizerTest extends OptimizerTestBase { + /* + * Always run ImplementCastsAndTypeChecks 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 performCastReplacement = true; + private final boolean runEqualityNormalizer = true; + // These are enabled as needed for a given test + private boolean runMakeCallsStatic; + private boolean runMethodInliner; + private boolean runMethodCallTightener; + private boolean runPruner; + private boolean runTypeTightener; + @Override protected void setUp() throws Exception { super.setUp(); @@ -46,6 +60,11 @@ runMethodCallTightener = false; runMethodInliner = true; runMakeCallsStatic = true; + // Opportunities for ordinalization are only present after unused references to $VALUES are + // pruned. + // NOTE: because we are pruning, each test case needs to make sure that enums that are + // considered for ordinalization are still live. + runPruner = true; } @Override @@ -54,45 +73,45 @@ } public void testOrdinalizeBasicAssignment() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit apple = Fruit.APPLE;", - "Fruit orange = Fruit.ORANGE;"); + "Fruit orange = Fruit.ORANGE;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizeNewArrayAndAssignmentLocalRef() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit[] fruits = new Fruit[] {Fruit.APPLE, Fruit.ORANGE, Fruit.APPLE};", - "if (fruits[0] == Fruit.APPLE) {", - " fruits[0] = Fruit.ORANGE;", - "}"); + "if (fruits[0] == Fruit.APPLE) {", + " fruits[0] = Fruit.ORANGE;", + "}"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizeNewArrayOfArrayAndAssignmentLocalRef() - throws UnableToCompleteException { + throws UnableToCompleteException { 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;", - "}"); + " {{Fruit.APPLE, Fruit.ORANGE},{Fruit.APPLE, Fruit.ORANGE}};", + "if (fruits[0][1] == Fruit.APPLE) {", + " fruits[0][1] = Fruit.ORANGE;", + "}"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizeNewArrayAndAssignmentFieldRef() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("private final Fruit[] fruits = new Fruit[] ", - " {Fruit.APPLE, Fruit.ORANGE, Fruit.APPLE};"); + " {Fruit.APPLE, Fruit.ORANGE, Fruit.APPLE};"); optimize("void", "EntryPoint ep = new EntryPoint();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); @@ -100,12 +119,12 @@ } public void testOrdinalizableFinalFieldUninitializedByDefault() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("private final Fruit uninitializedFinalFruit;", - "public EntryPoint() {", - " uninitializedFinalFruit = Fruit.ORANGE;", - "}"); + "public EntryPoint() {", + " uninitializedFinalFruit = Fruit.ORANGE;", + "}"); optimize("void", "EntryPoint ep = new EntryPoint();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); @@ -114,18 +133,18 @@ } public void testOrdinalizeSwitchStatement() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); setupFruitSwitchMethod(); optimize("void", "String apple = fruitSwitch(Fruit.APPLE);", - "String orange = fruitSwitch(Fruit.ORANGE);"); + "String orange = fruitSwitch(Fruit.ORANGE);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizeIfStatement() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl( "public static String fruitIf(Fruit fruit) {", @@ -138,14 +157,14 @@ " }", "}"); optimize("void", "String apple = fruitIf(Fruit.APPLE);", - "String orange = fruitIf(Fruit.ORANGE);"); + "String orange = fruitIf(Fruit.ORANGE);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizeConditional() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit = (true) ? Fruit.APPLE : Fruit.ORANGE;"); @@ -154,7 +173,7 @@ } public void testOrdinalizeFieldRefOrdinalMethodCall() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "int i = Fruit.APPLE.ordinal();"); @@ -163,10 +182,10 @@ } public void testOrdinalizeVariableRefOrdinalMethodCall() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "int i = fruit.ordinal();"); + "int i = fruit.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); @@ -178,7 +197,8 @@ optimize("void", "EmptyEnum myEnum;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); - assertTrue(tracker.isOrdinalized("test.EntryPoint$EmptyEnum")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$EmptyEnum") || + !tracker.isVisited("test.EntryPoint$EmptyEnum")); } public void testOrdinalizeUnusedEnum() throws UnableToCompleteException { @@ -187,84 +207,98 @@ optimize("void", "Fruit myEnum;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); - assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit") || + !tracker.isVisited("test.EntryPoint$Fruit")); } public void testOrdinalizeMethodCallExpressionOrdinalFieldRef() throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static Fruit getResolvedFruit(Fruit fruit) {", - " if (fruit == Fruit.APPLE) {", - " return Fruit.ORANGE;", - " } else { ", - " return Fruit.APPLE;", - " }", - "}"); + " 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;", - "}"); + " 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);"); + "Fruit fruit = Fruit.ORANGE;", + "int j = switchMethodCall(fruit);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizableStaticFieldRef() - throws UnableToCompleteException { + throws UnableToCompleteException { // this will cause a static field ref in the enum clinit setupFruitEnumWithStaticField(); - optimize("void", "String y = Fruit.staticField;"); + optimize("void", "String y = Fruit.staticField + Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testOrdinalizableStaticMethod() - throws UnableToCompleteException { + throws UnableToCompleteException { // this will cause a static method enum class setupFruitEnumWithStaticMethod(); - optimize("void", "int y = Fruit.staticMethod();"); + optimize("void", "int y = Fruit.staticMethod() + Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } - public void testNotOrdinalizableStaticMethodThatRefsValuesArray() - throws UnableToCompleteException { - // this will cause a static method that references an element - // of the values() array - setupFruitEnumWithStaticMethodThatRefsValuesArray(); - optimize("void", "Fruit y = Fruit.forInteger(0);"); + public void testOrdinalizableCallingValues() + throws UnableToCompleteException { + setupFruitEnum(); + optimize("void", "int l = Fruit.values().length;", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); - assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } - public void testNotOrdinalizableStaticMethodThatRefsValuesLength() - throws UnableToCompleteException { - // this will cause a static method that references values().length - setupFruitEnumWithStaticMethodThatRefsValuesLength(); - optimize("void", "Fruit y = Fruit.forInteger(0);"); + public void testOrdinalizableStaticFieldRefToVALUES() + throws UnableToCompleteException { + // this ends up inlining the values() method call, and thus $VALUES is referenced external + // to the Fruit enum class. + setupFruitEnum(); + optimize("void", "Fruit[] fruits = Fruit.values();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); - assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testOrdinalizableStaticMethodThatRefsValuesLength() + throws UnableToCompleteException { + // this will cause a static method that references values().length + setupFruitEnumWithStaticMethodThatRefsValuesLength(); + optimize("void", "Fruit y = Fruit.forInteger(0);", + "int ord = Fruit.APPLE.ordinal();"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit")); } public void testNotOrdinalizableInstanceStaticFieldRef() - throws UnableToCompleteException { + throws UnableToCompleteException { // this will cause a static field ref in the enum clinit setupFruitEnumWithStaticField(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "String y = fruit.staticField;"); + "String y = fruit.staticField;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -272,11 +306,11 @@ } public void testNotOrdinalizableInstanceStaticMethod() - throws UnableToCompleteException { + throws UnableToCompleteException { // this will cause a static method enum class setupFruitEnumWithStaticMethod(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "int y = fruit.staticMethod();"); + "int y = fruit.staticMethod();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -284,10 +318,10 @@ } public void testNotOrdinalizableClassLiteralReference() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Class clazz = Fruit.class;", - "String clazzStr = clazz.toString();"); + "String clazzStr = clazz.toString() + Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -295,10 +329,10 @@ } public void testNotOrdinalizableEnumValueOfWithClassLiteralArg() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Object Carrot = Enum.valueOf(Fruit.class, \"APPLE\");", - "String carrot = Carrot.toString();"); + "String carrot = Carrot.toString() + Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -306,10 +340,10 @@ } public void testNotOrdinalizableGetClassMethodCall() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Class clazz = Fruit.APPLE.getClass();", - "String clazzStr = clazz.toString();"); + "String clazzStr = clazz.toString() + Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -317,10 +351,11 @@ } public void testNotOrdinalizableExplicitCastToEnumClass() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Object obj = new Object();", - "Fruit fruit = (Fruit) obj;"); + "Fruit fruit = (Fruit) obj;", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -328,10 +363,11 @@ } public void testNotOrdinalizableExplicitCastToArrayOfEnumClass() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Enum[] enumArray = new Enum[10];", - "Fruit[] fruitArray = (Fruit[]) enumArray;"); + "Fruit[] fruitArray = (Fruit[]) enumArray;", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -339,10 +375,11 @@ } public void testNotOrdinalizableExplicitCastFromArrayOfEnumClass() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit[] fruitArray = new Fruit[10];", - "Enum[] enumArray = (Enum[]) fruitArray;"); + "Enum[] enumArray = (Enum[]) fruitArray;", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -350,10 +387,11 @@ } public void testNotOrdinalizableExplicitCastToArrayOfArrayOfEnumClass() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Enum[][] enumArray = new Enum[10][10];", - "Fruit[][] fruitArray = (Fruit[][]) enumArray;"); + "Fruit[][] fruitArray = (Fruit[][]) enumArray;", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -361,10 +399,11 @@ } public void testNotOrdinalizableExplicitCastFromArrayOfArrayOfEnumClass() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit[][] fruitArray = new Fruit[10][10];", - "Enum[][] enumArray = (Enum[][]) fruitArray;"); + "Enum[][] enumArray = (Enum[][]) fruitArray;", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -372,10 +411,10 @@ } public void testNotOrdinalizableExplicitCastFromEnumClass() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Enum Carrot = (Enum) Fruit.APPLE;", - "String carrot = Carrot.toString();"); + "String carrot = Carrot.toString();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -383,10 +422,11 @@ } public void testNotOrdinalizableOrdinalMethodRefFromExplicitCastWithBlackListableSubExpression() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "int ord = " + - "((Fruit) Enum.valueOf(Fruit.class,\"APPLE\")).ordinal();"); + "((Fruit) Enum.valueOf(Fruit.class,\"APPLE\")).ordinal();", + "int ord2 = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -394,10 +434,10 @@ } public void testNotOrdinalizableInstanceFieldRef() - throws UnableToCompleteException { + throws UnableToCompleteException { // this will cause an instance field ref in the enum constructor setupFruitEnumWithInstanceField(); - optimize("void"); + optimize("void", "String instanceField = Fruit.APPLE.instanceField;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -405,12 +445,12 @@ } public void testNotOrdinalizableInstanceOfEnumExpression() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "if (fruit instanceof Enum) {", - " fruit = Fruit.ORANGE;", - "}"); + "if (fruit instanceof Enum) {", + " fruit = Fruit.ORANGE;", + "}"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -418,24 +458,13 @@ } public void testNotOrdinalizableInstanceOfEnumTestType() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Object fruitObj = new Object();", - "if (fruitObj instanceof Fruit) {", - " fruitObj = null;", - "}"); - - EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); - assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); - assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); - } - - public void testNotOrdinalizableStaticFieldRefToVALUES() - throws UnableToCompleteException { - // this ends up inlining the values() method call, and thus $VALUES is referenced external - // to the Fruit enum class. - setupFruitEnum(); - optimize("void", "Fruit[] fruits = Fruit.values();"); + "if (fruitObj instanceof Fruit) {", + " fruitObj = null;", + "}", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -443,12 +472,13 @@ } public void testNotOrdinalizableStaticMethodCallValues() - throws UnableToCompleteException { + throws UnableToCompleteException { // make sure values() method call doesn't doesn't get inlined runMethodInliner = false; setupFruitEnum(); - optimize("void", "Fruit[] fruits = Fruit.values();"); + optimize("void", "Fruit[] fruits = Fruit.values();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -456,14 +486,15 @@ } public void testNotOrdinalizableJsniFieldRef() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static Fruit instanceFruit;"); addSnippetClassDecl("public static native void jsniMethod() /*-{", - " var x = @test.EntryPoint::instanceFruit", - "}-*/"); + " var x = @test.EntryPoint::instanceFruit", + "}-*/"); optimize("void", "instanceFruit = Fruit.APPLE;", - "jsniMethod();"); + "jsniMethod();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -471,12 +502,13 @@ } public void testNotOrdinalizableJsniFieldRefStatic() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static native void jsniMethod() /*-{", - " var x = @test.EntryPoint.Fruit::APPLE", - "}-*/"); - optimize("void", "jsniMethod();"); + " var x = @test.EntryPoint.Fruit::APPLE", + "}-*/"); + optimize("void", "jsniMethod();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -484,12 +516,13 @@ } public void testNotOrdinalizableJsniFieldRefClassLiteral() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static native void jsniMethod() /*-{", - " var x = @test.EntryPoint.Fruit::class", - "}-*/"); - optimize("void", "jsniMethod();"); + " var x = @test.EntryPoint.Fruit::class", + "}-*/"); + optimize("void", "jsniMethod();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -497,10 +530,12 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpAssignment() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); - optimize("void", "Enum tomato;", - "tomato = Fruit.APPLE;"); + optimize("void", + "Enum tomato;", + "tomato = Fruit.APPLE;", + "int ord = tomato.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -508,12 +543,13 @@ } public void testNotOrdinalizableImplicitUpcastFieldInitializedWithNullByDefault() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("static private Fruit uninitializedFruitAsNull;"); optimize("void", "if (uninitializedFruitAsNull != Fruit.APPLE) {", - " uninitializedFruitAsNull = Fruit.ORANGE;", - "}"); + " uninitializedFruitAsNull = Fruit.ORANGE;", + "}", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -521,12 +557,12 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpEquals() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); setupVegetableEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "Enum carrot = (Enum) Vegetable.CARROT;", - "boolean test = (fruit == carrot);"); + "Enum carrot = (Enum) Vegetable.CARROT;", + "boolean test = (fruit == carrot);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -536,13 +572,13 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpNotEquals() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); setupVegetableEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "Enum carrot = (Enum) Vegetable.CARROT;", - // do in opposite order from OpEquals test - "boolean test = (carrot != fruit);"); + "Enum carrot = (Enum) Vegetable.CARROT;", + // do in opposite order from OpEquals test + "boolean test = (carrot != fruit);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -552,17 +588,17 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpEqualsNull() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static boolean testIsNull(Fruit fruit) {", - " if (fruit == null) {", - " return true;", - " } else {", - " return false;", - " }", - "}"); + " if (fruit == null) {", + " return true;", + " } else {", + " return false;", + " }", + "}"); optimize("void", "Fruit fruit = Fruit.APPLE;", - "boolean isNull = testIsNull(fruit) || testIsNull(Fruit.ORANGE);"); + "boolean isNull = testIsNull(fruit) || testIsNull(Fruit.ORANGE);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -570,17 +606,17 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpNotEqualsNull() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static boolean testIsNull(Fruit fruit) {", - " if (fruit != null) {", - " return true;", - " } else {", - " return false;", - " }", - "}"); + " if (fruit != null) {", + " return true;", + " } else {", + " return false;", + " }", + "}"); optimize("void", "Fruit fruit = Fruit.APPLE;", - "boolean isNull = testIsNull(fruit) || testIsNull(Fruit.ORANGE);"); + "boolean isNull = testIsNull(fruit) || testIsNull(Fruit.ORANGE);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -588,10 +624,10 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcat() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "String str = \"A string followed by \" + fruit;"); + "String str = \"A string followed by \" + fruit;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -599,10 +635,10 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcat2() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "String str = fruit + \" followed by a string\";"); + "String str = fruit + \" followed by a string\";"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -610,11 +646,11 @@ } public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcatAssignment() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit = Fruit.APPLE;", - "String str = \"A string concatenated with: \";", - "str += fruit;"); + "String str = \"A string concatenated with: \";", + "str += fruit;"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -622,9 +658,10 @@ } public void testNotOrdinalizableImplicitUpcastDeclarationToNull() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); - optimize("void", "Fruit fruit = null;"); + optimize("void", "Fruit fruit = null;", + "int ord = fruit == null ? Fruit.APPLE.ordinal() : fruit.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -632,10 +669,11 @@ } public void testNotOrdinalizableImplicitUpcastAssignmentToNull() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Fruit fruit;", - "fruit = null;"); + "fruit = null;", + "int ord = fruit == null ? Fruit.APPLE.ordinal() : fruit.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -643,9 +681,10 @@ } public void testNotOrdinalizableImplicitUpcastDeclaration() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); - optimize("void", "Enum tomato = Fruit.APPLE;"); + optimize("void", "Enum tomato = Fruit.APPLE;", + "int ord = Fruit.APPLE.ordinal() + tomato.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -653,21 +692,22 @@ } public void testNotOrdinalizableImplicitUpcastConditional() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); setupVegetableEnum(); optimize("void", "Enum tomato = null;", - "tomato = (true) ? Fruit.APPLE : Vegetable.CARROT;"); + "tomato = (true) ? Fruit.APPLE : Vegetable.CARROT;", + "int ord = Fruit.APPLE.ordinal() + Vegetable.CARROT.ordinal() + tomato.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); assertTrue(tracker.isVisited("test.EntryPoint$Vegetable")); - assertFalse(tracker.isOrdinalized("test.EntryPoint$Vegetable")); + assertTrue(tracker.isOrdinalized("test.EntryPoint$Vegetable")); } public void testNotOrdinalizableImplicitUpcastOverriddenMethodReturnType() - throws UnableToCompleteException { + throws UnableToCompleteException { // this test depends on the tighteners running runTypeTightener = true; @@ -679,31 +719,31 @@ * overridden method type. */ addSnippetClassDecl("public interface EnumInterface {", - " String name();", - "}"); + " String name();", + "}"); addSnippetClassDecl("public abstract class AbstractClass<T extends EnumInterface> {", - " public abstract T getEnumClass();", - "}"); + " public abstract T getEnumClass();", + "}"); addSnippetClassDecl("public class CustomClass1 extends AbstractClass<EnumClass1> {", - " public EnumClass1 getEnumClass() { return EnumClass1.CONST1; }", - "}"); + " public EnumClass1 getEnumClass() { return EnumClass1.CONST1; }", + "}"); addSnippetClassDecl("public class CustomClass2 extends AbstractClass<EnumClass2> {", - " public EnumClass2 getEnumClass() { return EnumClass2.CONST2; }", - "}"); + " public EnumClass2 getEnumClass() { return EnumClass2.CONST2; }", + "}"); addSnippetClassDecl("public enum EnumClass1 implements EnumInterface {", - " CONST1;", - "}"); + " CONST1;", + "}"); addSnippetClassDecl("public enum EnumClass2 implements EnumInterface {", - " CONST2;", - "}"); + " CONST2;", + "}"); addSnippetClassDecl("public static void testEnumClass(AbstractClass abstractClass) {", - " EnumInterface enumClass = abstractClass.getEnumClass();", - "}"); + " EnumInterface enumClass = abstractClass.getEnumClass();", + "}"); optimize("void", "EntryPoint ep = new EntryPoint();", - "AbstractClass abstractClass1 = ep.new CustomClass1();", - "AbstractClass abstractClass2 = ep.new CustomClass2();", - "testEnumClass(abstractClass1);", - "testEnumClass(abstractClass2);"); + "AbstractClass abstractClass1 = ep.new CustomClass1();", + "AbstractClass abstractClass2 = ep.new CustomClass2();", + "testEnumClass(abstractClass1);", + "testEnumClass(abstractClass2);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$EnumClass1")); @@ -713,18 +753,18 @@ } public void testNotOrdinalizableImplicitUpcastMethodCallArgs() - throws UnableToCompleteException { + throws UnableToCompleteException { 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;", - "}"); + // 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(); @@ -733,16 +773,17 @@ } public void testNotOrdinalizableImplicitUpcastMethodCallArgsNewArray() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static String getEnumString(Enum[] myEnumArray) {", - " String retString = \"\";", - " for (Enum myEnum : myEnumArray) {", - " retString += myEnum.name();", - " }", - " return retString;", - "}"); - optimize("void", "String stringFruits = getEnumString(new Enum[] {Fruit.APPLE, Fruit.ORANGE});"); + " String retString = \"\";", + " for (Enum myEnum : myEnumArray) {", + " retString += myEnum.name();", + " }", + " return retString;", + "}"); + optimize("void", + "String stringFruits = getEnumString(new Enum[] {Fruit.APPLE, Fruit.ORANGE});"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -750,15 +791,15 @@ } public void testNotOrdinalizableImplicitUpcastMethodCallVarArgs() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static String getEnumString(Enum...myEnumArray) {", - " String retString = \"\";", - " for (Enum myEnum : myEnumArray) {", - " retString += myEnum.name();", - " }", - " return retString;", - "}"); + " String retString = \"\";", + " for (Enum myEnum : myEnumArray) {", + " retString += myEnum.name();", + " }", + " return retString;", + "}"); optimize("void", "String stringFruits = getEnumString(Fruit.APPLE, Fruit.ORANGE);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); @@ -767,7 +808,7 @@ } public void testNotOrdinalizableImplicitUpcastNewArrayElements() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); optimize("void", "Enum[] enums = new Enum[] {Fruit.APPLE, Fruit.ORANGE};"); @@ -777,20 +818,21 @@ } public void testNotOrdinalizableImplicitUpcastNewArrayArrayElements() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); - optimize("void", "Enum[][] enums = new Enum[][] {{Fruit.APPLE, Fruit.ORANGE},{Fruit.ORANGE, Fruit.APPLE}};"); + optimize("void", + "Enum[][] enums = new Enum[][] {{Fruit.APPLE, Fruit.ORANGE},{Fruit.ORANGE, Fruit.APPLE}};"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); } - public void testNotOrdinalizableImplicitUpcastJsniMethodBodyParams() - throws UnableToCompleteException { + public void testNotOrdinalizableJsniMethodBodyParams() + throws UnableToCompleteException { setupFruitEnum(); - addSnippetClassDecl("public static native void passEnumToJsniMethod(Fruit myEnum) /*-{", - "}-*/"); + addSnippetClassDecl("public static native void passEnumToJsniMethod(Enum myEnum) /*-{", + " myEnum == null; }-*/"); optimize("void", "passEnumToJsniMethod(Fruit.APPLE);"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); @@ -798,14 +840,29 @@ assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); } - public void testNotOrdinalizableImplicitUpcastJsniMethodBodyReturnType() - throws UnableToCompleteException { + public void testNotOrdinalizableImplicitUpcastJsniMethodBodyParams() + throws UnableToCompleteException { setupFruitEnum(); - addSnippetClassDecl("public static native Fruit returnFruitViaJsni() /*-{", - " var myJso;", - " return myJso;", - "}-*/"); - optimize("void", "Fruit fruit = returnFruitViaJsni();"); + addSnippetClassDecl("public static native void passEnumToJsniMethod(Fruit myEnum) /*-{", + " myEnum == null; }-*/;"); + optimize("void", "passEnumToJsniMethod(Fruit.APPLE);"); + + EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); + assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); + assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit")); + } + + public void testNotOrdinalizableJsniMethodBodyCall() + throws UnableToCompleteException { + setupFruitEnum(); + addSnippetClassDecl("public static native void consumeFruitViaJsni() /*-{", + " var myJso = @test.EntryPoint::calledFromJsni(*)();", + "}-*/;", + "public static Fruit calledFromJsni() {", + " return Fruit.APPLE;", + "}"); + optimize("void", "consumeFruitViaJsni();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -813,14 +870,15 @@ } public void testNotOrdinalizableImplicitUpcastJsniMethodRefParams() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); setupFruitSwitchMethod(); addSnippetClassDecl("public static native void fruitSwitchViaJsni() /*-{", - " var myJso;", - " var result = @test.EntryPoint::fruitSwitch(Ltest/EntryPoint$Fruit;)(myJso);", - "}-*/"); - optimize("void", "fruitSwitchViaJsni();"); + " var myJso;", + " var result = @test.EntryPoint::fruitSwitch(Ltest/EntryPoint$Fruit;)(myJso);", + "}-*/"); + optimize("void", "fruitSwitchViaJsni();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -828,15 +886,16 @@ } public void testNotOrdinalizableImplicitUpcastJsniMethodRefReturnType() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); addSnippetClassDecl("public static Fruit returnSomeFruit() {", - " return Fruit.APPLE;", - "}"); + " return Fruit.APPLE;", + "}"); addSnippetClassDecl("public static native void jsniMethodRefWithEnumReturn() /*-{", - " var result = @test.EntryPoint::returnSomeFruit()();", - "}-*/"); - optimize("void", "jsniMethodRefWithEnumReturn();"); + " var result = @test.EntryPoint::returnSomeFruit()();", + "}-*/"); + optimize("void", "jsniMethodRefWithEnumReturn();", + "int ord = Fruit.APPLE.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -844,19 +903,20 @@ } public void testNotOrdinalizableImplicitUpcastReturnStatement() - throws UnableToCompleteException { + throws UnableToCompleteException { setupFruitEnum(); setupVegetableEnum(); addSnippetClassDecl("public static Enum returnAsEnum(int mode) {", - " if (mode == 0) {", - " return Fruit.APPLE;", - " } else {", - " return Vegetable.CARROT;", - " }", - "}"); + " 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);"); + // do a second one, to prevent inlining + "Enum myOtherEnum = returnAsEnum(1);", + "int ord = Fruit.APPLE.ordinal() + Vegetable.CARROT.ordinal();"); EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker(); assertTrue(tracker.isVisited("test.EntryPoint$Fruit")); @@ -875,49 +935,49 @@ private void setupFruitEnumWithInstanceField() { addSnippetClassDecl("public enum Fruit {APPLE(\"a\"), ORANGE(\"b\");", - " public final String instanceField;", - " private Fruit(String str) {", - " instanceField = str;", - " }", - "}"); + " public final String instanceField;", + " private Fruit(String str) {", + " instanceField = str;", + " }", + "}"); } private void setupFruitEnumWithStaticField() { addSnippetClassDecl("public enum Fruit {APPLE, ORANGE;", - " public static final String staticField = \"STATIC\";", - "}"); + " public static final String staticField = \"STATIC\";", + "}"); } private void setupFruitEnumWithStaticMethod() { addSnippetClassDecl("public enum Fruit {APPLE, ORANGE;", - " public static final int staticMethod() {", - " int x = 0;", - " return x;", - " }", - "}"); + " public static final int staticMethod() {", + " int x = 0;", + " return x;", + " }", + "}"); } private void setupFruitEnumWithStaticMethodThatRefsValuesArray() { // add a little extra logic here, to prevent inlining addSnippetClassDecl("public enum Fruit {APPLE, ORANGE;", - " public static Fruit forInteger(int value) {", - " if (value < 0 || value >= 2) {", - " return ORANGE;", - " }", - " return Fruit.values()[value];", - " }", - "}"); + " public static Fruit forInteger(int value) {", + " if (value < 0 || value >= 2) {", + " return ORANGE;", + " }", + " return Fruit.values()[value];", + " }", + "}"); } private void setupFruitEnumWithStaticMethodThatRefsValuesLength() { addSnippetClassDecl("public enum Fruit {APPLE, ORANGE;", - " public static Fruit forInteger(int value) {", - " if (value < 0 || value >= Fruit.values().length) {", - " return ORANGE;", - " }", - " return APPLE;", - " }", - "}"); + " public static Fruit forInteger(int value) {", + " if (value < 0 || value >= Fruit.values().length) {", + " return ORANGE;", + " }", + " return APPLE;", + " }", + "}"); } private void setupVegetableEnum() { @@ -926,29 +986,14 @@ private void setupFruitSwitchMethod() { addSnippetClassDecl("public static String fruitSwitch(Fruit fruit) {", - " switch(fruit) {", - " case APPLE: return \"Apple\";", - " case ORANGE: return \"Orange\";", - " default: return \"Unknown\";", - " }", - "}"); + " switch(fruit) {", + " case APPLE: return \"Apple\";", + " case ORANGE: return \"Orange\";", + " default: return \"Unknown\";", + " }", + "}"); } - /* - * Always run ImplementCastsAndTypeChecks 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 performCastReplacement = true; - private final boolean runEqualityNormalizer = true; - - // These are enabled as needed for a given test - private boolean runMakeCallsStatic; - private boolean runMethodInliner; - private boolean runMethodCallTightener; - private boolean runTypeTightener; - @Override protected boolean optimizeMethod(JProgram program, JMethod method) { /* @@ -962,32 +1007,25 @@ * These are a subset of the actual optimizers run in JJS.optimizeLoop(). */ boolean didChange = false; - AstDumper.maybeDumpAST(program, "EnumOrdinalizerTest_start"); + program.addEntryMethod(findMainMethod(program)); if (runMakeCallsStatic) { didChange = MakeCallsStatic.exec(new JJSOptionsImpl(), 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"); + } + if (runPruner) { + didChange = Pruner.exec(program, true).didChange() || didChange; } didChange = EnumOrdinalizer.exec(program).didChange() || didChange; - AstDumper.maybeDumpAST(program, - "EnumOrdinalizerTest_after_EnumOrdinalizer"); /* * Run these normalizers to sanity check the AST. If there are any
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java index 3af876d..35a4c42 100644 --- a/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
@@ -218,6 +218,27 @@ SomeParentParentParent.callSomeParentParentParentM(new SomeSubSubClassInAnotherPackage())); } + enum MyEnum { + A, + B, + C; + + public final static MyEnum[] VALUES = values(); + + public int getPriority() { + return VALUES.length - ordinal(); + } + } + + /** + * Tests that enum ordinalizer does not incorrectly optimize {@code MyEnum}. + * <p> + * Test for issue 8846:. + */ + public void testMyEnum() { + assertEquals(2, MyEnum.B.getPriority()); + } + private static void assertEqualContents(float[] expected, float[] actual) { assertEquals("Array length mismatch", expected.length, actual.length);