/* * Copyright 2012 Google Inc. All Rights Reserved. * * 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.devtools.j2objc.translate; import com.google.devtools.j2objc.GenerationTest; import com.google.devtools.j2objc.util.CodeReferenceMap; import java.io.IOException; /** * Unit tests for DeadCodeEliminator. * * @author Daniel Connelly */ public class DeadCodeEliminatorTest extends GenerationTest { public void testDeadMethod() throws IOException { String source = "class A {\n" + " private static interface B {\n" + " String bar();\n" + " }\n" + " private void baz() {\n" + " // nothing\n" + " }\n" + "}\n"; CodeReferenceMap map = CodeReferenceMap.builder() .addMethod("A$B", "bar", "()Ljava/lang/String;") .build(); setDeadCodeMap(map); String translation = translateSourceFile(source, "A", "A.m"); assertTranslation(translation, "@interface A_B"); assertNotInTranslation(translation, "bar"); assertTranslation(translation, "baz"); } public void testDeadMethod_AnonymousClassMember() throws IOException { String source = "abstract class B {}\n" + "class A {\n" + " private B b = new B() {\n" + " public void foo() {}\n" + " };\n" + "}\n"; CodeReferenceMap map = CodeReferenceMap.builder() .addMethod("A$1", "foo", "()V") .build(); setDeadCodeMap(map); String translation = translateSourceFile(source, "A", "A.m"); assertNotInTranslation(translation, "foo"); } public void testDeadMethod_InnerClassConstructor() throws IOException { String source = "class A {\n" + " class B {\n" + " B(int i) {}\n" + " }\n" + "}\n"; CodeReferenceMap map = CodeReferenceMap.builder() .addMethod("A$B", "A$B", "(LA;I)V") .build(); setDeadCodeMap(map); String translation = translateSourceFile(source, "A", "A.m"); assertNotInTranslation(translation, "withInt"); } public void testDeadFields() throws IOException { String source = "import static java.lang.System.out;\n" + "import static java.lang.System.in;\n" + "class A {\n" + " private static final int foo = 1;\n" + " public static final String bar = \"bar\";\n" + " static final double pi = 3.2; // in Indiana only\n" + " final String baz = null, bah = \"123\";\n" + " private int abc = 9;\n" + "}\n"; CodeReferenceMap map = CodeReferenceMap.builder() .addField("A", "foo") .addField("A", "baz") .addField("java.lang.System", "in") .build(); setDeadCodeMap(map); String translation = translateSourceFile(source, "A", "A.h"); assertTranslation(translation, "#define A_pi 3.2"); assertTranslation(translation, "NSString *bah_;"); assertNotInTranslation(translation, "baz"); translation = getTranslatedFile("A.m"); assertTranslation(translation, "#define A_foo 1"); assertTranslation(translation, "NSString *A_bar = @\"bar\";"); assertTranslation(translation, "abc_ = 9;"); assertTranslation(translation, "JreStrongAssign(&self->bah_, @\"123\");"); assertNotInTranslation(translation, "baz"); } public void testDeadInitializer() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("A").build(); setDeadCodeMap(map); String source = "class A {\n" + " static final int baz = 9;\n" + " static { System.out.println(\"foo\"); }\n" + " { System.out.println(\"bar\"); }\n" + "}\n"; String translation = translateSourceFile(source, "A", "A.h"); assertTranslation(translation, "#define A_baz 9"); translation = getTranslatedFile("A.m"); assertNotInTranslation(translation, "println"); assertNotInTranslation(translation, "initialize"); assertNotInTranslation(translation, "foo"); assertNotInTranslation(translation, "bar"); } public void testDeadEnum() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("A$Thing") .build(); setDeadCodeMap(map); String source = "class A {\n" + " private static void foo() {}\n" + " public enum Thing implements java.io.Serializable {\n" + " THING1(27),\n" + " THING2(89) { void bar() {} },\n" + " THING3 { void bar() { foo(); } };\n" + " private Thing(int x) {}\n" + " private Thing() {}\n" + " }\n" + "}\n"; String translation = translateSourceFile(source, "A", "A.m"); assertNotInTranslation(translation, "initWithInt"); assertNotInTranslation(translation, "THING1"); assertNotInTranslation(translation, "THING2"); assertNotInTranslation(translation, "THING3"); String header = getTranslatedFile("A.h"); assertNotInTranslation(header, "Serializable"); } public void testConstructorGeneration() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("A") .addMethod("C", "C", "(I)V") .build(); setDeadCodeMap(map); addSourceFile("class B {\n" + " public B(int x, boolean y, String z, java.util.List w) {}\n" + "}", "B.java"); String translation = translateSourceFile("class A extends B {\n" + " public A(int i) { super(i, true, \"foo\", new java.util.ArrayList()); }\n" + "}\n", "A", "A.m"); assertNotInTranslation(translation, "initWithInt"); assertNotInTranslation(translation, "B_init"); translation = translateSourceFile("class C extends B {\n" + " public C(int i) { super(i, true, \"foo\", new java.util.ArrayList()); }\n" + "}\n", "C", "C.m"); assertNotInTranslation(translation, "initWithInt"); assertNotInTranslation(translation, "B_init"); } public void testDeadClass_FieldRemoval() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .build(); setDeadCodeMap(map); String source = "class Foo {\n" + " static final int x = f();\n" + " static final int y = 0;\n" + " static int f() { return 0; }\n" + "}\n"; String translation = translateSourceFile(source, "Foo", "Foo.h"); assertTranslation(translation, "#define Foo_y 0"); translation = getTranslatedFile("Foo.m"); assertNotInTranslation(translation, "jint Foo_x_"); assertNotInTranslation(translation, "Foo_x_ = Foo_f()"); assertNotInTranslation(translation, "+ (jint)f"); assertNotInTranslation(translation, "bar"); } public void testDeadClass_StaticNestedClass() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .build(); setDeadCodeMap(map); String source = "class Foo {\n" + " static class Bar {}\n" + "}\n" + "class Baz extends Foo.Bar {\n" + "}\n"; String translation = translateSourceFile(source, "Foo", "Foo.h"); assertTranslation(translation, "@interface Foo_Bar : NSObject"); translation = getTranslatedFile("Foo.m"); assertTranslation(translation, "Foo_Bar_init"); } public void testDeadClass_DeadStaticNestedClass() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .addClass("Foo$Bar") .addMethod("Foo$Baz", "g", "()V") .build(); setDeadCodeMap(map); String source = "class Foo {\n" + " static class Bar { void f() {} }\n" + " static class Baz { void g() {} }\n" + "}\n"; String translation = translateSourceFile(source, "Foo", "Foo.h"); assertTranslation(translation, "@interface Foo_Bar"); assertNotInTranslation(translation, "- (void)f"); assertTranslation(translation, "@interface Foo_Baz"); assertNotInTranslation(translation, "- (void)g"); translation = getTranslatedFile("Foo.m"); assertNotInTranslation(translation, "Foo_Bar_init"); assertNotInTranslation(translation, "- (void)f"); assertTranslation(translation, "Foo_Baz_init"); assertNotInTranslation(translation, "- (void)g"); } public void testDeadClass_DeadInnerClassConstructor() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addField("Foo$A", "z") .addField("Foo$A", "this$0") .addMethod("Foo$A", "Foo$A", "(LFoo;I)V") .addMethod("Foo$A", "f", "()I") .build(); setDeadCodeMap(map); String source = "public class Foo {\n" + " int y;\n" + " public Foo(int x) { y = x; }\n" + "\n" + " class A {\n" + " int z;\n" + " A(int x) { z = x; }\n" + " int f() { return z + y; }\n" + " }\n" + "}\n"; String translation = translateSourceFile(source, "Foo", "Foo.h"); assertTranslation(translation, "@interface Foo_A"); assertNotInTranslation(translation, "z_;"); translation = getTranslatedFile("Foo.m"); assertNotInTranslation(translation, "Foo *this$0_;"); assertNotInTranslation(translation, "JreStrongAssign(&self->this$0_, outer$"); assertNotInTranslation(translation, "self->z_ = x;"); assertNotInTranslation(translation, "- (jint)f"); } public void testDeadClass_SupertypeRemoval() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .build(); setDeadCodeMap(map); addSourceFile("class SuperClass {}", "SuperClass.java"); addSourceFile("interface SuperI { void f(); }", "SuperI.java"); String source = "class Foo extends SuperClass implements SuperI {\n" + " public void f() {}\n" + "}\n"; String translation = translateSourceFile(source, "Foo", "Foo.h"); assertNotInTranslation(translation, "SuperClass"); assertNotInTranslation(translation, "SuperI"); } // Verify that annotation bodies aren't stripped when specified in a dead code report. public void testDeadAnnotation() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .build(); setDeadCodeMap(map); String source = "import java.lang.annotation.Retention;\n" + "import java.lang.annotation.RetentionPolicy;\n" + "@Retention(RetentionPolicy.RUNTIME)\n" + "public @interface Foo {\n" + " String value() default \"\";\n" + "}\n"; String translation = translateSourceFile(source, "Foo", "Foo.h"); assertTranslation(translation, "@property (readonly) NSString *value;"); } public void testDeadDefaultConstructor() throws IOException { CodeReferenceMap map = CodeReferenceMap.builder() .addMethod("Test", "Test", "()V") .build(); setDeadCodeMap(map); String translation = translateSourceFile("class Test {}", "Test", "Test.h"); // Make sure the default constructor is not added. assertNotInTranslation(translation, "init"); } public void testDeadFastEnumerationImplementation() throws IOException { String source = "import java.util.Iterator;\n" + "interface SomeInterface extends Iterable<String> {}\n" + "class Base {}\n" + "class Foo extends Base implements SomeInterface {\n" + " @Override\n" + " public Iterator<String> iterator() { return null; }\n" + "}"; CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .build(); setDeadCodeMap(map); String header = translateSourceFile(source, "Foo", "Foo.h"); String impl = getTranslatedFile("Foo.m"); assertNotInTranslation(header, "@interface Foo : Base < SomeInterface >"); assertTranslation(header, "@interface Foo : NSObject"); assertNotInTranslation(impl, "countByEnumeratingWithState:"); } public void testTypeNarrowingMethodsNotShowingInDeadClasses() throws IOException { String source = "class Base<T> { \n" + " T someMethod() { return null; }\n" + "}\n" + "class Foo extends Base<String> {}"; CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .build(); setDeadCodeMap(map); String header = translateSourceFile(source, "Foo", "Foo.h"); assertNotInTranslation(header, "@interface Foo : Base"); assertTranslation(header, "@interface Foo : NSObject"); assertTranslation(header, "- (id)someMethod;"); assertNotInTranslation(header, "- (NSString *)someMethod;"); } public void testDeadMethodInBaseClassNotShowingInChildClasses() throws IOException { String source = "class Base<T> { \n" + " T someDeadMethod() { return null; }\n" + "}\n" + "class Foo extends Base<String> {}"; CodeReferenceMap map = CodeReferenceMap.builder() .addMethod("Base", "someDeadMethod", "()Ljava/lang/Object;") .build(); setDeadCodeMap(map); String header = translateSourceFile(source, "Foo", "Foo.h"); assertNotInTranslation(header, "- (id)someDeadMethod;"); assertNotInTranslation(header, "- (NSString *)someDeadMethod;"); } public void testDeadMethodInBaseClassNotShowingInDeadChildClasses() throws IOException { String source = "class Base<T> { \n" + " T someDeadMethod() { return null; }\n" + "}\n" + "class Foo extends Base<String> {}"; CodeReferenceMap map = CodeReferenceMap.builder() .addClass("Foo") .addMethod("Base", "someDeadMethod", "()Ljava/lang/Object;") .build(); setDeadCodeMap(map); String header = translateSourceFile(source, "Foo", "Foo.h"); assertNotInTranslation(header, "- (id)someDeadMethod;"); assertNotInTranslation(header, "- (NSString *)someDeadMethod;"); } public void testDeadDefaultInterfaceMethod() throws IOException { // Test dead method String source = "interface I { default void foo() { } } " + " class A implements I { } "; CodeReferenceMap map = CodeReferenceMap.builder().addMethod("I", "foo", "()V").build(); setDeadCodeMap(map); String translation = translateSourceFile(source, "A", "A.m"); assertNotInTranslation(translation, "I_foo(self);"); // Test dead class map = CodeReferenceMap.builder().addClass("I").build(); setDeadCodeMap(map); translation = translateSourceFile(source, "A", "A.m"); assertNotInTranslation(translation, "I_foo(self);"); } public void testDeadMethodShimGenerator() throws IOException { String sourceI = "interface I <T> { " + " void foo(T t); " + " void bar(T t); " + " void baz(T t); } "; String sourceA = "class A { " + " public void foo(String s) { } " + " public void bar(String s) { } " + " public void baz(String s) { } } "; String sourceB = "class B extends A implements I<String> { } "; addSourceFile(sourceI, "I.java"); addSourceFile(sourceA, "A.java"); addSourceFile(sourceB, "B.java"); CodeReferenceMap map = CodeReferenceMap.builder() .addMethod("I", "foo", "(Ljava/lang/Object;)V") .addMethod("I", "baz", "(Ljava/lang/Object;)V") .addMethod("A", "baz", "(Ljava/lang/String;)V") .build(); setDeadCodeMap(map); String aImpl = translateSourceFile("A", "A.m"); String bImpl = translateSourceFile("B", "B.m"); String iHeader = translateSourceFile("I", "I.h"); assertTranslation(aImpl, "(void)fooWithNSString:(NSString *)s"); assertNotInTranslation(bImpl, "[self fooWithNSString:arg0];"); assertNotInTranslation(iHeader, "(void)fooWithId:(id)t"); assertTranslation(bImpl, "[self barWithNSString:arg0];"); assertTranslation(iHeader, "(void)barWithId:(id)t"); assertNotInTranslation(aImpl, "(void)bazWithNSString:(NSString *)s"); assertNotInTranslation(bImpl, "[self bazWithNSString:arg0];"); assertNotInTranslation(iHeader, "(void)bazWithId:(id)t"); } }