Synthesize retargeted library classes
Bug: 171197204
Bug: 170677722
Change-Id: I37f8f7a95ff8ed865b90926d91e293b32b2af696
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e9ea099..946a5be 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.EnumInfoMapCollector;
@@ -263,6 +264,10 @@
AppView.createForR8(new AppInfoWithSubtyping(application), options);
appView.setAppServices(AppServices.builder(appView).build());
+ if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+ application = DesugaredLibraryRetargeter.amendLibraryWithRetargetedMembers(appView);
+ }
+
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
timing.begin("Strip unused code");
Set<DexType> classesToRetainInnerClassAttributeFor = null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8c067cc..9007252 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -258,6 +258,14 @@
assert parameterAnnotationsList != null;
}
+ public DexString getName() {
+ return getReference().name;
+ }
+
+ public DexMethod getReference() {
+ return method;
+ }
+
public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
return annotations.keepIf(
annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index de97baf..9312434 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,13 +6,13 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.naming.NamingLens;
-import com.google.common.collect.Maps;
-import java.util.Map;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
+import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
implements PresortedComparable<DexMethod> {
@@ -232,4 +232,13 @@
assert isSingleVirtualMethodCached(receiverType);
return singleTargetCache.get(receiverType);
}
+
+ public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createMethod(
+ holder, dexItemFactory.prependTypeToProto(type, proto), name);
+ }
+
+ public DexMethod withHolder(DexType holder, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createMethod(holder, proto, name);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f8aaa42..cd127f0 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -191,7 +191,7 @@
public static class Builder extends DexApplication.Builder<Builder> {
- private final ImmutableList<DexLibraryClass> libraryClasses;
+ private ImmutableList<DexLibraryClass> libraryClasses;
private final ImmutableList<DexClasspathClass> classpathClasses;
Builder(LazyLoadedDexApplication application) {
@@ -209,13 +209,19 @@
classpathClasses = application.classpathClasses;
}
+ public Builder addLibraryClasses(Collection<DexLibraryClass> classes) {
+ libraryClasses =
+ ImmutableList.<DexLibraryClass>builder().addAll(libraryClasses).addAll(classes).build();
+ return self();
+ }
+
@Override
Builder self() {
return this;
}
@Override
- public DexApplication build() {
+ public DirectMappedDexApplication build() {
// Rebuild the map. This will fail if keys are not unique.
// TODO(zerny): Consider not rebuilding the map if no program classes are added.
Map<DexType, DexClass> allClasses =
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 5457311..82f34d7 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -32,6 +32,10 @@
this.enclosingMethod = enclosingMethod;
}
+ public static EnclosingMethodAttribute none() {
+ return null;
+ }
+
public void write(ClassWriter writer, NamingLens lens) {
if (enclosingMethod != null) {
writer.visitOuterClass(
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index b40f328..52d1220 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collections;
+import java.util.List;
import org.objectweb.asm.ClassWriter;
/** Representation of an entry in the Java InnerClasses attribute table. */
@@ -38,6 +40,10 @@
this.innerName = innerName;
}
+ public static List<InnerClassAttribute> emptyList() {
+ return Collections.emptyList();
+ }
+
public boolean isNamed() {
return innerName != null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
index 96fb82b..e1f9da1 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -15,6 +15,10 @@
this.nestHost = nestHost;
}
+ public static NestHostClassAttribute none() {
+ return null;
+ }
+
public DexType getNestHost() {
return nestHost;
}
diff --git a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
index f88a39f..f9d1a35 100644
--- a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
@@ -5,6 +5,8 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.naming.NamingLens;
+import java.util.Collections;
+import java.util.List;
import org.objectweb.asm.ClassWriter;
public class NestMemberClassAttribute {
@@ -15,6 +17,10 @@
this.nestMember = nestMember;
}
+ public static List<NestMemberClassAttribute> emptyList() {
+ return Collections.emptyList();
+ }
+
public DexType getNestMember() {
return nestMember;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
new file mode 100644
index 0000000..b251860
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2020, 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.ir.desugar;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class DesugaredLibraryRetargeter {
+
+ public static DirectMappedDexApplication amendLibraryWithRetargetedMembers(
+ AppView<AppInfoWithSubtyping> appView) {
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+ Map<DexType, DexLibraryClass> synthesizedLibraryClasses =
+ synthesizeLibraryClassesForRetargetedMembers(appView, retargetCoreLibMember);
+ Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedLibraryMethods =
+ synthesizedMembersForRetargetClasses(
+ appView, retargetCoreLibMember, synthesizedLibraryClasses);
+ synthesizedLibraryMethods.forEach(DexLibraryClass::appendDirectMethods);
+ DirectMappedDexApplication newApplication =
+ appView
+ .appInfo()
+ .app()
+ .asDirect()
+ .builder()
+ .addLibraryClasses(synthesizedLibraryClasses.values())
+ .build();
+ appView.setAppInfo(new AppInfoWithSubtyping(newApplication));
+ return newApplication;
+ }
+
+ private static Map<DexType, DexLibraryClass> synthesizeLibraryClassesForRetargetedMembers(
+ AppView<AppInfoWithSubtyping> appView,
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ Map<DexType, DexLibraryClass> synthesizedLibraryClasses = new LinkedHashMap<>();
+ for (Map<DexType, DexType> oldToNewTypeMap : retargetCoreLibMember.values()) {
+ for (DexType newType : oldToNewTypeMap.values()) {
+ if (appView.definitionFor(newType) == null) {
+ synthesizedLibraryClasses.computeIfAbsent(
+ newType,
+ type ->
+ // Synthesize a library class with the given name. Note that this is assuming that
+ // the library class inherits directly from java.lang.Object, does not implement
+ // any interfaces, etc.
+ new DexLibraryClass(
+ type,
+ Kind.CF,
+ new SynthesizedOrigin(
+ "Desugared library retargeter", DesugaredLibraryRetargeter.class),
+ ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC),
+ dexItemFactory.objectType,
+ DexTypeList.empty(),
+ dexItemFactory.createString("DesugaredLibraryRetargeter"),
+ NestHostClassAttribute.none(),
+ NestMemberClassAttribute.emptyList(),
+ EnclosingMethodAttribute.none(),
+ InnerClassAttribute.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ dexItemFactory.getSkipNameValidationForTesting()));
+ }
+ }
+ }
+ return synthesizedLibraryClasses;
+ }
+
+ private static Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembersForRetargetClasses(
+ AppView<AppInfoWithSubtyping> appView,
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
+ Map<DexType, DexLibraryClass> synthesizedLibraryClasses) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ Map<DexLibraryClass, Set<DexEncodedMethod>> synthesizedMembers = new IdentityHashMap<>();
+ for (Entry<DexString, Map<DexType, DexType>> entry : retargetCoreLibMember.entrySet()) {
+ DexString methodName = entry.getKey();
+ Map<DexType, DexType> types = entry.getValue();
+ types.forEach(
+ (oldType, newType) -> {
+ DexClass oldClass = appView.definitionFor(oldType);
+ DexLibraryClass newClass = synthesizedLibraryClasses.get(newType);
+ if (oldClass == null || newClass == null) {
+ return;
+ }
+ for (DexEncodedMethod method :
+ oldClass.methods(method -> method.getName() == methodName)) {
+ DexMethod retargetMethod = method.getReference().withHolder(newType, dexItemFactory);
+ if (!method.isStatic()) {
+ retargetMethod = retargetMethod.withExtraArgumentPrepended(oldType, dexItemFactory);
+ }
+ synthesizedMembers
+ .computeIfAbsent(
+ newClass,
+ ignore ->
+ new TreeSet<>((x, y) -> x.getReference().slowCompareTo(y.getReference())))
+ .add(
+ new DexEncodedMethod(
+ retargetMethod,
+ MethodAccessFlags.fromCfAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ null,
+ true));
+ }
+ });
+ }
+ return synthesizedMembers;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index 12b8b09..55b95a9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -4,17 +4,14 @@
package com.android.tools.r8.classmerging.vertical;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,21 +34,19 @@
@Test
public void test() throws Exception {
- try {
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(TestClass.class)
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
- .enableNeverClassInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .compile();
- assertTrue(parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
- } catch (CompilationFailedException e) {
- // TODO(b/170677722): Fix compilation failure.
- assertTrue(parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
- assertTrue(e.getCause() instanceof AbortException);
- assertThat(e.getCause().getMessage(), containsString("FORCE inlining on non-inlinable"));
- }
+ // Regression test for b/170677722.
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ assertThat(inspector.clazz(B.class), isPresent());
+ });
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
index 95fef57..415a868 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
@@ -5,13 +5,12 @@
package com.android.tools.r8.ir.optimize.inliner;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,14 +40,10 @@
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
- inspector -> {
- // TODO(b/171197204): Method should be inlined.
- assertThat(
- inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
- notIf(
- isPresent(),
- parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)));
- });
+ inspector ->
+ assertThat(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
+ not(isPresent())));
}
static class TestClass {