Add tests about duplicate entries in annotation set.
Bug: 129241209
Change-Id: I21af225f37689e50b58aaf7cdf913b4b4ed17e09
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index c9b8ffc..a0cb023 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -522,7 +522,8 @@
}
private void writeAnnotationSet(DexAnnotationSet set) {
- assert PresortedComparable.isSorted(set.annotations, (item) -> item.annotation.type);
+ assert PresortedComparable.isSorted(set.annotations, (item) -> item.annotation.type)
+ : "Unsorted annotation set: " + set.toSourceString();
mixedSectionOffsets.setOffsetFor(set, dest.align(4));
if (Log.ENABLED) {
Log.verbose(getClass(), "Writing AnnotationSet @ 0x%08x.", dest.position());
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index 44b09b0..a0c7006 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -14,7 +14,7 @@
private static final int UNSORTED = 0;
private static final DexAnnotationSet THE_EMPTY_ANNOTATIONS_SET =
- new DexAnnotationSet(new DexAnnotation[0]);
+ new DexAnnotationSet(DexAnnotation.EMPTY_ARRAY);
public final DexAnnotation[] annotations;
private int sorted = UNSORTED;
@@ -136,4 +136,9 @@
}
return new DexAnnotationSet(rewritten);
}
+
+ @Override
+ public String toString() {
+ return Arrays.toString(annotations);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/d8/DuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/d8/DuplicateAnnotationTest.java
new file mode 100644
index 0000000..ca67633
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/DuplicateAnnotationTest.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.d8;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexFilePerClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.nio.file.Path;
+import org.junit.Test;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD, ElementType.FIELD})
+@interface TestKeep {
+}
+
+class TestA {
+ @TestKeep
+ void foo() {
+ System.out.println("TestA::foo");
+ }
+}
+
+class TestB extends TestA {
+ @TestKeep
+ static TestA instance;
+
+ @TestKeep
+ void foo() {
+ super.foo();
+ System.out.println("TestB::foo");
+ }
+
+ @TestKeep
+ static void bar() {
+ instance = new TestB();
+ }
+
+ public static void main(String... args) {
+ bar();
+ instance.foo();
+ }
+}
+
+public class DuplicateAnnotationTest extends TestBase {
+
+ @Test
+ public void testMergingViaD8() throws Exception {
+ Path dex1 = temp.newFile("classes1.zip").toPath().toAbsolutePath();
+ CodeInspector inspector =
+ testForD8()
+ .addProgramClasses(TestA.class)
+ .setIntermediate(true)
+ .setProgramConsumer(new ArchiveConsumer(dex1))
+ .compile()
+ .inspector();
+
+ ClassSubject testA = inspector.clazz(TestA.class);
+ assertThat(testA, isPresent());
+
+ MethodSubject foo = testA.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ AnnotationSubject annotation = foo.annotation(TestKeep.class.getName());
+ assertThat(annotation, isPresent());
+
+ Path dex2 = temp.newFile("classes2.zip").toPath().toAbsolutePath();
+ inspector =
+ testForD8()
+ .addProgramClasses(TestB.class)
+ .setIntermediate(true)
+ .setProgramConsumer(new ArchiveConsumer(dex2))
+ .compile()
+ .inspector();
+ ClassSubject testB = inspector.clazz(TestB.class);
+ assertThat(testB, isPresent());
+
+ FieldSubject instance = testB.uniqueFieldWithName("instance");
+ assertThat(instance, isPresent());
+ annotation = instance.annotation(TestKeep.class.getName());
+ assertThat(annotation, isPresent());
+
+ foo = testB.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ annotation = foo.annotation(TestKeep.class.getName());
+ assertThat(annotation, isPresent());
+
+ MethodSubject bar = testB.uniqueMethodWithName("bar");
+ assertThat(bar, isPresent());
+ annotation = bar.annotation(TestKeep.class.getName());
+ assertThat(annotation, isPresent());
+
+ Path merged = temp.newFile("merge.zip").toPath().toAbsolutePath();
+ testForD8()
+ .addProgramFiles(dex1, dex2)
+ .setProgramConsumer(new ArchiveConsumer(merged))
+ .compile();
+ }
+
+ @Test
+ public void testDuplicationInInput() throws Exception {
+ Path dex1 = temp.newFile("classes1.zip").toPath().toAbsolutePath();
+ try {
+ testForD8()
+ .addProgramClassFileData(TestADump.dump())
+ .setIntermediate(true)
+ .setProgramConsumer(new ArchiveConsumer(dex1))
+ .compile();
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("Unsorted annotation set"));
+ assertThat(e.getCause().getMessage(), containsString(TestKeep.class.getName()));
+ }
+ }
+
+ @Test
+ public void testJVMOutput() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(
+ TestADump.dump(),
+ ToolHelper.getClassAsBytes(TestB.class),
+ ToolHelper.getClassAsBytes(TestKeep.class))
+ .run(TestB.class.getTypeName())
+ .assertSuccessWithOutput(StringUtils.lines("TestA::foo", "TestB::foo"));
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/d8/TestADump.java b/src/test/java/com/android/tools/r8/d8/TestADump.java
new file mode 100644
index 0000000..610d194
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/TestADump.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.d8;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+class TestADump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+ AnnotationVisitor annotationVisitor0;
+
+ classWriter.visit(
+ V1_8, ACC_SUPER,
+ "com/android/tools/r8/d8/TestA",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitSource("DuplicateAnnotationTest.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(28, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable(
+ "this", "Lcom/android/tools/r8/d8/TestA;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "foo", "()V", null, null);
+ {
+ annotationVisitor0 =
+ methodVisitor.visitAnnotation("Lcom/android/tools/r8/d8/TestKeep;", false);
+ annotationVisitor0.visitEnd();
+ // Intentionally introduce a duplication.
+ annotationVisitor0 =
+ methodVisitor.visitAnnotation("Lcom/android/tools/r8/d8/TestKeep;", false);
+ annotationVisitor0.visitEnd();
+ }
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(31, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("TestA::foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(32, label1);
+ methodVisitor.visitInsn(RETURN);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLocalVariable(
+ "this", "Lcom/android/tools/r8/d8/TestA;", null, label0, label2, 0);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java
index 83673a7..91364d4 100644
--- a/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/DexFileMergerTests.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.dexfilemerger;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8;
@@ -13,6 +16,7 @@
import com.android.tools.r8.ExtractMarker;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.dex.Constants;
@@ -26,19 +30,15 @@
import java.nio.file.Paths;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-public class DexFileMergerTests {
+public class DexFileMergerTests extends TestBase {
private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexmergesample";
private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
private static final int MAX_METHOD_COUNT = Constants.U16BIT_MAX;
- @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
private Path createMergerInputWithTwoClasses(OutputMode outputMode, boolean addMarker)
throws CompilationFailedException, IOException {
// Compile Class1 and Class2
@@ -128,11 +128,16 @@
});
}
- @Test(expected = CompilationFailedException.class)
- public void failIfTooBig() throws IOException, ExecutionException, CompilationFailedException {
+ @Test
+ public void failIfTooBig() throws IOException, ExecutionException {
// Generates an application with two classes, each with the number of methods just enough not to
// fit into a single dex file.
- generateClassesAndTest(1, 2);
+ try {
+ generateClassesAndTest(1, 2);
+ fail("Expect to fail");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("does not fit into a single dex file"));
+ }
}
@Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
new file mode 100644
index 0000000..3dca26b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class KotlinDuplicateAnnotationTest extends AbstractR8KotlinTestBase {
+ private Consumer<InternalOptions> optionsModifier =
+ o -> {
+ o.enableTreeShaking = true;
+ o.enableMinification = false;
+ o.enableInlining = false;
+ };
+
+ public KotlinDuplicateAnnotationTest(
+ KotlinTargetVersion targetVersion, boolean allowAccessModification) {
+ super(targetVersion, allowAccessModification);
+ }
+
+ @Test
+ public void testDuplicateAnnotation() throws Exception {
+ final String mainClassName = "duplicate_annotation.MainKt";
+ try {
+ runTest(
+ "duplicate_annotation",
+ mainClassName,
+ StringUtils.lines(
+ "-keep,allowobfuscation @interface **.TestAnnotation",
+ "-keepattributes *Annotations*"
+ ),
+ optionsModifier,
+ null);
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("Unsorted annotation set"));
+ assertThat(e.getCause().getMessage(), containsString("TestAnnotation"));
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 11e92e1..33a8bf3 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -4,8 +4,11 @@
package com.android.tools.r8.maindexlist;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexIndexedConsumer;
@@ -25,9 +28,7 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.stream.Collectors;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
public class MainDexListOutputTest extends TestBase {
@@ -70,10 +71,7 @@
}
}
- @Rule
- public ExpectedException thrown = ExpectedException.none();
-
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testNoMainDex() throws Exception {
Reporter reporter = new Reporter();
try {
@@ -84,9 +82,12 @@
ToolHelper.getClassAsBytes(HelloWorldMain.class), Origin.unknown())
.setMainDexListOutputPath(mainDexListOutput)
.build();
+ fail("Expect to fail");
} catch (CompilationFailedException e) {
assertEquals(1, reporter.errorCount);
- throw e;
+ assertThat(
+ e.getCause().getMessage(),
+ containsString("--main-dex-list-output require --main-dex-rules and/or --main-dex-list"));
}
}
diff --git a/src/test/kotlinR8TestResources/duplicate_annotation/main.kt b/src/test/kotlinR8TestResources/duplicate_annotation/main.kt
new file mode 100644
index 0000000..99d2273
--- /dev/null
+++ b/src/test/kotlinR8TestResources/duplicate_annotation/main.kt
@@ -0,0 +1,17 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package duplicate_annotation
+
+annotation class TestAnnotation
+
+class Foo {
+ @get:TestAnnotation
+ val test: Int
+ @TestAnnotation
+ get() = 0
+}
+
+fun main() {
+ println(Foo().test)
+}
\ No newline at end of file