Version 2.0.25
Cherry-pick: Mark default methods as live if a sub-interface is
instantiated
CL: https://r8-review.googlesource.com/48019
Cherry-pick: Resolve default methods in sub-interfaces
CL: https://r8-review.googlesource.com/48018
Cherry-pick: Rename callsite different from the implementing
interfaces' methods
CL: https://r8-review.googlesource.com/48007
Bug: 148447828
Bug: 148168065
Bug: 148368442
Change-Id: I18a711bbd3a404e17aa3dd6bd7d9d5f5bca72b9e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 1b43f56..d2ef72e 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.0.24";
+ public static final String LABEL = "2.0.25";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 4854741..229d8eb 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -341,12 +341,7 @@
// public void bar() { }
// }
//
- if (resolvedMethod.hasCode()) {
- DexProgramClass holder = resolvedHolder.asProgramClass();
- if (appInfo.hasAnyInstantiatedLambdas(holder)) {
- result.add(resolvedMethod);
- }
- }
+ addIfDefaultMethodWithLambdaInstantiations(appInfo, resolvedMethod, result);
DexMethod method = resolvedMethod.method;
Consumer<DexEncodedMethod> addIfNotAbstract =
@@ -356,10 +351,8 @@
}
};
// Default methods are looked up when looking at a specific subtype that does not override
- // them.
- // Otherwise, we would look up default methods that are actually never used. However, we have
- // to
- // add bridge methods, otherwise we can remove a bridge that will be used.
+ // them. Otherwise, we would look up default methods that are actually never used. However, we
+ // have to add bridge methods, otherwise we can remove a bridge that will be used.
Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
m -> {
if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
@@ -374,7 +367,11 @@
if (clazz.isInterface()) {
ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
if (targetMethods.isSingleResolution()) {
- addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
+ // Sub-interfaces can have default implementations that override the resolved method.
+ // Therefore we have to add default methods in sub interfaces.
+ DexEncodedMethod singleTarget = targetMethods.getSingleTarget();
+ addIfDefaultMethodWithLambdaInstantiations(appInfo, singleTarget, result);
+ addIfNotAbstractAndBridge.accept(singleTarget);
}
} else {
ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
@@ -385,6 +382,19 @@
}
return result;
}
+
+ private void addIfDefaultMethodWithLambdaInstantiations(
+ AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexEncodedMethod> result) {
+ if (method == null) {
+ return;
+ }
+ if (method.hasCode()) {
+ DexProgramClass holder = appInfo.definitionForProgramType(method.method.holder);
+ if (appInfo.hasAnyInstantiatedLambdas(holder)) {
+ result.add(method);
+ }
+ }
+ }
}
abstract static class EmptyResult extends ResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 6c097a4..a1789b0 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -10,6 +10,7 @@
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.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DisjointSets;
import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
@@ -241,6 +242,7 @@
private final Set<DexCallSite> callSites = new HashSet<>();
private final Map<DexMethod, Set<InterfaceReservationState>> methodStates = new HashMap<>();
+ private final List<DexMethod> callSiteCollidingMethods = new ArrayList<>();
void addState(DexMethod method, InterfaceReservationState interfaceState) {
methodStates.computeIfAbsent(method, m -> new HashSet<>()).add(interfaceState);
@@ -248,6 +250,7 @@
void appendMethodGroupState(InterfaceMethodGroupState state) {
callSites.addAll(state.callSites);
+ callSiteCollidingMethods.addAll(state.callSiteCollidingMethods);
for (DexMethod key : state.methodStates.keySet()) {
methodStates.computeIfAbsent(key, k -> new HashSet<>()).addAll(state.methodStates.get(key));
}
@@ -460,6 +463,37 @@
groupState.addCallSite(callSite);
callSiteMethods.add(wrapped);
}
+ if (callSiteMethods.isEmpty()) {
+ return;
+ }
+ // For intersection types, we have to iterate all the multiple interfaces to look for
+ // methods with the same signature.
+ List<DexType> implementedInterfaces =
+ LambdaDescriptor.getInterfaces(callSite, appView.appInfo());
+ if (implementedInterfaces != null) {
+ for (int i = 1; i < implementedInterfaces.size(); i++) {
+ // Add the merging state for all additional implemented interfaces into the state
+ // for the group, if the name is different, to ensure that we do not pick the same
+ // name.
+ DexClass iface = appView.definitionFor(implementedInterfaces.get(i));
+ assert iface.isInterface();
+ for (DexEncodedMethod implementedMethod : implementedMethods) {
+ for (DexEncodedMethod virtualMethod : iface.virtualMethods()) {
+ boolean differentName =
+ !implementedMethod.method.name.equals(virtualMethod.method.name);
+ if (differentName
+ && MethodJavaSignatureEquivalence.getEquivalenceIgnoreName()
+ .equivalent(implementedMethod.method, virtualMethod.method)) {
+ InterfaceMethodGroupState interfaceMethodGroupState =
+ globalStateMap.computeIfAbsent(
+ equivalence.wrap(implementedMethod.method),
+ k -> new InterfaceMethodGroupState());
+ interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod.method);
+ }
+ }
+ }
+ }
+ }
if (callSiteMethods.size() > 1) {
// Implemented interfaces have different protos. Unify them.
Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
@@ -550,6 +584,27 @@
callSiteRenamings.put(callSite, newName);
}
}
+
+ // After all naming is completed for callsites, we must ensure to rename all interface methods
+ // that can collide with the callsite method name.
+ for (Wrapper<DexMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+ InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
+ if (groupState.callSiteCollidingMethods.isEmpty()) {
+ continue;
+ }
+ DexMethod key = interfaceMethodGroup.get();
+ MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.holder);
+ DexString existingRenaming = keyNamingState.newOrReservedNameFor(key);
+ assert existingRenaming != null;
+ for (DexMethod collidingMethod : groupState.callSiteCollidingMethods) {
+ DexString newNameInGroup = newNameInGroup(collidingMethod, keyNamingState, groupState);
+ minifierState.putRenaming(collidingMethod, newNameInGroup);
+ MethodNamingState<?> methodNamingState =
+ minifierState.getNamingState(collidingMethod.holder);
+ methodNamingState.addRenaming(newNameInGroup, collidingMethod);
+ keyNamingState.addRenaming(newNameInGroup, collidingMethod);
+ }
+ }
timing.end();
timing.end(); // end compute timing
@@ -568,6 +623,12 @@
return newName;
}
+ private DexString newNameInGroup(
+ DexMethod method, MethodNamingState<?> namingState, InterfaceMethodGroupState groupState) {
+ // Check if the name is available in all states.
+ return namingState.nextName(method, (candidate, ignore) -> groupState.isAvailable(candidate));
+ }
+
private void patchUpChildrenInReservationStates() {
for (Map.Entry<DexType, InterfaceReservationState> entry : interfaceStateMap.entrySet()) {
for (DexType parent : entry.getValue().iface.interfaces.values) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 7926de0..85c2f0f 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -62,8 +62,12 @@
return candidate;
}
}
+ return nextName(method, isAvailable);
+ }
+
+ DexString nextName(DexMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
InternalNewNameState internalState = getOrCreateInternalState(method);
- newName = namingStrategy.next(method, internalState, isAvailable);
+ DexString newName = namingStrategy.next(method, internalState, isAvailable);
assert newName != null;
return newName;
}
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 1b76214..6d418cd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2058,6 +2058,7 @@
return;
}
+ // TODO(mkroghj): Remove pinnedItems check here.
if (instantiatedTypes.contains(clazz)
|| instantiatedInterfaceTypes.contains(clazz)
|| pinnedItems.contains(clazz.type)) {
@@ -2076,8 +2077,8 @@
if (currentClass == null || currentClass.lookupVirtualMethod(possibleTarget) != null) {
continue;
}
- // TODO(zerny): Why does not not confer with lambdas and pinned too?
- if (instantiatedTypes.contains(currentClass)) {
+ if (instantiatedTypes.contains(currentClass)
+ || instantiatedInterfaceTypes.contains(currentClass)) {
markVirtualMethodAsLive(
clazz,
encodedPossibleTarget,
diff --git a/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java b/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
new file mode 100644
index 0000000..9a55cee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
@@ -0,0 +1,84 @@
+// 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.naming;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 IntersectionLambdaTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar", "K.baz"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IntersectionLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(IntersectionLambdaTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(IntersectionLambdaTest.class)
+ .enableMergeAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ public interface I {
+ void foo();
+ }
+
+ public interface J {
+ default void bar() {
+ System.out.println("J.bar");
+ }
+ }
+
+ public interface K {
+ default void baz() {
+ System.out.println("K.baz");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callI((I & J & K) () -> System.out.println("Lambda.foo"));
+ }
+
+ private static void callI(I i) {
+ i.foo();
+ ((J) i).bar();
+ ((K) i).baz();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IntersectionWithInheritanceLambdaTest.java b/src/test/java/com/android/tools/r8/naming/IntersectionWithInheritanceLambdaTest.java
new file mode 100644
index 0000000..f784a8f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IntersectionWithInheritanceLambdaTest.java
@@ -0,0 +1,78 @@
+// 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.naming;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 IntersectionWithInheritanceLambdaTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar", "K.baz"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IntersectionWithInheritanceLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(IntersectionWithInheritanceLambdaTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(IntersectionWithInheritanceLambdaTest.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public interface J {
+ default void bar() {
+ System.out.println("J.bar");
+ }
+ }
+
+ public interface K extends J {
+ void foo();
+
+ default void baz() {
+ System.out.println("K.baz");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callFooBarBaz(() -> System.out.println("Lambda.foo"));
+ }
+
+ private static void callFooBarBaz(K k) {
+ k.foo();
+ k.bar();
+ k.baz();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
new file mode 100644
index 0000000..94fc918
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -0,0 +1,108 @@
+// 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.resolution.interfacetargets;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a reproduction of b/148168065 */
+@RunWith(Parameterized.class)
+public class DefaultMethodAsOverrideWithLambdaTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"Lambda.foo", "J.bar"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultMethodAsOverrideWithLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @FunctionalInterface
+ @NeverMerge
+ public interface I {
+ void foo();
+
+ default void bar() {
+ System.out.println("I.bar");
+ }
+ }
+
+ @NeverMerge
+ public interface J extends I {
+
+ @Override
+ default void bar() {
+ System.out.println("J.bar");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements I {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ System.out.println("A.foo");
+ }
+
+ @Override
+ public void bar() {
+ System.out.println("A.bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ callI((J) () -> System.out.println("Lambda.foo"));
+ } else {
+ callI(new A());
+ }
+ }
+
+ private static void callI(I i) {
+ i.foo();
+ i.bar();
+ }
+ }
+}