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 e3becb5..a7eca9c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -292,6 +292,7 @@
}
if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
+ application = DesugaredLibraryRetargeter.amendLibraryWithRetargetedMembers(appView);
}
InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
BackportedMethodRewriter.registerAssumedLibraryTypes(options);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index a0fd03b..b34c9f1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -31,6 +31,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
+import java.util.function.Function;
/* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
* minimal amount of knowledge in the overall program, provided through classpath. Basic
@@ -57,6 +58,11 @@
return new AppInfoWithClassHierarchy(appInfo);
}
+ public AppInfoWithClassHierarchy rebuildWithClassHierarchy(
+ Function<DexApplication, DexApplication> fn) {
+ return new AppInfoWithClassHierarchy(fn.apply(app()));
+ }
+
@Override
public boolean hasClassHierarchy() {
assert checkIfObsolete();
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 5150413..3d9ea9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -259,6 +259,10 @@
return accessFlags;
}
+ public DexString getName() {
+ return getReference().name;
+ }
+
public DexMethod getReference() {
return method;
}
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 dbccbd6..4ad4459 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -195,4 +195,13 @@
public boolean isInstanceInitializer(DexDefinitionSupplier definitions) {
return definitions.dexItemFactory().isConstructor(this);
}
+
+ 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 5f0a349..5844b8e 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -252,6 +252,12 @@
return self();
}
+ public Builder addLibraryClasses(Collection<DexLibraryClass> classes) {
+ libraryClasses =
+ ImmutableList.<DexLibraryClass>builder().addAll(libraryClasses).addAll(classes).build();
+ return self();
+ }
+
@Override
public DirectMappedDexApplication build() {
// Rebuild the map. This will fail if keys are not unique.
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..e007f4e 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -19,6 +19,10 @@
return nestHost;
}
+ public static NestHostClassAttribute none() {
+ return null;
+ }
+
public void write(ClassWriter writer, NamingLens lens) {
assert nestHost != null;
writer.visitNestHost(lens.lookupInternalName(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
index d81df77..c84f803 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -4,12 +4,15 @@
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.AppInfoWithClassHierarchy;
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.DexApplication;
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;
@@ -18,8 +21,14 @@
import com.android.tools.r8.graph.DexProto;
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.GenericSignature.ClassTypeSignature;
+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.graph.ResolutionResult;
import com.android.tools.r8.ir.code.IRCode;
@@ -28,6 +37,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.Maps;
@@ -35,9 +45,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
@@ -77,6 +90,109 @@
}
}
+ public static DirectMappedDexApplication amendLibraryWithRetargetedMembers(
+ AppView<AppInfoWithClassHierarchy> 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::addDirectMethods);
+ DirectMappedDexApplication newApplication =
+ appView
+ .appInfo()
+ .app()
+ .asDirect()
+ .builder()
+ .addLibraryClasses(synthesizedLibraryClasses.values())
+ .build();
+ appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(app -> newApplication));
+ return newApplication;
+ }
+
+ private static Map<DexType, DexLibraryClass> synthesizeLibraryClassesForRetargetedMembers(
+ AppView<AppInfoWithClassHierarchy> 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<AppInfoWithClassHierarchy> 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;
+ }
+
private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
StringDiagnostic warning =
new StringDiagnostic(
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 982fb65..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.errors.InternalCompilerError;
-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 InternalCompilerError);
- 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 {