Only retain non-runtime annotations in intermediate mode.
Bug: 130028992
Change-Id: Idb1fb90c041159d5fa481e75071791b8b6a8e61e
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f0591ee..9438632 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -249,6 +249,7 @@
internal.minimalMainDex = internal.debug;
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
+ internal.readCompileTimeAnnotations = intermediate;
// Assert and fixup defaults.
assert !internal.isShrinking();
assert !internal.isMinifying();
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 2a290b6..168d3e9 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -151,10 +152,31 @@
List<DexAnnotation> annotations,
JarApplicationReader application) {
assert annotations != null;
- int visiblity = visible ? DexAnnotation.VISIBILITY_RUNTIME : DexAnnotation.VISIBILITY_BUILD;
- return new CreateAnnotationVisitor(application, (names, values) ->
- annotations.add(new DexAnnotation(visiblity,
- createEncodedAnnotation(desc, names, values, application))));
+ if (visible || retainCompileTimeAnnotation(desc, application)) {
+ int visiblity = visible ? DexAnnotation.VISIBILITY_RUNTIME : DexAnnotation.VISIBILITY_BUILD;
+ return new CreateAnnotationVisitor(
+ application,
+ (names, values) ->
+ annotations.add(
+ new DexAnnotation(
+ visiblity, createEncodedAnnotation(desc, names, values, application))));
+ }
+ return null;
+ }
+
+ private static boolean retainCompileTimeAnnotation(
+ String desc, JarApplicationReader application) {
+ if (application.options.readCompileTimeAnnotations) {
+ return true;
+ }
+ if (application.options.processCovariantReturnTypeAnnotations) {
+ // @CovariantReturnType annotations are processed by CovariantReturnTypeAnnotationTransformer,
+ // they thus need to be read here and will then be removed as part of the processing.
+ DexType type = application.getTypeFromDescriptor(desc);
+ return CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation(
+ type, application.options.itemFactory);
+ }
+ return false;
}
private static DexEncodedAnnotation createEncodedAnnotation(String desc,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 0c76382..9c0c542 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -248,8 +248,12 @@
}
private boolean isCovariantReturnTypeAnnotation(DexEncodedAnnotation annotation) {
- return annotation.type == factory.annotationCovariantReturnType
- || annotation.type == factory.annotationCovariantReturnTypes;
+ return isCovariantReturnTypeAnnotation(annotation.type, factory);
+ }
+
+ public static boolean isCovariantReturnTypeAnnotation(DexType type, DexItemFactory factory) {
+ return type == factory.annotationCovariantReturnType
+ || type == factory.annotationCovariantReturnTypes;
}
private static boolean hasVirtualMethodWithSignature(DexClass clazz, DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d227afd..9d3c521 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -262,6 +262,7 @@
// Skipping min_api check and compiling an intermediate result intended for later merging.
// Intermediate builds also emits or update synthesized classes mapping.
public boolean intermediate = false;
+ public boolean readCompileTimeAnnotations = true;
public List<String> logArgumentsFilter = ImmutableList.of();
// Flag to turn on/off lambda class merging in R8.
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 11dc790..310124e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -734,6 +734,13 @@
// checked into the Art repo.
private static final Multimap<String, TestCondition> failingRunWithArtOutput =
new ImmutableListMultimap.Builder<String, TestCondition>()
+ // This test assumes that class-retention annotations are preserved by the compiler and
+ // then checks for backwards compatibility with M where they could incorrectly be observed
+ // by the program at runtime.
+ .put(
+ "005-annotations",
+ TestCondition.match(
+ TestCondition.compilers(CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF)))
// On Art 4.4.4 we have fewer refs than expected (except for d8 when compiled with dx).
.put("072-precise-gc",
TestCondition.match(
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
index 581bfe8..4f512b7 100644
--- a/src/test/java/com/android/tools/r8/TestBaseBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -9,9 +9,11 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
public abstract class TestBaseBuilder<
C extends BaseCommand,
@@ -67,6 +69,16 @@
return self();
}
+ public T addMainDexListClasses(Class<?>... classes) {
+ return addMainDexListClasses(Arrays.asList(classes));
+ }
+
+ public T addMainDexListClasses(Collection<Class<?>> classes) {
+ builder.addMainDexClasses(
+ classes.stream().map(Class::getTypeName).collect(Collectors.toList()));
+ return self();
+ }
+
public T addMainDexListFiles(Collection<Path> files) {
builder.addMainDexListFiles(files);
return self();
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 42d47e3..6fbb80f 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -23,7 +23,7 @@
getAvailableRuntimes().collect(Collectors.toList());
// Predicate describing which test parameters are applicable to the test.
- // Built via the methods found below. Default to no applicable parameters, i.e., the emtpy set.
+ // Built via the methods found below. Defaults to no applicable parameters, i.e., the emtpy set.
private Predicate<TestParameters> filter = param -> false;
private TestParametersBuilder() {}
@@ -142,11 +142,34 @@
*/
private static final AndroidApiLevel lowestCompilerApiLevel = AndroidApiLevel.B;
- private boolean enableAllApiLevels = false;
+ private boolean enableApiLevels = false;
+
+ private Predicate<AndroidApiLevel> apiLevelFilter = param -> false;
+
+ private TestParametersBuilder withApiFilter(Predicate<AndroidApiLevel> filter) {
+ enableApiLevels = true;
+ apiLevelFilter = apiLevelFilter.or(filter);
+ return this;
+ }
public TestParametersBuilder withAllApiLevels() {
- enableAllApiLevels = true;
- return this;
+ return withApiFilter(api -> true);
+ }
+
+ public TestParametersBuilder withApiLevelsStartingAtIncluding(AndroidApiLevel startInclusive) {
+ return withApiFilter(api -> startInclusive.getLevel() <= api.getLevel());
+ }
+
+ public TestParametersBuilder withApiLevelsStartingAtExcluding(AndroidApiLevel startExclusive) {
+ return withApiFilter(api -> startExclusive.getLevel() < api.getLevel());
+ }
+
+ public TestParametersBuilder withApiLevelsEndingAtInclusive(AndroidApiLevel endInclusive) {
+ return withApiFilter(api -> api.getLevel() <= endInclusive.getLevel());
+ }
+
+ public TestParametersBuilder withApiLevelsEndingAtExcluding(AndroidApiLevel endExclusive) {
+ return withApiFilter(api -> api.getLevel() < endExclusive.getLevel());
}
public TestParametersCollection build() {
@@ -158,15 +181,31 @@
}
public Stream<TestParameters> createParameters(TestRuntime runtime) {
- if (enableAllApiLevels && runtime.isDex()) {
- AndroidApiLevel vmLevel = runtime.asDex().getMinApiLevel();
- if (vmLevel != lowestCompilerApiLevel) {
- return Stream.of(
- new TestParameters(runtime, vmLevel),
- new TestParameters(runtime, lowestCompilerApiLevel));
+ if (!enableApiLevels || !runtime.isDex()) {
+ return Stream.of(new TestParameters(runtime));
+ }
+ List<AndroidApiLevel> sortedApiLevels =
+ Arrays.stream(AndroidApiLevel.values()).filter(apiLevelFilter).collect(Collectors.toList());
+ if (sortedApiLevels.isEmpty()) {
+ return Stream.of();
+ }
+ AndroidApiLevel vmLevel = runtime.asDex().getMinApiLevel();
+ AndroidApiLevel lowestApplicable = sortedApiLevels.get(sortedApiLevels.size() - 1);
+ if (vmLevel.getLevel() < lowestApplicable.getLevel()) {
+ return Stream.of();
+ }
+ if (sortedApiLevels.size() > 1) {
+ for (int i = 0; i < sortedApiLevels.size(); i++) {
+ AndroidApiLevel highestApplicable = sortedApiLevels.get(i);
+ if (highestApplicable.getLevel() <= vmLevel.getLevel()
+ && lowestApplicable != highestApplicable) {
+ return Stream.of(
+ new TestParameters(runtime, lowestApplicable),
+ new TestParameters(runtime, highestApplicable));
+ }
}
}
- return Stream.of(new TestParameters(runtime));
+ return Stream.of(new TestParameters(runtime, lowestApplicable));
}
// Public method to check that the CF runtime coincides with the system runtime.
diff --git a/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
new file mode 100644
index 0000000..be166ec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/RetentionPolicyTest.java
@@ -0,0 +1,107 @@
+// 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.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetentionPolicyTest extends TestBase {
+
+ private static final Collection<Class<?>> CLASSES =
+ ImmutableList.of(ClassRetained.class, SourceRetained.class, RuntimeRetained.class, A.class);
+
+ private static final String EXPECTED =
+ StringUtils.lines("@" + RuntimeRetained.class.getName() + "()");
+
+ @Parameters(name = "{0}, intermediate:{1}")
+ public static List<Object> data() {
+ return getTestParameters().withAllRuntimes().build().stream()
+ .flatMap(
+ parameters -> {
+ if (parameters.isCfRuntime()) {
+ return Stream.of((Object) new Object[] {parameters, false});
+ }
+ return Stream.of(new Object[] {parameters, true}, new Object[] {parameters, false});
+ })
+ .collect(Collectors.toList());
+ }
+
+ private final TestParameters parameters;
+ private final boolean intermediate;
+
+ public RetentionPolicyTest(TestParameters parameters, boolean intermediate) {
+ this.parameters = parameters;
+ this.intermediate = intermediate;
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ @interface ClassRetained {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SourceRetained {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface RuntimeRetained {}
+
+ @ClassRetained
+ @SourceRetained
+ @RuntimeRetained
+ public static class A {
+
+ public static void main(String[] args) {
+ for (Annotation annotation : A.class.getAnnotations()) {
+ System.out.println(annotation);
+ }
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+ TestBuilder<?, ?> testBuilder =
+ parameters.isCfRuntime()
+ ? testForJvm()
+ : testForD8().setMinApi(parameters.getRuntime()).setIntermediate(intermediate);
+
+ CodeInspector inspector =
+ testBuilder
+ .addProgramClasses(CLASSES)
+ .run(parameters.getRuntime(), A.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspector();
+
+ ClassSubject clazz = inspector.clazz(A.class);
+ assertThat(clazz, isPresent());
+ // Source retained annotations are always gone, even in the CF inputs.
+ assertFalse(clazz.annotation(SourceRetained.class.getName()).isPresent());
+ // Class retained annotations are present in CF and in intermediate builds.
+ assertEquals(
+ parameters.isCfRuntime() || intermediate,
+ clazz.annotation(ClassRetained.class.getName()).isPresent());
+ // Runtime retained annotations are present in all.
+ assertTrue(clazz.annotation(RuntimeRetained.class.getName()).isPresent());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
index f1f2842..eaa8718 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.AsmTestBase;
@@ -20,8 +21,12 @@
public class CovariantReturnTypeAnnotationTransformerTest extends AsmTestBase {
public static final String PACKAGE_NAME = "com/android/tools/r8/ir/desugar/annotations";
- public static final String CRT_NAME = "dalvik/annotation/codegen/CovariantReturnType";
- public static final String CRTS_SIMPLE_NAME = "CovariantReturnTypes";
+ public static final String CRT_BINARY_NAME = "dalvik/annotation/codegen/CovariantReturnType";
+ public static final String CRTS_INNER_NAME = "CovariantReturnTypes";
+ public static final String CRTS_BINARY_NAME = CRT_BINARY_NAME + "$" + CRTS_INNER_NAME;
+
+ public static final String CRT_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.');
+ public static final String CRTS_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.');
@Test
public void testVersion1WithClient1And2() throws Exception {
@@ -32,6 +37,9 @@
ToolHelper.getClassAsBytes(B.class),
ToolHelper.getClassAsBytes(C.class));
+ // Version 1 does not contain annotations.
+ checkPresenceOfCovariantAnnotations(input, false);
+
// Version 1 of the library should always work.
succeedsIndependentOfFlag(input, false);
}
@@ -45,6 +53,9 @@
ToolHelper.getClassAsBytes(B.class),
ToolHelper.getClassAsBytes(C.class));
+ // Version 1 does not contain annotations.
+ checkPresenceOfCovariantAnnotations(input, false);
+
// There will be no methods with the signature "L.../B;->method()L.../B;" and
// "L.../C;->method()L.../C;".
failsIndependentOfFlag(input);
@@ -59,6 +70,9 @@
com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+ // Version 2 contains annotations.
+ checkPresenceOfCovariantAnnotations(input, true);
+
// Version 2 of the library should always work.
succeedsIndependentOfFlag(input, true);
}
@@ -72,6 +86,9 @@
com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+ // Version 2 contains annotations.
+ checkPresenceOfCovariantAnnotations(input, true);
+
// If CovariantReturnType annotations are processed, then synthetic methods with the signatures
// "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;" will be added by D8.
succeedsWithOption(input, true, true);
@@ -90,6 +107,9 @@
com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+ // Version 3 does not contain annotations.
+ checkPresenceOfCovariantAnnotations(input, false);
+
// Version 3 of the library should always work.
succeedsIndependentOfFlag(input, false);
}
@@ -103,6 +123,9 @@
com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+ // Version 3 does not contain annotations.
+ checkPresenceOfCovariantAnnotations(input, false);
+
// Version 3 of the library should always work with client 1.
succeedsIndependentOfFlag(input, false);
}
@@ -116,9 +139,15 @@
com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+ // Version 2 contains annotations.
+ checkPresenceOfCovariantAnnotations(input, true);
+
AndroidApp output =
compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = true);
+ // Compilation output does not contain annotations.
+ checkPresenceOfCovariantAnnotations(output, false);
+
// Compilation will fail with a compilation error the second time if the implementation does
// not remove the CovariantReturnType annotations properly during the first compilation.
compileWithD8(output, options -> options.processCovariantReturnTypeAnnotations = true);
@@ -129,8 +158,8 @@
AndroidApp output =
compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
String stdout = runOnArt(output, Client.class.getCanonicalName());
- Assert.assertEquals(getExpectedOutput(), stdout);
-
+ assertEquals(getExpectedOutput(), stdout);
+ checkPresenceOfCovariantAnnotations(output, false);
if (option && checkPresenceOfSyntheticMethods) {
checkPresenceOfSyntheticMethods(output);
}
@@ -139,6 +168,7 @@
private void failsWithOption(AndroidApp input, boolean option) throws Exception {
AndroidApp output =
compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+ checkPresenceOfCovariantAnnotations(output, false);
ToolHelper.ProcessResult result = runOnArtRaw(output, Client.class.getCanonicalName());
assertThat(result.stderr, containsString("java.lang.NoSuchMethodError"));
}
@@ -154,6 +184,18 @@
failsWithOption(input, false);
}
+ private void checkPresenceOfCovariantAnnotations(AndroidApp app, boolean expected)
+ throws Exception {
+ CodeInspector inspector = new CodeInspector(app);
+ assertEquals(
+ expected,
+ inspector.allClasses().stream()
+ .anyMatch(
+ clazz ->
+ clazz.allMethods().stream()
+ .anyMatch(method -> method.annotation(CRTS_TYPE_NAME).isPresent())));
+ }
+
private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
CodeInspector inspector = new CodeInspector(output);
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
index 3dadc2b..1b6d3ef 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.ir.desugar.annotations.version2;
-import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_BINARY_NAME;
import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
import org.objectweb.asm.AnnotationVisitor;
@@ -40,7 +40,7 @@
{
mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
{
- av0 = mv.visitAnnotation("L" + CRT_NAME + ";", false);
+ av0 = mv.visitAnnotation("L" + CRT_BINARY_NAME + ";", false);
av0.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
av0.visit("presentAfter", new Integer(25));
av0.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
index 30cf603..2838141 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.ir.desugar.annotations.version2;
-import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRTS_SIMPLE_NAME;
-import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRTS_BINARY_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRTS_INNER_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_BINARY_NAME;
import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
import org.objectweb.asm.AnnotationVisitor;
@@ -31,9 +32,9 @@
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
cw.visitInnerClass(
- CRT_NAME + "$" + CRTS_SIMPLE_NAME,
- CRT_NAME,
- CRTS_SIMPLE_NAME,
+ CRTS_BINARY_NAME,
+ CRT_BINARY_NAME,
+ CRTS_INNER_NAME,
ACC_PUBLIC + ACC_STATIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE);
{
@@ -48,17 +49,17 @@
{
mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
{
- av0 = mv.visitAnnotation("L" + CRT_NAME + "$" + CRTS_SIMPLE_NAME + ";", false);
+ av0 = mv.visitAnnotation("L" + CRTS_BINARY_NAME + ";", false);
{
AnnotationVisitor av1 = av0.visitArray("value");
{
- AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+ AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_BINARY_NAME + ";");
av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
av2.visit("presentAfter", new Integer(25));
av2.visitEnd();
}
{
- AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+ AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_BINARY_NAME + ";");
av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/C;"));
av2.visit("presentAfter", new Integer(28));
av2.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
new file mode 100644
index 0000000..94e2a9c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -0,0 +1,132 @@
+// 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.maindexlist;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MainDexWithSynthesizedClassesTest extends TestBase {
+
+ static final AndroidApiLevel nativeMultiDexLevel = AndroidApiLevel.L;
+
+ static final String EXPECTED = StringUtils.lines("AB");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevelsEndingAtExcluding(nativeMultiDexLevel)
+ .build();
+ }
+
+ public MainDexWithSynthesizedClassesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testFinal() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ } else {
+ D8TestCompileResult compileResult =
+ testForD8()
+ .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+ .addMainDexListClasses(TestClass.class, A.class)
+ .setMinApiThreshold(parameters.getApiLevel())
+ .compile();
+ checkCompilationResult(compileResult);
+ }
+ }
+
+ @Test
+ public void testIntermediate() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ D8TestCompileResult intermediateResult =
+ testForD8()
+ .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+ .setMinApiThreshold(parameters.getApiLevel())
+ .setIntermediate(true)
+ .compile();
+ D8TestCompileResult compileResult =
+ testForD8()
+ .addProgramFiles(intermediateResult.writeToZip())
+ .addMainDexListClasses(TestClass.class, A.class)
+ .setMinApiThreshold(parameters.getApiLevel())
+ .compile();
+ checkCompilationResult(compileResult);
+ }
+
+ private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception {
+ if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+ < nativeMultiDexLevel.getLevel()) {
+ compileResult
+ .runDex2Oat(parameters.getRuntime().asDex().getVm())
+ .assertNoVerificationErrors();
+ } else {
+ compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED);
+ }
+ Path out = temp.newFolder().toPath();
+ compileResult.apply(b -> b.app.writeToDirectory(out, OutputMode.DexIndexed));
+ Path classes = out.resolve("classes.dex");
+ Path classes2 = out.resolve("classes2.dex");
+ assertTrue(Files.exists(classes));
+ assertTrue(Files.exists(classes2));
+ checkContainsLambdaClasses(new CodeInspector(classes), A.class);
+ checkContainsLambdaClasses(new CodeInspector(classes2), B.class);
+ }
+
+ private void checkContainsLambdaClasses(CodeInspector inspector, Class<?> lambdaHolder) {
+ assertTrue(
+ inspector.allClasses().stream()
+ .anyMatch(
+ clazz ->
+ clazz.getOriginalName().contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
+ && clazz
+ .getOriginalName()
+ .contains("$" + lambdaHolder.getSimpleName() + "$")));
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println(new A().foo().get());
+ }
+ }
+
+ interface Getter {
+ String get();
+ }
+
+ static class A {
+ Getter foo() {
+ return () -> "A" + new B().foo().get();
+ }
+ }
+
+ static class B {
+ Getter foo() {
+ return () -> "B";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index f585226..2d30f24 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -95,11 +95,7 @@
obfuscatedToOriginalMapping = null;
}
Timing timing = new Timing("CodeInspector");
- InternalOptions options = new InternalOptions();
- options.enableCfFrontend = true;
- if (optionsConsumer != null) {
- optionsConsumer.accept(options);
- }
+ InternalOptions options = runOptionsConsumer(optionsConsumer);
dexItemFactory = options.itemFactory;
AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
application = new ApplicationReader(input, options, timing).read();