Merge commit '6c69177d44ff4783d78111d70d91a90805e2e962' into dev-release
diff --git a/src/main/java/com/android/tools/r8/annotations/SynthesizedClassMap.java b/src/main/java/com/android/tools/r8/annotations/SynthesizedClassMap.java new file mode 100644 index 0000000..b46a2ba --- /dev/null +++ b/src/main/java/com/android/tools/r8/annotations/SynthesizedClassMap.java
@@ -0,0 +1,15 @@ +// Copyright (c) 2017, 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface SynthesizedClassMap { + Class<?>[] value() default {}; +}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java index f2959bb..3483fbb 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -5,6 +5,7 @@ import com.android.tools.r8.dex.IndexedItemCollection; import com.android.tools.r8.dex.MixedSectionCollection; +import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.graph.DexValue.DexValueAnnotation; import com.android.tools.r8.graph.DexValue.DexValueArray; import com.android.tools.r8.graph.DexValue.DexValueInt; @@ -21,7 +22,10 @@ import com.android.tools.r8.utils.structural.StructuralMapping; import com.android.tools.r8.utils.structural.StructuralSpecification; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.TreeSet; import java.util.function.Function; public class DexAnnotation extends DexItem implements StructuralItem<DexAnnotation> { @@ -92,7 +96,8 @@ } if (annotation == options.itemFactory.dalvikFastNativeAnnotation || annotation == options.itemFactory.dalvikCriticalNativeAnnotation - || annotation == options.itemFactory.annotationSynthesizedClass) { + || annotation == options.itemFactory.annotationSynthesizedClass + || annotation == options.itemFactory.annotationSynthesizedClassMap) { return true; } if (options.processCovariantReturnTypeAnnotations) { @@ -358,6 +363,43 @@ return new DexValueString(factory.createString(string)); } + public static Collection<DexType> readAnnotationSynthesizedClassMap( + DexProgramClass clazz, DexItemFactory dexItemFactory) { + DexAnnotation foundAnnotation = + clazz.annotations().getFirstMatching(dexItemFactory.annotationSynthesizedClassMap); + if (foundAnnotation != null) { + if (foundAnnotation.annotation.elements.length != 1) { + throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation)); + } + DexAnnotationElement value = foundAnnotation.annotation.elements[0]; + if (value.name != dexItemFactory.valueString) { + throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation)); + } + DexValueArray existingList = value.value.asDexValueArray(); + if (existingList == null) { + throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation)); + } + Collection<DexType> synthesized = new ArrayList<>(existingList.values.length); + for (DexValue element : existingList.getValues()) { + if (!element.isDexValueType()) { + throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation)); + } + synthesized.add(element.asDexValueType().value); + } + return synthesized; + } + return Collections.emptyList(); + } + + private static String getInvalidSynthesizedClassMapMessage( + DexProgramClass annotatedClass, + DexAnnotation invalidAnnotation) { + return annotatedClass.toSourceString() + + " is annotated with invalid " + + invalidAnnotation.annotation.type.toString() + + ": " + invalidAnnotation.toString(); + } + public static DexAnnotation createAnnotationSynthesizedClass( SyntheticKind kind, DexType synthesizingContext, DexItemFactory dexItemFactory) { DexAnnotationElement kindElement = @@ -414,6 +456,26 @@ return new Pair<>(kind, valueElement.value.asDexValueType().getValue()); } + public static DexAnnotation createAnnotationSynthesizedClassMap( + TreeSet<DexType> synthesized, + DexItemFactory dexItemFactory) { + DexValueType[] values = synthesized.stream() + .map(DexValueType::new) + .toArray(DexValueType[]::new); + DexValueArray array = new DexValueArray(values); + DexAnnotationElement pair = + new DexAnnotationElement(dexItemFactory.createString("value"), array); + return new DexAnnotation( + VISIBILITY_BUILD, + new DexEncodedAnnotation( + dexItemFactory.annotationSynthesizedClassMap, new DexAnnotationElement[]{pair})); + } + + public static boolean isSynthesizedClassMapAnnotation(DexAnnotation annotation, + DexItemFactory factory) { + return annotation.annotation.type == factory.annotationSynthesizedClassMap; + } + public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) { DexEncodedAnnotation rewritten = rewriter.apply(annotation); if (rewritten == annotation) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 3f6eff3..0752d20 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -605,6 +605,8 @@ public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;"); public final DexType annotationSynthesizedClass = createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClass;"); + public final DexType annotationSynthesizedClassMap = + createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClassMap;"); public final DexType annotationCovariantReturnType = createStaticallyKnownType("Ldalvik/annotation/codegen/CovariantReturnType;"); public final DexType annotationCovariantReturnTypes =
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index 057a80f..762617e 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -115,6 +115,10 @@ return isAnnotationTypeLive; case DexAnnotation.VISIBILITY_BUILD: + if (DexAnnotation.isSynthesizedClassMapAnnotation(annotation, dexItemFactory)) { + // TODO(sgjesse) When should these be removed? + return true; + } if (!config.runtimeInvisibleAnnotations) { return false; }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java index a60967f..58315d4 100644 --- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java +++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -91,11 +91,6 @@ return this == NONE; } - public boolean isMainDexTypeThatShouldIncludeDependencies(DexType type) { - // Dependencies of 'type' are only needed if 'type' is a direct/executed main-dex type. - return classList.contains(type) || tracedRoots.contains(type); - } - public boolean isMainDex(ProgramDefinition definition) { return isFromList(definition) || isTracedRoot(definition) || isDependency(definition); }
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java index ba25312..3632f93 100644 --- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java +++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -264,6 +264,7 @@ dexItemFactory.annotationMethodParameters, dexItemFactory.annotationSourceDebugExtension, dexItemFactory.annotationSynthesizedClass, + dexItemFactory.annotationSynthesizedClassMap, dexItemFactory.annotationThrows, dexItemFactory.serializedLambdaType) .addAll(dexItemFactory.getJavaConversionTypes())
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java index bff9202..82f3204 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java +++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -15,6 +15,7 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.shaking.MainDexInfo; import java.util.Comparator; +import java.util.Set; /** * A synthesizing context is a description of the context that gives rise to a synthetic item. @@ -122,15 +123,14 @@ appView.rewritePrefix.rewriteType(hygienicType, rewrittenType); } - // TODO(b/180074885): Remove this once main-dex is traced at the end of of compilation. void addIfDerivedFromMainDexClass( - DexProgramClass externalSyntheticClass, MainDexInfo mainDexInfo) { - if (mainDexInfo.isMainDex(externalSyntheticClass)) { - return; - } + DexProgramClass externalSyntheticClass, + MainDexInfo mainDexInfo, + Set<DexType> allMainDexTypes) { // The input context type (not the annotated context) determines if the derived class is to be - // in main dex, as it is the input context type that is traced as part of main-dex tracing. - if (mainDexInfo.isMainDexTypeThatShouldIncludeDependencies(inputContextType)) { + // in main dex. + // TODO(b/168584485): Once resolved allMainDexTypes == mainDexClasses. + if (allMainDexTypes.contains(inputContextType)) { mainDexInfo.addSyntheticClass(externalSyntheticClass); } }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java index 2e70db6..65de13f 100644 --- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java +++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.AppInfo; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexAnnotation; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; @@ -31,9 +32,11 @@ import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; import com.android.tools.r8.utils.structural.RepresentativeMap; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.google.common.hash.HashCode; import java.util.ArrayList; @@ -45,6 +48,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Function; @@ -258,6 +262,7 @@ Result computeFinalSynthetics(AppView<?> appView) { assert verifyNoNestedSynthetics(); DexApplication application; + MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo(); Builder lensBuilder = new Builder(); ImmutableMap.Builder<DexType, SyntheticMethodReference> finalMethodsBuilder = ImmutableMap.builder(); @@ -285,6 +290,11 @@ ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses = finalClassesBuilder.build(); + handleSynthesizedClassMapping( + finalSyntheticProgramDefinitions, application, options, mainDexInfo, lensBuilder.typeMap); + + assert appView.appInfo().getMainDexInfo() == mainDexInfo; + Set<DexType> prunedSynthetics = Sets.newIdentityHashSet(); committed.forEachNonLegacyItem( reference -> { @@ -344,6 +354,96 @@ return true; } + private void handleSynthesizedClassMapping( + List<DexProgramClass> finalSyntheticClasses, + DexApplication application, + InternalOptions options, + MainDexInfo mainDexInfo, + Map<DexType, DexType> derivedMainDexTypesToIgnore) { + boolean includeSynthesizedClassMappingInOutput = shouldAnnotateSynthetics(options); + if (includeSynthesizedClassMappingInOutput) { + updateSynthesizedClassMapping(application, finalSyntheticClasses); + } + updateMainDexListWithSynthesizedClassMap(application, mainDexInfo, derivedMainDexTypesToIgnore); + if (!includeSynthesizedClassMappingInOutput) { + clearSynthesizedClassMapping(application); + } + } + + private void updateSynthesizedClassMapping( + DexApplication application, List<DexProgramClass> finalSyntheticClasses) { + ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized = + ArrayListMultimap.create(); + for (DexType type : committed.getLegacyTypes()) { + DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type)); + if (clazz != null) { + for (DexProgramClass origin : clazz.getSynthesizedFrom()) { + originalToSynthesized.put(origin, clazz); + } + } + } + for (DexProgramClass clazz : finalSyntheticClasses) { + for (DexProgramClass origin : clazz.getSynthesizedFrom()) { + originalToSynthesized.put(origin, clazz); + } + } + for (Map.Entry<DexProgramClass, Collection<DexProgramClass>> entry : + originalToSynthesized.asMap().entrySet()) { + DexProgramClass original = entry.getKey(); + // Use a tree set to make sure that we have an ordering on the types. + // These types are put in an array in annotations in the output and we + // need a consistent ordering on them. + TreeSet<DexType> synthesized = new TreeSet<>(DexType::compareTo); + entry.getValue().stream() + .map(dexProgramClass -> dexProgramClass.type) + .forEach(synthesized::add); + synthesized.addAll( + DexAnnotation.readAnnotationSynthesizedClassMap(original, application.dexItemFactory)); + + DexAnnotation updatedAnnotation = + DexAnnotation.createAnnotationSynthesizedClassMap( + synthesized, application.dexItemFactory); + + original.setAnnotations(original.annotations().getWithAddedOrReplaced(updatedAnnotation)); + } + } + + private void updateMainDexListWithSynthesizedClassMap( + DexApplication application, + MainDexInfo mainDexInfo, + Map<DexType, DexType> derivedMainDexTypesToIgnore) { + if (mainDexInfo.isEmpty()) { + return; + } + List<DexProgramClass> newMainDexClasses = new ArrayList<>(); + mainDexInfo.forEachExcludingDependencies( + dexType -> { + DexProgramClass programClass = + DexProgramClass.asProgramClassOrNull(application.definitionFor(dexType)); + if (programClass != null) { + Collection<DexType> derived = + DexAnnotation.readAnnotationSynthesizedClassMap( + programClass, application.dexItemFactory); + for (DexType type : derived) { + DexType mappedType = derivedMainDexTypesToIgnore.getOrDefault(type, type); + DexProgramClass syntheticClass = + DexProgramClass.asProgramClassOrNull(application.definitionFor(mappedType)); + if (syntheticClass != null) { + newMainDexClasses.add(syntheticClass); + } + } + } + }); + newMainDexClasses.forEach(mainDexInfo::addSyntheticClass); + } + + private void clearSynthesizedClassMapping(DexApplication application) { + for (DexProgramClass clazz : application.classes()) { + clazz.setAnnotations( + clazz.annotations().getWithout(application.dexItemFactory.annotationSynthesizedClassMap)); + } + } + private static DexApplication buildLensAndProgram( AppView<?> appView, Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups, @@ -354,8 +454,23 @@ DexApplication application = appView.appInfo().app(); DexItemFactory factory = appView.dexItemFactory(); List<DexProgramClass> newProgramClasses = new ArrayList<>(); - Set<DexType> pruned = Sets.newIdentityHashSet(); + // TODO(b/168584485): Remove this once class-mapping support is removed. + Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet(); + MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo(); + mainDexInfo.forEachExcludingDependencies( + mainDexType -> { + derivedMainDexTypes.add(mainDexType); + DexProgramClass mainDexClass = + DexProgramClass.asProgramClassOrNull( + appView.appInfo().definitionForWithoutExistenceAssert(mainDexType)); + if (mainDexClass != null) { + derivedMainDexTypes.addAll( + DexAnnotation.readAnnotationSynthesizedClassMap(mainDexClass, factory)); + } + }); + + Set<DexType> pruned = Sets.newIdentityHashSet(); syntheticMethodGroups.forEach( (syntheticType, syntheticGroup) -> { SyntheticMethodDefinition representative = syntheticGroup.getRepresentative(); @@ -456,7 +571,8 @@ addMainDexAndSynthesizedFromForMember( member, externalSyntheticClass, - appView.appInfo().getMainDexInfo(), + mainDexInfo, + derivedMainDexTypes, appForLookup::programDefinitionFor); } }); @@ -477,7 +593,8 @@ addMainDexAndSynthesizedFromForMember( member, externalSyntheticClass, - appView.appInfo().getMainDexInfo(), + mainDexInfo, + derivedMainDexTypes, appForLookup::programDefinitionFor); } }); @@ -527,8 +644,11 @@ SyntheticDefinition<?, ?, ?> member, DexProgramClass externalSyntheticClass, MainDexInfo mainDexInfo, + Set<DexType> derivedMainDexTypes, Function<DexType, DexProgramClass> definitions) { - member.getContext().addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo); + member + .getContext() + .addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo, derivedMainDexTypes); // TODO(b/168584485): Remove this once class-mapping support is removed. DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType()); if (from != null) { @@ -541,6 +661,7 @@ // This is currently also disabled on CF to CF desugaring to avoid missing class references to // the annotated classes. // TODO(b/147485959): Find an alternative encoding for synthetics to avoid missing-class refs. + // TODO(b/168584485): Remove support for main-dex tracing with the class-map annotation. return options.intermediate && !options.cfToCfDesugar; }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java index 43bbd99..82cfebf 100644 --- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -361,25 +361,15 @@ abstract D8IncrementalTestRunner test(String testName, String packageName, String mainClass); @Override - protected void testIntermediateWithMainDexList( - String packageName, - Path input, - int expectedMainDexListSize, - List<String> mainDexClasses, - List<String> mainDexOverApproximation) - throws Throwable { + protected void testIntermediateWithMainDexList(String packageName, Path input, + int expectedMainDexListSize, String... mainDexClasses) throws Throwable { // Skip those tests. Assume.assumeTrue(false); } @Override - protected Path buildDexThroughIntermediate( - String packageName, - Path input, - OutputMode outputMode, - AndroidApiLevel minApi, - List<String> mainDexClasses) - throws Throwable { + protected Path buildDexThroughIntermediate(String packageName, Path input, OutputMode outputMode, + AndroidApiLevel minApi, String... mainDexClasses) throws Throwable { // tests using this should already been skipped. throw new Unreachable(); }
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java index 4913bc5..4c88d91 100644 --- a/src/test/java/com/android/tools/r8/D8TestBuilder.java +++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -6,7 +6,6 @@ import com.android.tools.r8.D8Command.Builder; import com.android.tools.r8.TestBase.Backend; import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer; -import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; @@ -87,16 +86,4 @@ builder.addMainDexRulesFiles(mainDexRuleFiles); return self(); } - - public D8TestBuilder addMainDexRules(String... rules) { - builder.addMainDexRules(Arrays.asList(rules), Origin.unknown()); - return self(); - } - - public D8TestBuilder addMainDexKeepClassRules(Class<?>... classes) { - for (Class<?> clazz : classes) { - addMainDexRules("-keep class " + clazz.getTypeName()); - } - return self(); - } }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java index 52480be..2773cf1 100644 --- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java +++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -9,19 +9,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import com.android.tools.r8.ToolHelper.DexVm; import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.OffOrAuto; import com.android.tools.r8.utils.StringUtils; -import com.android.tools.r8.utils.StringUtils.BraceType; import com.android.tools.r8.utils.TestDescriptionWatcher; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.FoundClassSubject; @@ -31,8 +27,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.collect.Sets.SetView; import com.google.common.io.ByteStreams; import java.io.IOException; import java.io.InputStream; @@ -41,12 +35,13 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; @@ -119,24 +114,6 @@ return withBuilderTransformation(builder -> builder.addMainDexClasses(classes)); } - C withMainDexKeepClassRules(List<String> classes) { - return withBuilderTransformation( - builder -> { - if (builder instanceof D8Command.Builder) { - ((D8Command.Builder) builder) - .addMainDexRules( - ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown()); - } else if (builder instanceof R8Command.Builder) { - ((R8Command.Builder) builder) - .addMainDexRules( - ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown()); - } else { - fail("Unexpected builder type: " + builder.getClass()); - } - return builder; - }); - } - C withInterfaceMethodDesugaring(OffOrAuto behavior) { return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior); } @@ -488,20 +465,15 @@ testIntermediateWithMainDexList( "lambdadesugaring", 1, - ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"), - ImmutableList.of()); + "lambdadesugaring.LambdaDesugaring$I"); } @Test public void testLambdaDesugaringWithMainDexList2() throws Throwable { // Main dex class has many lambdas. - testIntermediateWithMainDexList( - "lambdadesugaring", - // TODO(b/180074885): Over approximation not present in R8. - this instanceof R8RunExamplesAndroidOTest ? 51 : 52, - ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"), - // TODO(b/180074885): Over approximation due to invoke-dynamic reference adds as dependency. - ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$D")); + testIntermediateWithMainDexList("lambdadesugaring", + 33, + "lambdadesugaring.LambdaDesugaring$Refs$B"); } @Test @@ -511,11 +483,7 @@ "interfacemethods", Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION), 2, - ImmutableList.of("interfacemethods.I1"), - // TODO(b/180074885): Over approximation due to including I1-CC by being derived from I1, - // but after desugaring I1 does not reference I1$-CC (the static method is moved), so it - // is incorrect to include I1-CC in the main dex. - ImmutableList.of("interfacemethods.I1$-CC")); + "interfacemethods.I1"); } @@ -526,11 +494,7 @@ "interfacemethods", Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION), 2, - ImmutableList.of("interfacemethods.I2"), - // TODO(b/180074885): Over approximation due to including I2$-CC by being derived from I2, - // but after desugaring I2 does not reference I2$-CC (the default method is moved), so it - // is incorrect to include I2$-CC in the main dex. - ImmutableList.of("interfacemethods.I2$-CC")); + "interfacemethods.I2"); } @Test @@ -546,23 +510,20 @@ private void testIntermediateWithMainDexList( String packageName, int expectedMainDexListSize, - List<String> mainDexClasses, - List<String> mainDexOverApproximation) + String... mainDexClasses) throws Throwable { testIntermediateWithMainDexList( packageName, Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION), expectedMainDexListSize, - mainDexClasses, - mainDexOverApproximation); + mainDexClasses); } protected void testIntermediateWithMainDexList( String packageName, Path input, int expectedMainDexListSize, - List<String> mainDexClasses, - List<String> mainDexOverApproximation) + String... mainDexClasses) throws Throwable { AndroidApiLevel minApi = AndroidApiLevel.K; @@ -573,18 +534,16 @@ .withMinApiLevel(minApi) .withOptionConsumer(option -> option.minimalMainDex = true) .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false) - .withMainDexKeepClassRules(mainDexClasses) + .withMainDexClass(mainDexClasses) .withKeepAll(); Path fullDexes = temp.getRoot().toPath().resolve(packageName + "full" + ZIP_EXTENSION); full.build(input, fullDexes); // Builds with intermediate in both output mode. - Path dexesThroughIndexedIntermediate = - buildDexThroughIntermediate( - packageName, input, OutputMode.DexIndexed, minApi, mainDexClasses); - Path dexesThroughFilePerInputClassIntermediate = - buildDexThroughIntermediate( - packageName, input, OutputMode.DexFilePerClassFile, minApi, mainDexClasses); + Path dexesThroughIndexedIntermediate = buildDexThroughIntermediate( + packageName, input, OutputMode.DexIndexed, minApi, mainDexClasses); + Path dexesThroughFilePerInputClassIntermediate = buildDexThroughIntermediate( + packageName, input, OutputMode.DexFilePerClassFile, minApi, mainDexClasses); // Collect main dex types. CodeInspector fullInspector = getMainDexInspector(fullDexes); @@ -592,45 +551,20 @@ getMainDexInspector(dexesThroughIndexedIntermediate); CodeInspector filePerInputClassIntermediateInspector = getMainDexInspector(dexesThroughFilePerInputClassIntermediate); - Set<String> fullMainClasses = new HashSet<>(); + Collection<String> fullMainClasses = new HashSet<>(); fullInspector.forAllClasses( clazz -> fullMainClasses.add(clazz.getFinalDescriptor())); - Set<String> indexedIntermediateMainClasses = new HashSet<>(); + Collection<String> indexedIntermediateMainClasses = new HashSet<>(); indexedIntermediateInspector.forAllClasses( clazz -> indexedIntermediateMainClasses.add(clazz.getFinalDescriptor())); - Set<String> filePerInputClassIntermediateMainClasses = new HashSet<>(); + Collection<String> filePerInputClassIntermediateMainClasses = new HashSet<>(); filePerInputClassIntermediateInspector.forAllClasses( clazz -> filePerInputClassIntermediateMainClasses.add(clazz.getFinalDescriptor())); // Check. Assert.assertEquals(expectedMainDexListSize, fullMainClasses.size()); - SetView<String> adjustedFull = - Sets.difference( - fullMainClasses, - new HashSet<>( - ListUtils.map(mainDexOverApproximation, DescriptorUtils::javaTypeToDescriptor))); - assertEqualSets(adjustedFull, indexedIntermediateMainClasses); - assertEqualSets(adjustedFull, filePerInputClassIntermediateMainClasses); - } - - <T> void assertEqualSets(Set<T> expected, Set<T> actual) { - SetView<T> missing = Sets.difference(expected, actual); - SetView<T> unexpected = Sets.difference(actual, expected); - if (missing.isEmpty() && unexpected.isEmpty()) { - return; - } - StringBuilder builder = new StringBuilder("Sets differ."); - if (!missing.isEmpty()) { - builder.append("\nMissing items: [\n "); - StringUtils.append(builder, missing, "\n ", BraceType.NONE); - builder.append("\n]"); - } - if (!unexpected.isEmpty()) { - builder.append("\nUnexpected items: [\n "); - StringUtils.append(builder, unexpected, "\n ", BraceType.NONE); - builder.append("\n]"); - } - fail(builder.toString()); + Assert.assertEquals(fullMainClasses, indexedIntermediateMainClasses); + Assert.assertEquals(fullMainClasses, filePerInputClassIntermediateMainClasses); } protected Path buildDexThroughIntermediate( @@ -638,7 +572,7 @@ Path input, OutputMode outputMode, AndroidApiLevel minApi, - List<String> mainDexClasses) + String... mainDexClasses) throws Throwable { Path intermediateDex = temp.getRoot().toPath().resolve(packageName + "intermediate" + ZIP_EXTENSION); @@ -657,7 +591,7 @@ test(packageName + "dex", packageName, "N/A") .withOptionConsumer(option -> option.minimalMainDex = true) .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false) - .withMainDexKeepClassRules(mainDexClasses) + .withMainDexClass(mainDexClasses) .withMinApiLevel(minApi) .withKeepAll(); @@ -710,7 +644,8 @@ } } - protected CodeInspector getMainDexInspector(Path zip) throws IOException { + protected CodeInspector getMainDexInspector(Path zip) + throws IOException, ExecutionException { try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) { try (InputStream in = zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java index 81f0674..05f84af 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -153,7 +153,7 @@ testForD8(parameters.getBackend()) .addProgramClasses(CLASSES) .setMinApi(parameters.getApiLevel()) - .addMainDexRules(keepMainProguardConfiguration(TestClass.class)) + .addMainDexListClasses(MiniAssert.class, TestClass.class, User2.class) .setProgramConsumer(mainDexConsumer) .compile() .inspect(this::checkExpectedSynthetics) @@ -185,8 +185,44 @@ testForD8() .addProgramFiles(perClassOutput) .setMinApi(parameters.getApiLevel()) - // Trace the classes run by main which will pick up their dependencies. - .addMainDexRules(keepMainProguardConfiguration(TestClass.class)) + .addMainDexListClasses(MiniAssert.class, TestClass.class, User2.class) + .setProgramConsumer(mainDexConsumer) + .compile() + .inspect(this::checkExpectedSynthetics) + .run(parameters.getRuntime(), TestClass.class, getRunArgs()) + .assertSuccessWithOutput(EXPECTED); + checkMainDex(mainDexConsumer); + } + + // TODO(b/168584485): This test should be removed once support is dropped. + @Test + public void testD8MergingWithTraceCf() throws Exception { + assumeTrue(parameters.isDexRuntime()); + Path out1 = + testForD8() + .addProgramClasses(User1.class) + .addClasspathClasses(CLASSES) + .setIntermediate(true) + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(); + + Path out2 = + testForD8() + .addProgramClasses(User2.class) + .addClasspathClasses(CLASSES) + .setIntermediate(true) + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(); + + MainDexConsumer mainDexConsumer = new MainDexConsumer(); + testForD8(parameters.getBackend()) + .addProgramClasses(TestClass.class, MiniAssert.class) + .addProgramFiles(out1, out2) + .setMinApi(parameters.getApiLevel()) + .addMainDexListClassReferences( + traceMainDex(CLASSES, Collections.emptyList()).getMainDexList()) .setProgramConsumer(mainDexConsumer) .compile() .inspect(this::checkExpectedSynthetics)
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 d3d99ee..1bbcb9e 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -116,11 +116,12 @@ @Test public void testD8DesugaredLambdasInMainDexList() throws Exception { + Path mainDexList = writeTextToTempFile(testClassMainDexName); TestMainDexListConsumer consumer = new TestMainDexListConsumer(); testForD8() .setMinApi(AndroidApiLevel.K) .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class)) - .addMainDexListClasses(TestClass.class) + .addMainDexListFiles(ImmutableList.of(mainDexList)) .setMainDexListConsumer(consumer) .compile(); assertTrue(consumer.called); @@ -128,6 +129,7 @@ @Test public void testD8DesugaredLambdasInMainDexListMerging() throws Exception { + Path mainDexList = writeTextToTempFile(testClassMainDexName); // Build intermediate dex code first. Path dexOutput = testForD8() @@ -141,7 +143,7 @@ testForD8() .setMinApi(AndroidApiLevel.K) .addProgramFiles(dexOutput) - .addMainDexKeepClassRules(TestClass.class) + .addMainDexListFiles(ImmutableList.of(mainDexList)) .setMainDexListConsumer(consumer) .compile(); assertTrue(consumer.called);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java index 5e88d7d..9fa7fe9 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -72,7 +72,7 @@ D8TestCompileResult compileResult = testForD8() .addProgramFiles(intermediateResult.writeToZip()) - .addMainDexKeepClassRules(TestClass.class, A.class) + .addMainDexListClasses(TestClass.class, A.class) .setMinApiThreshold(parameters.getApiLevel()) .compile(); checkCompilationResult(compileResult);