Merge "Handle non-standard invoke-super in class merging"
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
new file mode 100644
index 0000000..7f31098
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2018, 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.graph;
+
+public class DefaultUseRegistry extends UseRegistry {
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ return true;
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ return true;
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index c347dba..5da6f06 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DefaultUseRegistry;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
@@ -68,7 +69,7 @@
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
private final GraphLense graphLense;
- private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
+ private final Map<DexType, DexType> mergedClasses = new HashMap<>();
private final Timing timing;
private Collection<DexMethod> invokes;
@@ -107,6 +108,36 @@
}
}
}
+
+ // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
+ // following example. (Alternatively, it would be possible to merge A and B and rewrite the
+ // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This
+ // situation should generally not occur in practice, though.)
+ //
+ // class A {}
+ // class B extends A {
+ // public void m() {}
+ // }
+ // class C extends A {
+ // public void m() {
+ // invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B
+ // }
+ // }
+ if (!method.isStaticMethod()) {
+ method.registerCodeReferences(
+ new DefaultUseRegistry() {
+ @Override
+ public boolean registerInvokeSuper(DexMethod target) {
+ DexClass targetClass = appInfo.definitionFor(target.getHolder());
+ if (targetClass != null
+ && targetClass.isProgramClass()
+ && targetClass.lookupVirtualMethod(target) == null) {
+ pinnedTypes.add(target.getHolder());
+ }
+ return true;
+ }
+ });
+ }
}
}
return pinnedTypes;
@@ -401,8 +432,7 @@
// Record that invoke-super instructions in the target class should be redirected to the
// newly created direct method.
- deferredRenamings.mapVirtualMethodToDirectInType(
- virtualMethod.method, resultingDirectMethod.method, target.type);
+ redirectSuperCallsInTarget(virtualMethod.method, resultingDirectMethod.method);
if (shadowedBy == null) {
// In addition to the newly added direct method, create a virtual method such that we do
@@ -491,6 +521,36 @@
return deferredRenamings;
}
+ private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) {
+ // If we merge class B into class C, and class C contains an invocation super.m(), then it
+ // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
+ // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
+ // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
+ // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
+ //
+ // We handle this by adding a mapping for [target] and all of its supertypes.
+ DexClass holder = target;
+ while (holder != null && holder.isProgramClass()) {
+ DexMethod signatureInHolder =
+ application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
+ // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+ if (resolutionSucceeds(signatureInHolder)) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInHolder, newTarget, target.type);
+ holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
+ } else {
+ break;
+ }
+ }
+ }
+
+ private boolean resolutionSucceeds(DexMethod targetMethod) {
+ DexClass enclosingClass = appInfo.definitionFor(targetMethod.holder);
+ return enclosingClass != null
+ && (enclosingClass.lookupVirtualMethod(targetMethod) != null
+ || appInfo.lookupSuperTarget(targetMethod, enclosingClass.type) != null);
+ }
+
private DexEncodedMethod buildBridgeMethod(
DexEncodedMethod signature, DexMethod invocationTarget) {
DexType holder = target.type;