Desugared lib: fix minification
- do not rename duplicated call-backs in R8
- make sure wrapper methods are kept in R8
Bug:148841210
Change-Id: I868ca46de98debefa3f56999ef06fea2a5e16043
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 61d7abe..c85439b 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -3,25 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph.analysis;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -96,28 +89,19 @@
DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
if (appView.definitionFor(mockType) == null) {
assert DesugaredLibraryAPIConverter.isVivifiedType(mockType);
- classpathClasses.add(
- new DexClasspathClass(
- mockType,
- Kind.CF,
- new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
- ClassAccessFlags.fromDexAccessFlags(
- Constants.ACC_SYNTHETIC
- | Constants.ACC_PUBLIC
- | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- appView.dexItemFactory().createString("vivified"),
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting()));
+ assert wrapper.instanceFields().size() == 1;
+ DexType typeToMock = wrapper.instanceFields().get(0).field.type;
+ DexClass classToMock = appView.definitionFor(typeToMock);
+ assert classToMock != null;
+ DexClasspathClass mockedSuperClass =
+ converter.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+ classpathClasses.add(mockedSuperClass);
+ for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
+ // The mock is generated at the end of the enqueuing phase, so we need to manually set the
+ // library override.
+ assert mockedSuperClass.lookupVirtualMethod(virtualMethod.method) != null;
+ virtualMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+ }
}
}
return classpathClasses;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 5df6a03..ceed9ef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -303,6 +304,11 @@
return wrapperSynthesizor.synthesizeWrappersAndMapToReverse();
}
+ public DexClasspathClass synthesizeClasspathMock(
+ DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+ return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+ }
+
private List<DexEncodedMethod> generateCallbackMethods(
Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
List<DexEncodedMethod> newDexEncodedMethods = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index d3206e7..069015f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
@@ -12,6 +13,7 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -32,6 +34,7 @@
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -107,12 +110,14 @@
private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
private final DexItemFactory factory;
private final DesugaredLibraryAPIConverter converter;
+ private final DexString vivifiedSourceFile;
DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.dexWrapperPrefix = factory.createString("L" + WRAPPER_PREFIX);
this.converter = converter;
+ this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
}
public static boolean isSynthesizedWrapper(DexType type) {
@@ -347,6 +352,51 @@
return finalizeWrapperMethods(generatedMethods, finalMethods);
}
+ private DexEncodedMethod[] synthesizeVirtualMethodsForClasspathMock(
+ DexClass dexClass, DexType mockType) {
+ List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+ List<DexEncodedMethod> generatedMethods = new ArrayList<>();
+ // Generate only abstract methods for library override detection.
+ for (DexEncodedMethod dexEncodedMethod : dexMethods) {
+ DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+ assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+ if (!dexEncodedMethod.isFinal()) {
+ DexMethod methodToInstall =
+ DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+ dexEncodedMethod.method, mockType, appView);
+ DexEncodedMethod newDexEncodedMethod =
+ newSynthesizedMethod(methodToInstall, dexEncodedMethod, null);
+ generatedMethods.add(newDexEncodedMethod);
+ }
+ }
+ return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+ }
+
+ DexClasspathClass synthesizeClasspathMock(
+ DexClass classToMock, DexType mockType, boolean mockIsInterface) {
+ return new DexClasspathClass(
+ mockType,
+ Kind.CF,
+ new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
+ ClassAccessFlags.fromDexAccessFlags(
+ Constants.ACC_SYNTHETIC
+ | Constants.ACC_PUBLIC
+ | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ vivifiedSourceFile,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ synthesizeVirtualMethodsForClasspathMock(classToMock, mockType),
+ appView.dexItemFactory().getSkipNameValidationForTesting());
+ }
+
private DexEncodedMethod[] finalizeWrapperMethods(
List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
if (finalMethods.isEmpty()) {
@@ -376,7 +426,11 @@
DexMethod methodToInstall, DexEncodedMethod template, Code code) {
MethodAccessFlags newFlags = template.accessFlags.copy();
assert newFlags.isPublic();
- newFlags.unsetAbstract();
+ if (code == null) {
+ newFlags.setAbstract();
+ } else {
+ newFlags.unsetAbstract();
+ }
// TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
newFlags.unsetBridge();
newFlags.setSynthetic();
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index d506d84..1bc05e9 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -276,14 +276,6 @@
DexString reservedName = strategy.getReservedName(method, holder);
if (reservedName != null) {
state.reserveName(reservedName, method.method);
- // This is reserving names which after prefix rewriting will actually override a library
- // method.
- if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
- state.reserveName(
- reservedName,
- DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
- method.method, method.method.holder, appView));
- }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index e768589..359de49 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -213,11 +213,13 @@
final AppView<?> appView;
private final DexItemFactory factory;
+ private final boolean desugaredLibraryRenaming;
public MinifierMemberNamingStrategy(AppView<?> appView) {
super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.desugaredLibraryRenaming = appView.rewritePrefix.isRewriting();
}
@Override
@@ -260,6 +262,12 @@
|| appView.rootSet().mayNotBeMinified(method.method, appView)) {
return method.method.name;
}
+ if (desugaredLibraryRenaming
+ && method.isLibraryMethodOverride().isTrue()
+ && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+ // With desugared library, call-backs names are reserved here.
+ return method.method.name;
+ }
return null;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 7c468b0..7e381ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1681,6 +1681,7 @@
ResolutionResult firstResolution =
appView.appInfo().resolveMethod(instantiatedClass, method.method);
markResolutionAsLive(libraryClass, firstResolution);
+ markOverridesAsLibraryMethodOverrides(method.method, instantiatedClass);
// Due to API conversion, some overrides can be hidden since they will be rewritten. See
// class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -1695,9 +1696,9 @@
ResolutionResult secondResolution =
appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
markResolutionAsLive(libraryClass, secondResolution);
+ markOverridesAsLibraryMethodOverrides(methodToResolve, instantiatedClass);
}
- markOverridesAsLibraryMethodOverrides(method, instantiatedClass);
}
}
@@ -1714,13 +1715,13 @@
}
private void markOverridesAsLibraryMethodOverrides(
- DexEncodedMethod libraryMethod, DexProgramClass instantiatedClass) {
+ DexMethod libraryMethod, DexProgramClass instantiatedClass) {
Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(instantiatedClass);
Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(instantiatedClass);
while (!worklist.isEmpty()) {
DexProgramClass clazz = worklist.removeFirst();
assert visited.contains(clazz);
- DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod.method);
+ DexEncodedMethod libraryMethodOverride = clazz.lookupVirtualMethod(libraryMethod);
if (libraryMethodOverride != null) {
if (libraryMethodOverride.isLibraryMethodOverride().isTrue()) {
continue;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 38e8bac..04630af 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -7,96 +7,137 @@
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
+import org.junit.BeforeClass;
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 CallBackConversionTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT = StringUtils.lines("0", "1", "0", "1");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public CallBackConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .setMinApi(MIN_SUPPORTED)
+ .addProgramClasses(CustomLibClass.class)
+ .compile()
+ .writeToZip();
+ }
+
+ private static void assertDuplicatedAPI(CodeInspector i) {
+ List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
+ assertEquals(2, virtualMethods.size());
+ assertTrue(
+ virtualMethods.stream()
+ .anyMatch(
+ m ->
+ m.getMethod()
+ .method
+ .proto
+ .parameters
+ .values[0]
+ .toString()
+ .equals("j$.util.function.Consumer")));
+ assertTrue(
+ virtualMethods.stream()
+ .anyMatch(
+ m ->
+ m.getMethod()
+ .method
+ .proto
+ .parameters
+ .values[0]
+ .toString()
+ .equals("java.util.function.Consumer")));
+ }
+
@Test
public void testCallBack() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Impl.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .inspect(
- i -> {
- // foo(j$) and foo(java)
- List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
- assertEquals(2, virtualMethods.size());
- assertTrue(
- virtualMethods.stream()
- .anyMatch(
- m ->
- m.getMethod()
- .method
- .proto
- .parameters
- .values[0]
- .toString()
- .equals("j$.util.function.Consumer")));
- assertTrue(
- virtualMethods.stream()
- .anyMatch(
- m ->
- m.getMethod()
- .method
- .proto
- .parameters
- .values[0]
- .toString()
- .equals("java.util.function.Consumer")));
- })
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
- .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+ .inspect(CallBackConversionTest::assertDuplicatedAPI)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testCallBackR8() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(Backend.DEX)
.addKeepMainRule(Impl.class)
.noMinification()
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Impl.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::assertLibraryOverridesThere)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
- .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+ .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testCallBackR8Minifying() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(Backend.DEX)
.addKeepMainRule(Impl.class)
- .setMinApi(AndroidApiLevel.B)
+ .setMinApi(parameters.getApiLevel())
.addProgramClasses(Impl.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::assertLibraryOverridesThere)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
- .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
private void assertLibraryOverridesThere(CodeInspector i) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index b13ade6..835d1cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -4,42 +4,145 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import static com.android.tools.r8.Collectors.toSingle;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
+import org.junit.BeforeClass;
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 ConversionIntroduceInterfaceMethodTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "action called from j$ consumer",
+ "forEach called",
+ "action called from java consumer",
+ "forEach called");
+ private static Path CUSTOM_LIB;
+
+ @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public ConversionIntroduceInterfaceMethodTest(
+ TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .setMinApi(MIN_SUPPORTED)
+ .addProgramClasses(CustomLibClass.class)
+ .compile()
+ .writeToZip();
+ }
+
@Test
- public void testNoInterfaceMethods() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ public void testNoInterfaceMethodsD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
- .setMinApi(AndroidApiLevel.B)
- .addProgramClasses(
- MyCollectionInterface.class,
- MyCollectionInterfaceAbstract.class,
- MyCollection.class,
- Executor.class)
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
.addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(
- StringUtils.lines(
- "action called from j$ consumer",
- "forEach called",
- "action called from java consumer",
- "forEach called"));
+ .inspect(this::assertDoubleForEach)
+ .inspect(this::assertWrapperMethodsPresent)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ private void assertDoubleForEach(CodeInspector inspector) {
+ System.out.println(inspector.allClasses().size());
+ FoundClassSubject myCollection =
+ inspector.allClasses().stream()
+ .filter(
+ c ->
+ c.getOriginalName()
+ .startsWith(
+ "com.android.tools.r8.desugar.desugaredlibrary.conversiontests")
+ && !c.getOriginalName().contains("Executor")
+ && !c.getOriginalName().contains("$-CC")
+ && !c.getDexClass().isInterface())
+ .collect(toSingle());
+ assertEquals(
+ "Missing duplicated forEach",
+ 2,
+ myCollection.getDexClass().virtualMethods().stream()
+ .filter(m -> m.method.name.toString().equals("forEach"))
+ .count());
+ }
+
+ private void assertWrapperMethodsPresent(CodeInspector inspector) {
+ List<FoundClassSubject> wrappers =
+ inspector.allClasses().stream()
+ .filter(
+ c ->
+ !c.getFinalName()
+ .startsWith(
+ "com.android.tools.r8.desugar.desugaredlibrary.conversiontests"))
+ .collect(Collectors.toList());
+ for (FoundClassSubject wrapper : wrappers) {
+ assertTrue(wrapper.virtualMethods().size() > 0);
+ }
+ }
+
+ @Test
+ public void testNoInterfaceMethodsR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(MyCollectionInterface.class, MyCollection.class, Executor.class)
+ .addKeepMainRule(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertDoubleForEach)
+ .inspect(this::assertWrapperMethodsPresent)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
static class CustomLibClass {
@@ -91,7 +194,6 @@
return false;
}
- @NotNull
@Override
public Iterator<E> iterator() {
return null;
@@ -142,12 +244,4 @@
@Override
public void clear() {}
}
-
- interface MyCollectionInterfaceAbstract<E> extends Collection<E> {
-
- // The following method override a method from Iterable and use a desugared type.
- // API conversion is required.
- @Override
- void forEach(Consumer<? super E> action);
- }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
index 631f721..10823c3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/MoreFunctionConversionTest.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-import static org.hamcrest.core.AllOf.allOf;
-import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.D8TestCompileResult;
@@ -97,9 +95,7 @@
shrinkDesugaredLibrary)
.addRunClasspathFiles(CUSTOM_LIB)
.run(parameters.getRuntime(), Executor.class)
- // TODO(b/139451198): Multiple library files are not supported with high API levels.
- .assertFailureWithErrorThatMatches(
- allOf(containsString("AbstractMethodError"), containsString("Function.apply")));
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
// If we have the exact same lambda in both, but one implements j$..Function and the other