/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.dev.jjs.impl; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; import com.google.gwt.dev.util.arg.SourceLevel; /** * Test for {@link Pruner}. */ public class PrunerTest extends OptimizerTestBase { @Override protected void setUp() throws Exception { sourceLevel = SourceLevel.JAVA8; super.setUp(); runDeadCodeElimination = true; } public void testSmoke() throws Exception { addSnippetClassDecl("static int foo(int i) { return i; }"); addSnippetClassDecl("static void unusedMethod() { }"); addSnippetClassDecl("static void usedMethod() { }"); addSnippetClassDecl("static class UnusedClass { }"); addSnippetClassDecl("static class UninstantiatedClass { " + "int field; native int method() /*-{ return 1; }-*/; }"); addSnippetClassDecl("static UninstantiatedClass uninstantiatedField;"); addSnippetClassDecl("static int unusedField;"); addSnippetClassDecl("static int unreadField;"); addSnippetClassDecl("static int unassignedField;"); addSnippetClassDecl("static UninstantiatedClass returnUninstantiatedClass() { return null; }"); addSnippetClassDecl( "interface UsedInterface {", " int unusedConstant = 2;", " int usedConstant = 3;", " void method2();", "}"); addSnippetClassDecl("static class UsedClass implements UsedInterface {", " int field2;", " public void method2() { field2 = usedConstant; }", " UsedClass(UninstantiatedClass c) { }", " UsedClass(UninstantiatedClass c1, UninstantiatedClass c2) { }", " UsedClass(UninstantiatedClass c1, int i, UninstantiatedClass c2) { field2 = i; }", " UsedClass(UninstantiatedClass c1, int i, UninstantiatedClass c2, int j) " + "{ field2 = i + j; }", "}"); addSnippetClassDecl( "static native void usedNativeMethod(UninstantiatedClass c, UsedClass c2)", "/*-{", " c.@test.EntryPoint.UninstantiatedClass::field = 2;", " c.@test.EntryPoint.UninstantiatedClass::method();", " c2.@test.EntryPoint.UsedClass::field2++;", " c2.@test.EntryPoint.UsedClass::method2();", "}-*/;"); addSnippetClassDecl( "static native void unusedNativeMethod()", "/*-{", "}-*/;"); addSnippetClassDecl("static void methodWithUninstantiatedParam(UninstantiatedClass c) { }"); addSnippetClassDecl("interface UnusedInterface { void foo(); }"); addSnippetClassDecl("interface Callback { void go(); }"); addSnippetImport("jsinterop.annotations.JsType"); addSnippetImport("jsinterop.annotations.JsConstructor"); addSnippetClassDecl("@JsType interface Js { void doIt(Callback cb); }"); addSnippetClassDecl("@JsType(isNative=true) static class JsProto { ", "public JsProto(int arg) {}", "}"); addSnippetClassDecl("static class JsProtoImpl extends JsProto {", "public JsProtoImpl() { super(10); }", "}"); addSnippetClassDecl("static class JsProtoImpl2 extends JsProto {", "@JsConstructor public JsProtoImpl2() { super(10); }", "}"); addSnippetClassDecl("static class JsProtoImpl3 extends JsProto {", "public JsProtoImpl3() { super(10); }", "}"); Result result; (result = optimize("void", "usedMethod();", "unreadField = 1;", // should be pruned because it's not read. "foo(unassignedField);", "returnUninstantiatedClass();", "usedNativeMethod(null, null);", "foo(uninstantiatedField.field);", "uninstantiatedField.method();", "methodWithUninstantiatedParam(null);", "new UsedClass(null);", "new UsedClass(returnUninstantiatedClass(), returnUninstantiatedClass());", "new UsedClass(returnUninstantiatedClass(), 3, returnUninstantiatedClass());", "new UsedClass(returnUninstantiatedClass(), 3, returnUninstantiatedClass(), 4);", "UninstantiatedClass localUninstantiated = null;", "JsProtoImpl jsp = new JsProtoImpl();" )).intoString( "EntryPoint.usedMethod();", "EntryPoint.foo(EntryPoint.unassignedField);", "EntryPoint.returnUninstantiatedClass();", "EntryPoint.usedNativeMethod(null, null);", "EntryPoint.foo(null.nullField);", "null.nullMethod();", "EntryPoint.methodWithUninstantiatedParam();", "new EntryPoint$UsedClass();", "EntryPoint.returnUninstantiatedClass();", "EntryPoint.returnUninstantiatedClass();", "new EntryPoint$UsedClass();", "int lastArg;", "new EntryPoint$UsedClass((lastArg = (EntryPoint.returnUninstantiatedClass(), 3), EntryPoint.returnUninstantiatedClass(), lastArg));", "new EntryPoint$UsedClass((EntryPoint.returnUninstantiatedClass(), 3), (EntryPoint.returnUninstantiatedClass(), 4));", "new EntryPoint$JsProtoImpl();" ); assertNotNull(result.findMethod("usedMethod")); // We do not assign to the field, but we use its default value. // Shouldn't be pruned. assertNotNull(result.findField("unassignedField")); assertNotNull(result.findMethod("usedNativeMethod")); assertNotNull(result.findMethod("returnUninstantiatedClass")); assertNotNull(result.findMethod("methodWithUninstantiatedParam")); assertNotNull(result.findClass("EntryPoint$UsedClass")); assertNotNull(result.findClass("EntryPoint$UsedInterface")); assertNull(result.findMethod("unusedMethod")); assertNull(result.findField("unusedField")); assertNull(result.findField("unreadField")); assertNull(result.findClass("EntryPoint$UnusedClass")); assertNull(result.findMethod("unusedNativeMethod")); assertNull(result.findField("uninstantiatedField")); assertNull(result.findClass("EntryPoint$UnusedInterface")); // Class is never instantiated. Should be pruned. assertNull(result.findClass("UninstantiatedClass")); assertEquals( "static null returnUninstantiatedClass(){\n" + " return null;\n" + "}", result.findMethod("returnUninstantiatedClass").toSource()); assertEquals( "static void methodWithUninstantiatedParam(){\n" + "}", result.findMethod("methodWithUninstantiatedParam").toSource()); assertEquals( "[final null nullField, int field2]", ((JsniMethodBody) result.findMethod("usedNativeMethod").getBody()) .getJsniFieldRefs().toString()); assertEquals( "[public final null nullMethod(), public void method2()]", ((JsniMethodBody) result.findMethod("usedNativeMethod").getBody()) .getJsniMethodRefs().toString()); assertEquals( "interface EntryPoint$UsedInterface {\n" + " final static int usedConstant\n\n" + " private static final void $clinit(){\n" + " final static int usedConstant = 3;\n" + " }\n" + "\n" + "}", result.findClass("EntryPoint$UsedInterface").toSource()); // Neither super ctor call, nor super call's param is pruned assertEquals( "public EntryPoint$JsProtoImpl(){\n" + " this.EntryPoint$JsProto.EntryPoint$JsProto(10);\n" + " this.$init();\n" + "}", findMethod(result.findClass("EntryPoint$JsProtoImpl"), "EntryPoint$JsProtoImpl").toSource()); // Not JsType'd, and not instantiated, so should be pruned assertNull(result.findClass("EntryPoint$JsProtoImpl3")); // Should be rescued because of @JsType assertNotNull(result.findClass("EntryPoint$JsProtoImpl2")); } public void testCleanupVariableOfNonReferencedType() throws Exception { runDeadCodeElimination = false; addSnippetClassDecl("static class A {}"); addSnippetClassDecl("static boolean fun(A a) { return a == null; }"); Result result = optimize("void", "fun(null);"); assertNull(result.findClass("EntryPoint$A")); assertEquals("test.EntryPoint.fun(Ltest/EntryPoint$A;)Z", result.findMethod("fun").toString()); assertTrue(result.findMethod("fun").getParams().get(0).getType().isNullType()); } /** * Test for issue 2478. */ public void testPrunerThenEqualityNormalizer() throws Exception { runDeadCodeElimination = false; addSnippetClassDecl("static int foo(int i) { return i; }"); addSnippetClassDecl("static class UninstantiatedClass { " + "int field[]; native int method() /*-{ return 1; }-*/; }"); addSnippetClassDecl("static UninstantiatedClass uninstantiatedField;"); Result result; (result = optimize("int", "int i = 0;", "if (uninstantiatedField.field[i] == 0) { i = 2; }", "return i;" )).intoString( "int i = 0;", "if (null.nullField[i] == 0) {", " i = 2;", "}", "return i;" ); EqualityNormalizer.exec(result.getOptimizedProgram()); } public void testInterface() throws Exception { runDeadCodeElimination = false; addSnippetClassDecl("interface I { int bar(); }"); addSnippetClassDecl("static class A implements I { public int bar() { return 1; } }"); Result result = optimize("void", "A a = new A(); a.bar();"); assertNotNull(result.findClass("EntryPoint$I")); assertNotNull(result.findClass("EntryPoint$A")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$A"), "bar")); assertNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I"), "bar")); } public void testDefaultInterface() throws Exception { runDeadCodeElimination = false; addSnippetClassDecl("interface I {default int bar() {return 1;} " + "default int foo() {return 1;} }"); addSnippetClassDecl("static class A implements I { }"); addSnippetClassDecl("interface I2 {default int foo() {return fun();}" + "default int fun() {return 1;}" + "default int goo() {return 0;}" + "static int s1() { return s2();}" + "static int s2() { return 0;}" + "static int s3() { return 1;}" + "}"); Result result = optimize("void", "A a = new A(); a.bar(); " + "I2 i = new I2() {}; i.foo(); I2.s1();"); assertNotNull(result.findClass("EntryPoint$A")); assertNotNull(result.findClass("EntryPoint$I")); assertNotNull(result.findClass("EntryPoint$I2")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I"), "bar")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$A"), "bar")); assertNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I"), "foo")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I2"), "foo")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I2"), "fun")); assertNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I2"), "goo")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I2"), "s1")); assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I2"), "s2")); assertNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$I2"), "s3")); } public void testPruneParamsAndLocalsInMethodBody() throws Exception { runDeadCodeElimination = false; addSnippetClassDecl("interface I {default int bar() {int a = 0; return 1;} " + "static int fun(int b) {int a = 0; return 1;} }"); Result result = optimize("void", "I i = new I() {}; i.bar(); I.fun(0);"); assertNotNull(result.findClass("EntryPoint$I")); JMethod bar = OptimizerTestBase.findMethod(result.findClass("EntryPoint$I"), "bar"); assertNotNull(bar); assertEquals("public int bar(){\n" + " 0;\n" + " return 1;\n" + "}", bar.toSource()); JMethod fun = OptimizerTestBase.findMethod(result.findClass("EntryPoint$I"), "fun"); assertNotNull(fun); assertEquals("public static int fun(){\n" + " 0;\n" + " return 1;\n" + "}", fun.toSource()); } public void testJsFunction_notPruneUnusedJsFunction() throws Exception { addSnippetImport("jsinterop.annotations.JsFunction"); addSnippetClassDecl( "@JsFunction interface MyJsFunctionInterface {", "int foo (int a);", "}"); addSnippetClassDecl( "interface MyPlainInterface {", "int foo (int a);", "}"); addSnippetClassDecl( "@JsFunction interface MyJsFunctionInterfaceUnused {", "int foo (int a);", "}"); Result result; (result = optimize("void", "MyJsFunctionInterface a = new MyJsFunctionInterface() {" + "@Override public int foo (int a) { return 1; }" + "};" + "MyPlainInterface b = new MyPlainInterface() {" + "@Override public int foo (int a) { return 1; }" + "};" )).intoString( "new EntryPoint$1();\n" + "new EntryPoint$2();" ); assertNotNull(result.findClass("EntryPoint$MyJsFunctionInterface")); assertNotNull(result.findClass("EntryPoint$MyPlainInterface")); assertNotNull(result.findClass("EntryPoint$MyJsFunctionInterfaceUnused")); // Function in JsFunction interface may be implicitly called in JS, should not be pruned. assertNotNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$1"), "foo")); assertNull(OptimizerTestBase.findMethod(result.findClass("EntryPoint$2"), "foo")); } public void testJsFunction_unrelatedjsFunctionCast() throws Exception { addSnippetImport("jsinterop.annotations.JsFunction"); addSnippetClassDecl( "@JsFunction interface MyJsFunctionInterface {", "int foo (int a);", "}"); addSnippetClassDecl( "@JsFunction interface MyOtherJsFunctionInterface {", "int bar (int a);", "}"); addSnippetClassDecl( "interface MyPlainInterface {", "int goo (int a);", "}"); Result result; (result = optimize("int", "MyJsFunctionInterface a = new MyJsFunctionInterface() {" + "@Override public int foo (int a) { return 1; }" + "};" + "return ((MyOtherJsFunctionInterface) a).bar(0) + ((MyPlainInterface) a).goo(0);" )).intoString( "EntryPoint$MyJsFunctionInterface a = new EntryPoint$1();\n" + "return ((EntryPoint$MyOtherJsFunctionInterface) a).bar(0) +" // SAM is not pruned. + " ((EntryPoint$MyPlainInterface) a).nullMethod();" ); // JsFunction can be cross casted, or casted from JavaScript function // so the JsFunction interface and its SAM function should not be pruned. assertNotNull(result.findClass("EntryPoint$MyOtherJsFunctionInterface")); assertNotNull(OptimizerTestBase.findMethod( result.findClass("EntryPoint$MyOtherJsFunctionInterface"), "bar")); assertNull(result.findClass("EntryPoint$MyPlainInterface")); } @Override protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) { program.addEntryMethod(findMainMethod(program)); boolean didChange = false; // TODO(jbrosenberg): remove loop when Pruner/CFA interaction is perfect. while (Pruner.exec(program, true).didChange()) { didChange = true; } return didChange; } }