Exploit that type information is exact for newly-created instances
This CL improves the mechanism for finding single virtual targets, by exploiting the fact that values that have been defined directly by a new-instance instruction have exact type information.
Bug: 140234782, 140233505
Change-Id: I1613c8c9c8100d6d918dddc8cc8e5cf1ff7ab74f
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 6399a1c..c2fdaaf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -40,16 +40,16 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultCallSiteOptimizationInfo;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.NestUtils;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultCallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.CfEmulateInterfaceSyntheticSourceCodeProvider;
@@ -1281,6 +1281,11 @@
}
@Override
+ public boolean isValidVirtualTargetForDynamicDispatch() {
+ return isVirtualMethod();
+ }
+
+ @Override
public DexEncodedMethod asResultOfResolve() {
checkIfObsolete();
return this;
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 83a4132..253d014 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -26,6 +26,8 @@
boolean isValidVirtualTarget(InternalOptions options);
+ boolean isValidVirtualTargetForDynamicDispatch();
+
default Set<DexEncodedMethod> lookupVirtualDispatchTargets(
boolean isInterface, AppInfoWithSubtyping appInfo) {
return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
@@ -141,6 +143,16 @@
}
@Override
+ public boolean isValidVirtualTargetForDynamicDispatch() {
+ for (DexEncodedMethod method : methods) {
+ if (!method.isVirtualMethod()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
public DexEncodedMethod asResultOfResolve() {
// Resolution may return any of the targets that were found.
return methods.get(0);
@@ -218,6 +230,11 @@
public boolean isValidVirtualTarget(InternalOptions options) {
return true;
}
+
+ @Override
+ public boolean isValidVirtualTargetForDynamicDispatch() {
+ return true;
+ }
}
abstract class FailedResolutionResult extends EmptyResult {
@@ -226,6 +243,11 @@
public boolean isValidVirtualTarget(InternalOptions options) {
return false;
}
+
+ @Override
+ public boolean isValidVirtualTargetForDynamicDispatch() {
+ return false;
+ }
}
class ClassNotFoundResult extends FailedResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 45716d0..dc74f64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -90,11 +90,12 @@
@Override
public DexEncodedMethod lookupSingleTarget(
AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
- DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
- DexMethod method = getInvokedMethod();
- return appView
- .appInfo()
- .lookupSingleInterfaceTarget(method, invocationContext, refinedReceiverType);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ return appInfo.lookupSingleInterfaceTarget(
+ getInvokedMethod(),
+ invocationContext,
+ TypeAnalysis.getRefinedReceiverType(appView, this),
+ getReceiver().getExactDynamicType());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 2e94e3f..9f5dbe6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -6,7 +6,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -59,8 +61,20 @@
public boolean verifyTypes(AppView<?> appView) {
assert super.verifyTypes(appView);
- TypeLatticeElement receiverType = getReceiver().getTypeLattice();
+ Value receiver = getReceiver();
+ TypeLatticeElement receiverType = receiver.getTypeLattice();
assert receiverType.isPreciseType();
+
+ if (appView.appInfo().hasSubtyping()) {
+ DexType exactReceiverType = receiver.getExactDynamicType();
+ if (exactReceiverType != null) {
+ DexType refinedReceiverType =
+ TypeAnalysis.getRefinedReceiverType(appView.withSubtyping(), this);
+ assert exactReceiverType == refinedReceiverType
+ || appView.appInfo().withSubtyping().isMissingOrHasMissingSuperType(exactReceiverType);
+ }
+ }
+
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 8a9caba..1d1b2a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -93,11 +93,12 @@
@Override
public DexEncodedMethod lookupSingleTarget(
AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
- DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
- DexMethod method = getInvokedMethod();
- return appView
- .appInfo()
- .lookupSingleVirtualTarget(method, invocationContext, refinedReceiverType);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ return appInfo.lookupSingleVirtualTarget(
+ getInvokedMethod(),
+ invocationContext,
+ TypeAnalysis.getRefinedReceiverType(appView, this),
+ getReceiver().getExactDynamicType());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 46e8ef7..7eb14d7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1164,4 +1164,17 @@
public TypeLatticeElement getTypeLattice() {
return typeLattice;
}
+
+ public DexType getExactDynamicType() {
+ Value root = getAliasedValue();
+ if (root.isPhi()) {
+ return null;
+ }
+ Instruction definition = root.definition;
+ if (definition.isNewInstance()) {
+ NewInstance newInstance = definition.asNewInstance();
+ return newInstance.clazz;
+ }
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9841fff..dddeb6c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -849,16 +849,40 @@
/** For mapping invoke virtual instruction to single target method. */
public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method, DexType invocationContext) {
assert checkIfObsolete();
- return lookupSingleVirtualTarget(method, invocationContext, method.holder);
+ return lookupSingleVirtualTarget(method, invocationContext, method.holder, null);
}
public DexEncodedMethod lookupSingleVirtualTarget(
- DexMethod method, DexType invocationContext, DexType refinedReceiverType) {
+ DexMethod method,
+ DexType invocationContext,
+ DexType refinedReceiverType,
+ DexType receiverLowerBoundType) {
assert checkIfObsolete();
DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
if (directResult != null) {
return directResult;
}
+
+ // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
+ // runtime type information. In this case, the invoke will dispatch to the resolution result
+ // from the runtime type of the receiver.
+ if (receiverLowerBoundType != null) {
+ if (receiverLowerBoundType == refinedReceiverType) {
+ ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
+ if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
+ if (refinedResolutionResult.hasSingleTarget()
+ && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ return refinedResolutionResult.asSingleTarget();
+ }
+ }
+ return null;
+ } else {
+ // We should never hit the case at the moment, but if we start tracking more precise lower-
+ // bound type information, we should handle this case as well.
+ }
+ }
+
// This implements the logic from
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
assert method != null;
@@ -1018,11 +1042,14 @@
public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method, DexType invocationContext) {
assert checkIfObsolete();
- return lookupSingleInterfaceTarget(method, invocationContext, method.holder);
+ return lookupSingleInterfaceTarget(method, invocationContext, method.holder, null);
}
public DexEncodedMethod lookupSingleInterfaceTarget(
- DexMethod method, DexType invocationContext, DexType refinedReceiverType) {
+ DexMethod method,
+ DexType invocationContext,
+ DexType refinedReceiverType,
+ DexType receiverLowerBoundType) {
assert checkIfObsolete();
DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
if (directResult != null) {
@@ -1031,6 +1058,27 @@
if (instantiatedLambdas.contains(method.holder)) {
return null;
}
+
+ // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
+ // runtime type information. In this case, the invoke will dispatch to the resolution result
+ // from the runtime type of the receiver.
+ if (receiverLowerBoundType != null) {
+ if (receiverLowerBoundType == refinedReceiverType) {
+ ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
+ if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
+ if (refinedResolutionResult.hasSingleTarget()
+ && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ return refinedResolutionResult.asSingleTarget();
+ }
+ }
+ return null;
+ } else {
+ // We should never hit the case at the moment, but if we start tracking more precise lower-
+ // bound type information, we should handle this case as well.
+ }
+ }
+
DexClass holder = definitionFor(method.holder);
if ((holder == null) || holder.isNotProgramClass() || !holder.accessFlags.isInterface()) {
return null;
diff --git a/src/test/examples/memberrebinding/Memberrebinding.java b/src/test/examples/memberrebinding/Memberrebinding.java
index 74c99fd..1e782ae 100644
--- a/src/test/examples/memberrebinding/Memberrebinding.java
+++ b/src/test/examples/memberrebinding/Memberrebinding.java
@@ -29,9 +29,15 @@
System.out.println(classExtendsOther.aMethodThatReturnsTwo());
System.out.println(classExtendsOther.aMethodThatReturnsThree());
System.out.println(classExtendsOther.aMethodThatReturnsFour());
- AnIndependentInterface iface = classExtendsOther;
+ AnIndependentInterface iface =
+ System.currentTimeMillis() >= 0
+ ? new ClassExtendsOtherLibraryClass()
+ : new OtherClassExtendsOtherLibraryClass();
System.out.println(iface.aMethodThatReturnsTwo());
- SuperClassOfClassExtendsOtherLibraryClass superClass = classExtendsOther;
+ SuperClassOfClassExtendsOtherLibraryClass superClass =
+ System.currentTimeMillis() >= 0
+ ? new ClassExtendsOtherLibraryClass()
+ : new OtherClassExtendsOtherLibraryClass();
System.out.println(superClass.aMethodThatReturnsTrue());
System.out.println(superClass.aMethodThatReturnsFalse());
}
diff --git a/src/test/examples/memberrebinding/OtherClassExtendsOtherLibraryClass.java b/src/test/examples/memberrebinding/OtherClassExtendsOtherLibraryClass.java
new file mode 100644
index 0000000..e88af28
--- /dev/null
+++ b/src/test/examples/memberrebinding/OtherClassExtendsOtherLibraryClass.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, 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 memberrebinding;
+
+import memberrebindinglib.AnIndependentInterface;
+
+public class OtherClassExtendsOtherLibraryClass extends SuperClassOfClassExtendsOtherLibraryClass
+ implements AnIndependentInterface {
+
+ @Override
+ public boolean aMethodThatReturnsTrue() {
+ return true;
+ }
+
+ @Override
+ public boolean aMethodThatReturnsFalse() {
+ return false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
index e832c3f..36ea940 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
@@ -83,6 +83,11 @@
.addInnerClasses(CustomCollectionTest.class)
.setMinApi(parameters.getApiLevel())
.addKeepClassAndMembersRules(Executor.class)
+ .addOptionsModification(
+ options -> {
+ // TODO(b/140233505): Allow devirtualization once fixed.
+ options.enableDevirtualization = false;
+ })
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(inspector -> this.assertCustomCollectionCallsCorrect(inspector, true))
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java
index 5f27e8d..f9778df 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java
@@ -103,6 +103,11 @@
.addKeepMainRule(TEST_CLASS)
.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
.setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> {
+ // TODO(b/140233505): Allow devirtualization once fixed.
+ options.enableDevirtualization = false;
+ })
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::checkRewrittenInvokes)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 695cf0a..a75cc4c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.devirtualize;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -18,7 +19,6 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -60,17 +60,15 @@
.inspector();
ClassSubject clazz = inspector.clazz(Main.class);
- MethodSubject m = clazz.method(CodeInspector.MAIN);
- long numOfInvokeInterface =
- Streams.stream(m.iterateInstructions(InstructionSubject::isInvokeInterface)).count();
- // List#add, List#get
- assertEquals(2, numOfInvokeInterface);
+ MethodSubject m = clazz.mainMethod();
+ // List#add and List#get get devirtualized into ArrayList#add and ArrayList#get.
+ assertTrue(m.streamInstructions().noneMatch(InstructionSubject::isInvokeInterface));
+ // System.out.println, List#add ~> ArrayList#add, List#get ~> ArrayList#get, I#get ~> A0#get.
long numOfInvokeVirtual =
- Streams.stream(m.iterateInstructions(InstructionSubject::isInvokeVirtual)).count();
- // System.out.println, I#get ~> A0#get
- assertEquals(2, numOfInvokeVirtual);
- long numOfCast = Streams.stream(m.iterateInstructions(InstructionSubject::isCheckCast)).count();
- // check-cast I ~> check-cast A0
+ m.streamInstructions().filter(InstructionSubject::isInvokeVirtual).count();
+ assertEquals(4, numOfInvokeVirtual);
+ // check-cast I ~> check-cast A0.
+ long numOfCast = m.streamInstructions().filter(InstructionSubject::isCheckCast).count();
assertEquals(1, numOfCast);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
index d26719b..67d312f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
@@ -10,7 +10,7 @@
private static final int COUNT = 8;
public static void main(String[] args) {
- I a0 = new A0();
+ I a0 = System.currentTimeMillis() >= 0 ? new A0() : new A1();
List<I> l = new ArrayList<>();
for (int i = 0; i < COUNT; i++) {
l.add(a0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java
new file mode 100644
index 0000000..ced497f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2019, 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.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+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.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.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 SingleTargetFromExactReceiverTypeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public SingleTargetFromExactReceiverTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SingleTargetFromExactReceiverTypeTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-keepclassmembers class " + A.class.getTypeName() + " {",
+ " void cannotBeInlinedDueToKeepRule();",
+ "}")
+ .enableClassInliningAnnotations()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::verifyOnlyCanBeInlinedHasBeenInlined)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(
+ "A.canBeInlined()",
+ "B.canBeInlined()",
+ "A.cannotBeInlinedDueToDynamicDispatch()",
+ "A.cannotBeInlinedDueToKeepRule()");
+ }
+
+ private void verifyOnlyCanBeInlinedHasBeenInlined(CodeInspector inspector) {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.uniqueMethodWithName("canBeInlined"), not(isPresent()));
+ assertThat(
+ aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToDynamicDispatch"), isPresent());
+ assertThat(aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToKeepRule"), isPresent());
+
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, isPresent());
+ assertThat(bClassSubject.uniqueMethodWithName("canBeInlined"), not(isPresent()));
+ assertThat(
+ bClassSubject.uniqueMethodWithName("cannotBeInlinedDueToDynamicDispatch"), isPresent());
+ assertThat(bClassSubject.uniqueMethodWithName("cannotBeInlinedDueToKeepRule"), isPresent());
+
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .anyMatch(x -> x.isConstString("A.canBeInlined()", JumboStringMode.ALLOW)));
+ assertThat(
+ mainMethodSubject,
+ invokesMethod(aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToDynamicDispatch")));
+ assertThat(
+ mainMethodSubject,
+ invokesMethod(aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToKeepRule")));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A().canBeInlined();
+ new B().canBeInlined();
+ (System.currentTimeMillis() >= 0 ? new A() : new B()).cannotBeInlinedDueToDynamicDispatch();
+ new A().cannotBeInlinedDueToKeepRule();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ public void canBeInlined() {
+ System.out.println("A.canBeInlined()");
+ }
+
+ public void cannotBeInlinedDueToDynamicDispatch() {
+ System.out.println("A.cannotBeInlinedDueToDynamicDispatch()");
+ }
+
+ public void cannotBeInlinedDueToKeepRule() {
+ System.out.println("A.cannotBeInlinedDueToKeepRule()");
+ }
+ }
+
+ static class B extends A {
+
+ @Override
+ public void canBeInlined() {
+ System.out.println("B.canBeInlined()");
+ }
+
+ @Override
+ public void cannotBeInlinedDueToDynamicDispatch() {
+ System.out.println("B.cannotBeInlinedDueToDynamicDispatch()");
+ }
+
+ @Override
+ public void cannotBeInlinedDueToKeepRule() {
+ System.out.println("B.cannotBeInlinedDueToKeepRule()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index a3cde5c..4996911 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -139,7 +139,9 @@
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
+ assertTrue(iterator.next().holder().is("java.lang.System"));
assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
+ assertTrue(iterator.next().holder().is("java.lang.System"));
assertTrue(
iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
assertTrue(
@@ -172,7 +174,10 @@
assertTrue(
iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
// Some dispatches on interfaces.
+ assertTrue(iterator.next().holder().is("java.lang.System"));
assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
+ // Some dispatches on classes.
+ assertTrue(iterator.next().holder().is("java.lang.System"));
assertTrue(iterator.next().holder().is("memberrebindinglib.SubClass"));
assertTrue(iterator.next().holder().is("memberrebindinglib.ImplementedInProgramClass"));
assertFalse(iterator.hasNext());
@@ -369,7 +374,7 @@
}
@Test
- public void memberRebindingTest() throws IOException, InterruptedException, ExecutionException {
+ public void memberRebindingTest() throws IOException, ExecutionException {
Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
Path out = Paths.get(temp.getRoot().getCanonicalPath());
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index b2e44c3..3cd46e12 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -85,14 +85,6 @@
assertFalse(method.isAbstract());
}
- private void defaultMethodAbstract(CodeInspector inspector) {
- ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
- assertTrue(clazz.isPresent());
- MethodSubject method = clazz.method("int", "method", ImmutableList.of());
- assertTrue(method.isPresent());
- assertTrue(method.isAbstract());
- }
-
@Test
public void test() throws Exception {
runTest(ImmutableList.of(), this::interfaceNotKept);
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/OtherClassImplementingInterface.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/OtherClassImplementingInterface.java
new file mode 100644
index 0000000..b6a39bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/OtherClassImplementingInterface.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2019, 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.shaking.defaultmethods;
+
+public class OtherClassImplementingInterface implements InterfaceWithDefaultMethods {
+ public int method() {
+ return 41;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java
index 5e46681..be7b509 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java
@@ -7,7 +7,10 @@
public class TestClass {
public void useInterfaceMethod() {
- InterfaceWithDefaultMethods iface = new ClassImplementingInterface();
+ InterfaceWithDefaultMethods iface =
+ System.currentTimeMillis() >= 0
+ ? new ClassImplementingInterface()
+ : new OtherClassImplementingInterface();
System.out.println(iface.method());
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/OtherClassImplementingInterface.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/OtherClassImplementingInterface.java
new file mode 100644
index 0000000..0c3f8f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/OtherClassImplementingInterface.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, 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.shaking.forceproguardcompatibility.defaultmethods;
+
+public class OtherClassImplementingInterface implements InterfaceWithDefaultMethods {
+ public int method() {
+ return 41;
+ }
+
+ public void method2(String x, int y) {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java
index 35ba3c5..c661852 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java
@@ -7,12 +7,18 @@
public class TestClass {
public void useInterfaceMethod() {
- InterfaceWithDefaultMethods iface = new ClassImplementingInterface();
+ InterfaceWithDefaultMethods iface =
+ System.currentTimeMillis() >= 0
+ ? new ClassImplementingInterface()
+ : new OtherClassImplementingInterface();
System.out.println(iface.method());
}
public void useInterfaceMethod2() {
- InterfaceWithDefaultMethods iface = new ClassImplementingInterface();
+ InterfaceWithDefaultMethods iface =
+ System.currentTimeMillis() >= 0
+ ? new ClassImplementingInterface()
+ : new OtherClassImplementingInterface();
iface.method2("a", 1);
}