blob: bce91d07b02ad47afaae1d28ababf26f3e0616cc [file] [log] [blame]
/*
* 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.
*/
public class EnumOrdinalizerTest extends OptimizerTestBase {
@Override
protected void setUp() throws Exception {
super.setUp();
EnumOrdinalizer.enableTracker();
// defaults, can be overridden by individual test cases
runTypeTightener = false;
runMethodCallTightener = false;
runMethodInliner = true;
runMakeCallsStatic = true;
}
public void testOrdinalizeBasicAssignment()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit apple = Fruit.APPLE;",
"Fruit orange = Fruit.ORANGE;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeNewArrayAndAssignmentLocalRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeNewArrayOfArrayAndAssignmentLocalRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeNewArrayAndAssignmentFieldRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizableFinalFieldUninitializedByDefault()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("private final Fruit uninitializedFinalFruit;",
"public EntryPoint() {",
" uninitializedFinalFruit = Fruit.ORANGE;",
"}");
optimize("void", "EntryPoint ep = new EntryPoint();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeSwitchStatement()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
setupFruitSwitchMethod();
optimize("void", "String apple = fruitSwitch(Fruit.APPLE);",
"String orange = fruitSwitch(Fruit.ORANGE);");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeIfStatement()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeConditional()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit = (true) ? Fruit.APPLE : Fruit.ORANGE;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeFieldRefOrdinalMethodCall()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "int i = Fruit.APPLE.ordinal();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeVariableRefOrdinalMethodCall()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"int i = fruit.ordinal();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeUnusedEmptyEnum() throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupEmptyEnum();
optimize("void", "EmptyEnum myEnum;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$EmptyEnum"));
}
public void testOrdinalizeUnusedEnum() throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit myEnum;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizeMethodCallExpressionOrdinalFieldRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizableStaticFieldRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
// this will cause a static field ref in the enum clinit
setupFruitEnumWithStaticField();
optimize("void", "String y = Fruit.staticField;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testOrdinalizableStaticMethod()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
// this will cause a static method enum class
setupFruitEnumWithStaticMethod();
optimize("void", "int y = Fruit.staticMethod();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableInstanceStaticFieldRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
// this will cause a static field ref in the enum clinit
setupFruitEnumWithStaticField();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"String y = fruit.staticField;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableInstanceStaticMethod()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
// this will cause a static method enum class
setupFruitEnumWithStaticMethod();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"int y = fruit.staticMethod();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableClassLiteralReference()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Class clazz = Fruit.class;",
"String clazzStr = clazz.toString();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableEnumValueOfWithClassLiteralArg()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Object Carrot = Enum.valueOf(Fruit.class, \"APPLE\");",
"String carrot = Carrot.toString();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableGetClassMethodCall()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Class clazz = Fruit.APPLE.getClass();",
"String clazzStr = clazz.toString();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableExplicitCastToEnumClass()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Object obj = new Object();",
"Fruit fruit = (Fruit) obj;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableExplicitCastToArrayOfEnumClass()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Enum[] enumArray = new Enum[10];",
"Fruit[] fruitArray = (Fruit[]) enumArray;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableExplicitCastFromArrayOfEnumClass()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit[] fruitArray = new Fruit[10];",
"Enum[] enumArray = (Enum[]) fruitArray;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableExplicitCastToArrayOfArrayOfEnumClass()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Enum[][] enumArray = new Enum[10][10];",
"Fruit[][] fruitArray = (Fruit[][]) enumArray;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableExplicitCastFromArrayOfArrayOfEnumClass()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit[][] fruitArray = new Fruit[10][10];",
"Enum[][] enumArray = (Enum[][]) fruitArray;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableExplicitCastFromEnumClass()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Enum Carrot = (Enum) Fruit.APPLE;",
"String carrot = Carrot.toString();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableOrdinalMethodRefFromExplicitCastWithBlackListableSubExpression()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "int ord = " +
"((Fruit) Enum.valueOf(Fruit.class,\"APPLE\")).ordinal();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableInstanceFieldRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
// this will cause an instance field ref in the enum constructor
setupFruitEnumWithInstanceField();
optimize("void");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableInstanceOfEnumExpression()
throws UnableToCompleteException {
setupFruitEnum();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"if (fruit instanceof Enum) {",
" fruit = Fruit.ORANGE;",
"}");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableInstanceOfEnumTestType()
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 {
EnumOrdinalizer.resetTracker();
// 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();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableStaticMethodCallValues()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
// make sure values() method call doesn't doesn't get inlined
runMethodInliner = false;
setupFruitEnum();
optimize("void", "Fruit[] fruits = Fruit.values();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableJsniFieldRef()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableJsniFieldRefStatic()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("public static native void jsniMethod() /*-{",
" var x = @test.EntryPoint.Fruit::APPLE",
"}-*/");
optimize("void", "jsniMethod();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableJsniFieldRefClassLiteral()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("public static native void jsniMethod() /*-{",
" var x = @test.EntryPoint.Fruit::class",
"}-*/");
optimize("void", "jsniMethod();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpAssignment()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Enum tomato;",
"tomato = Fruit.APPLE;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastFieldInitializedWithNullByDefault()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("static private Fruit uninitializedFruitAsNull;");
optimize("void", "if (uninitializedFruitAsNull != Fruit.APPLE) {",
" uninitializedFruitAsNull = Fruit.ORANGE;",
"}");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpEquals()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
setupVegetableEnum();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"Enum carrot = (Enum) Vegetable.CARROT;",
"boolean test = (fruit == carrot);");
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"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpNotEquals()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
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.resetTracker();
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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpNotEqualsNull()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcat()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"String str = \"A string followed by \" + fruit;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcat2()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"String str = fruit + \" followed by a string\";");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastBinaryOpStringConcatAssignment()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit = Fruit.APPLE;",
"String str = \"A string concatenated with: \";",
"str += fruit;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastDeclarationToNull()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit = null;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastAssignmentToNull()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Fruit fruit;",
"fruit = null;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastDeclaration()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Enum tomato = Fruit.APPLE;");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastConditional()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
setupVegetableEnum();
optimize("void", "Enum tomato = null;",
"tomato = (true) ? Fruit.APPLE : Vegetable.CARROT;");
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"));
}
public void testNotOrdinalizableImplicitUpcastOverriddenMethodReturnType()
throws UnableToCompleteException {
// this test depends on the tighteners running
runTypeTightener = true;
runMethodCallTightener = true;
EnumOrdinalizer.resetTracker();
/*
* 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();
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.resetTracker();
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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastMethodCallArgsNewArray()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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});");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastMethodCallVarArgs()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("public static String getEnumString(Enum...myEnumArray) {",
" 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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastNewArrayElements()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
optimize("void", "Enum[] enums = new Enum[] {Fruit.APPLE, Fruit.ORANGE};");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastNewArrayArrayElements()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
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 {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("public static native void passEnumToJsniMethod(Fruit myEnum) /*-{",
"}-*/");
optimize("void", "passEnumToJsniMethod(Fruit.APPLE);");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastJsniMethodBodyReturnType()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
setupFruitEnum();
addSnippetClassDecl("public static native Fruit returnFruitViaJsni() /*-{",
" var myJso;",
" return myJso;",
"}-*/");
optimize("void", "Fruit fruit = returnFruitViaJsni();");
EnumOrdinalizer.Tracker tracker = EnumOrdinalizer.getTracker();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastJsniMethodRefParams()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastJsniMethodRefReturnType()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
assertTrue(tracker.isVisited("test.EntryPoint$Fruit"));
assertFalse(tracker.isOrdinalized("test.EntryPoint$Fruit"));
}
public void testNotOrdinalizableImplicitUpcastReturnStatement()
throws UnableToCompleteException {
EnumOrdinalizer.resetTracker();
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();
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 setupEmptyEnum() {
addSnippetClassDecl("public enum EmptyEnum {}");
}
private void setupFruitEnum() {
addSnippetClassDecl("public enum Fruit {APPLE, ORANGE}");
}
private void setupFruitEnumWithInstanceField() {
addSnippetClassDecl("public enum Fruit {APPLE(\"a\"), ORANGE(\"b\");",
" 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\";",
"}");
}
private void setupFruitEnumWithStaticMethod() {
addSnippetClassDecl("public enum Fruit {APPLE, ORANGE;",
" public static final int staticMethod() {",
" int x = 0;",
" return x;",
" }",
"}");
}
private void setupVegetableEnum() {
addSnippetClassDecl("public enum Vegetable {CARROT, SPINACH}");
}
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;
// 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) {
/*
* 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;
}
}