blob: 85109e53006b8f6a71d100be134157299d69baae [file] [log] [blame]
/*
* Copyright 2014 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.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
/**
* Test for {@link TypeTightener}.
*/
public class TypeTightenerTest extends OptimizerTestBase {
@Override
protected void setUp() throws Exception {
super.setUp();
}
public void testTightenToNullType() throws Exception {
Result result = optimize("void", "String s = null;");
result.intoString("null s = null;");
}
public void testBinaryOperation() throws Exception {
Result result = optimize("void", "String s = null; String x = s + s;");
result.intoString("null s = null;", "String x = s + s;");
}
public void testBinaryOperation_nullField() throws Exception {
addSnippetClassDecl(
"static class A {",
" String name;",
"}");
Result result = optimize("void", "A a = null; a.name += null;");
result.intoString("null a = null;", "null.nullField += null;");
}
public void testBinaryOperation_notNullable() throws Exception {
optimize("void", "String other; String s = \"\"; s += null; if (s == null) { other = \"\"; } ")
.intoString("null other;", "String s = \"\";", "s += null;");
}
public void testBinaryOperation_nullableButNotNull() throws Exception {
optimize("void", "String s = null; s += null;")
.intoString("String s = null;", "s += null;");
}
public void testCastOperation() throws Exception {
addSnippetClassDecl("static class A { " + "String name; public void set() { name = \"A\";} }");
addSnippetClassDecl("static class B extends A {" + "public void set() { name = \"B\";} }");
addSnippetClassDecl("static class C extends A {" + "public void set() { name = \"C\";} }");
Result result =
optimize("void", "A b = new B();", "((A)b).set();", "((B)b).set();", "((C)b).set();");
result.intoString("EntryPoint$B b = new EntryPoint$B();", "b.set();", "b.set();",
"((null) b).nullMethod();");
}
public void testConditional() throws Exception {
addSnippetClassDecl("static class D {}");
addSnippetClassDecl("static class C extends D {}");
addSnippetClassDecl("static class B extends C {}");
addSnippetClassDecl("static class A extends C {}");
Result result = optimize("void", "int x = 0;", "A a1 = new A();", "A a2 = new A();",
"B b = new B();", "C c = new C();", "C c1 = (x > 0) ? a1 : b;", "C c2 = (x > 0) ? a1 : a2;",
"D d = (x > 0) ? b : c;");
result.intoString("int x = 0;", "EntryPoint$A a1 = new EntryPoint$A();",
"EntryPoint$A a2 = new EntryPoint$A();", "EntryPoint$B b = new EntryPoint$B();",
"EntryPoint$C c = new EntryPoint$C();", "EntryPoint$C c1 = x > 0 ? a1 : b;",
"EntryPoint$A c2 = x > 0 ? a1 : a2;", "EntryPoint$C d = x > 0 ? b : c;");
}
public void testTightenLocalVariable() throws Exception {
addSnippetClassDecl("static abstract class B {};");
addSnippetClassDecl("static class C extends B{};");
addSnippetClassDecl("static class D extends C{};");
addSnippetClassDecl("static class E extends C{};");
Result result = optimize("void",
"B b;",
"b = new C();", "b = new D();", "b = new E();");
result.intoString("EntryPoint$C b;", "b = new EntryPoint$C();",
"b = new EntryPoint$D();", "b = new EntryPoint$E();");
}
public void testTightenField() throws Exception {
addSnippetClassDecl("static class A {};");
addSnippetClassDecl("static class B extends A{};");
addSnippetClassDecl("static class C extends B{};");
addSnippetClassDecl("static class D extends B{};");
addSnippetClassDecl("static class Test {private A a;"
+ "public void fun1() {a = new C();}"
+ "public void fun2() {a = new D();}"
+ "}");
Result result = optimize("void", "");
assertEquals("EntryPoint$B a",
OptimizerTestBase.findField(result.findClass("EntryPoint$Test"), "a").toString());
}
public void testTightenParameterBasedOnAssignment() throws Exception {
addSnippetClassDecl("static class A { }");
addSnippetClassDecl("static class B extends A { }");
addSnippetClassDecl("static class C extends B { }");
addSnippetClassDecl("static class D extends B { }");
addSnippetClassDecl("static void test(A a) { }");
Result result = optimize("void", "C c = new C(); D d = new D(); test(c); test(d);");
assertParameterTypes(result, "test", "EntryPoint$B");
}
public void testTightenParameterBasedOnLeafType() throws Exception {
addSnippetClassDecl("abstract static class A { void makeSureFunIsCalled() {fun(this);} }");
addSnippetClassDecl("static class B extends A {}");
addSnippetClassDecl("static void fun(A a) {if (a == null) return;}");
Result result = optimize("void", "");
assertParameterTypes(result, "fun", "EntryPoint$B");
}
public void testTightenParameterBasedOnOverriddenMethods() throws Exception {
addSnippetClassDecl("static class A {}");
addSnippetClassDecl("static class B extends A {}");
addSnippetClassDecl("static class Test1 { public void fun(A a) {} }");
addSnippetClassDecl("static class Test2 extends Test1 {public void fun (A a) {} }");
Result result = optimize("void", "B b = new B();"
+ "Test1 test1 = new Test1();"
+ "test1.fun(b);");
assertParameterTypes(result, "EntryPoint$Test1.fun(Ltest/EntryPoint$A;)V", "EntryPoint$B");
assertParameterTypes(result, "EntryPoint$Test2.fun(Ltest/EntryPoint$A;)V", "EntryPoint$B");
}
public void testTightenParameterWithAccidentalOverrides() throws Exception {
// Test the assumption that accidental overrides can be safely deleted if the super method
// is still in place. That requires that the super method parameters not to be (over) tightened.
addSnippetClassDecl("interface I { boolean m1(Object obj); boolean m2(Object obj);}");
addSnippetClassDecl("static class A { public boolean m1(Object obj) { return obj == null; } }");
addSnippetClassDecl(
"static class B extends A {",
" public boolean m2(Object obj) { return obj == null; }",
"}");
addSnippetClassDecl("static class C extends B implements I {}");
Result result = optimize("void",
"B b = new B();",
"b.m1(null);",
"b.m2(b);",
"C c = new C();",
"c.m1(c);",
"c.m2(c);");
assertParameterTypes(result, "EntryPoint$A.m1(Ljava/lang/Object;)Z", "Object");
// This one could be tighetened safely to EntryPoint$B but typetighener does not
// tighten parameters in polymorphic method calls.
assertParameterTypes(result, "EntryPoint$B.m2(Ljava/lang/Object;)Z", "Object");
}
public void testMethodBasedOnLeafType() throws Exception {
addSnippetClassDecl("abstract static class A { void makeSureFunIsCalled() {fun(this);} }");
addSnippetClassDecl("static class B extends A {}");
addSnippetClassDecl("static A fun(A a) {return a;}");
Result result = optimize("void", "");
assertParameterTypes(result, "fun", "EntryPoint$B");
assertReturnType(result, "fun", "EntryPoint$B");
}
public void testMethodBasedOnReturns() throws Exception {
addSnippetClassDecl("static class A {}");
addSnippetClassDecl("static class B extends A {}");
addSnippetClassDecl("static class C extends B {}");
addSnippetClassDecl("static class D extends B {}");
addSnippetClassDecl(
"A fun(int a) { if(a<0) return new B(); else if(a==0) return new C(); return new D();}");
Result result = optimize("void", "");
assertReturnType(result, "fun", "EntryPoint$B");
}
public void testMethodBasedOnOverriders() throws Exception {
addSnippetClassDecl("abstract static class A { abstract public A fun(); }");
addSnippetClassDecl("static class B extends A { public B fun() {return new B();} }");
addSnippetClassDecl("static class C extends A { public B fun() {return new B();} }");
addSnippetClassDecl("static class D extends B { public D fun() {return new D();} }");
Result result = optimize("void", "");
assertReturnType(result, "EntryPoint$A.fun()Ltest/EntryPoint$A;", "EntryPoint$B");;
}
public void testTightenDependsOnTightenedMethods() throws Exception {
addSnippetClassDecl("abstract static class A {}");
addSnippetClassDecl("static class B extends A {}");
addSnippetClassDecl("static A test() { return new B(); }");
// test should be tightened to be static EntryPoint$B test();
addSnippetClassDecl("static class C {A a;}");
addSnippetClassDecl("static void fun1(A a) {if (a == null) return;}");
addSnippetClassDecl("static A fun2() { return test(); }"); // tighten method return type
Result result = optimize("void",
"A a = test();" // tighten local varialbe.
+ "C c = new C(); c.a = test();" // tighten field
+ "fun1(test());" // tighten parameter
);
assertReturnType(result, "fun2()Ltest/EntryPoint$A;", "EntryPoint$B");
assertParameterTypes(result, "fun1", "EntryPoint$B");;
assertEquals("EntryPoint$B a",
OptimizerTestBase.findLocal(result.findMethod(MAIN_METHOD_NAME), "a").toString());
assertEquals("EntryPoint$B a",
OptimizerTestBase.findField(result.findClass("EntryPoint$C"), "a").toString());
}
public void testDoNotTighten_staticDispatch() throws Exception {
addSnippetClassDecl("abstract static class A { public void m() {} }");
addSnippetClassDecl("static class B extends A { public void m() { super.m(); }}");
Result result = optimize("void",
"new B().m();");
assertForwardsTo(result.findMethod("test.EntryPoint$B.m()V"),
result.findMethod("test.EntryPoint$A.m()V"));
}
public void testTightenInstanceOf_singleConcrete() throws Exception {
addSnippetClassDecl("abstract static class A { public void m() {} }");
addSnippetClassDecl("static class B extends A { public void m() { super.m(); }}");
optimize("void"," A a= new B(); if (a instanceof B) { a.m(); }")
.intoString("EntryPoint$B a = new EntryPoint$B();", "a.m();");
}
@Override
protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) {
program.addEntryMethod(findMainMethod(program));
boolean didChange = false;
while (TypeTightener.exec(program).didChange()) {
MethodCallTightener.exec(program);
DeadCodeElimination.exec(program);
didChange = true;
}
return didChange;
}
}