Fix incorrect visibility check in inliner
Bug: 142239408
Change-Id: I33f7f73b13810e452484e5a40d549621f55166af
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index e2ad67e..773f4ad 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
@@ -45,6 +46,10 @@
copyMetadataFromPrevious(previous);
}
+ protected InternalOptions options() {
+ return app.options;
+ }
+
public void copyMetadataFromPrevious(AppInfo previous) {
this.synthesizedClasses.putAll(previous.synthesizedClasses);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index ec90464..460885c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -325,7 +325,6 @@
assert checkIfObsolete();
if (!isSubtype(invocationContext, method.holder)) {
DexClass contextClass = definitionFor(invocationContext);
- isSubtype(invocationContext, method.holder);
throw new CompilationError(
"Illegal invoke-super to " + method.toSourceString() + " from class " + invocationContext,
contextClass != null ? contextClass.getOrigin() : Origin.unknown());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 87350e7..263da6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -264,14 +264,6 @@
return false;
}
- DexClass holder = appView.definitionFor(singleTarget.method.holder);
- if (holder.isInterface()) {
- // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
- // runtime.
- whyAreYouNotInliningReporter.reportIncompatibleClassChangeError();
- return false;
- }
-
// Don't inline if target is synchronized.
if (singleTarget.accessFlags.isSynchronized()) {
whyAreYouNotInliningReporter.reportSynchronizedMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 818b8c2..b5c96d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -43,9 +43,6 @@
public void reportInaccessible() {}
@Override
- public void reportIncompatibleClassChangeError() {}
-
- @Override
public void reportIncorrectArity(int numberOfArguments, int arity) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index c560944..1e869eb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -60,8 +60,6 @@
public abstract void reportInaccessible();
- public abstract void reportIncompatibleClassChangeError();
-
public abstract void reportIncorrectArity(int numberOfArguments, int arity);
public abstract void reportInlineeDoesNotHaveCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 6bc09fd..dcc122b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -88,11 +88,6 @@
}
@Override
- public void reportIncompatibleClassChangeError() {
- print("invoke may fail with an IncompatibleClassChangeError.");
- }
-
- @Override
public void reportIncorrectArity(int numberOfArguments, int arity) {
print(
"number of arguments ("
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 4762db0..e3cce51 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -880,6 +880,31 @@
}
}
+ private DexEncodedMethod validateSingleVirtualTarget(
+ DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
+ assert resolutionResult.isValidVirtualTarget(options());
+
+ if (singleTarget == null) {
+ return null;
+ }
+
+ // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
+ // at runtime.
+ if (isInvalidSingleVirtualTarget(singleTarget, resolutionResult)) {
+ return null;
+ }
+
+ return singleTarget;
+ }
+
+ private boolean isInvalidSingleVirtualTarget(
+ DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
+ assert resolutionResult.isValidVirtualTarget(options());
+ // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
+ // at runtime.
+ return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags);
+ }
+
/** For mapping invoke virtual instruction to single target method. */
public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method, DexType invocationContext) {
assert checkIfObsolete();
@@ -903,11 +928,13 @@
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
- if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ if (resolutionResult.hasSingleTarget()
+ && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
if (refinedResolutionResult.hasSingleTarget()
&& refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
- return refinedResolutionResult.asSingleTarget();
+ return validateSingleVirtualTarget(
+ refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget());
}
}
return null;
@@ -942,27 +969,28 @@
// First get the target for the holder type.
ResolutionResult topMethod = resolveMethodOnClass(holder, method);
// We might hit none or multiple targets. Both make this fail at runtime.
- if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(app().options)) {
+ if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(options())) {
method.setSingleVirtualMethodCache(refinedReceiverType, null);
return null;
}
// Now, resolve the target with the refined receiver type.
- if (refinedReceiverIsStrictSubType) {
- topMethod = resolveMethodOnClass(refinedHolder, method);
- }
- DexEncodedMethod topSingleTarget = topMethod.asSingleTarget();
+ ResolutionResult refinedResolutionResult =
+ refinedReceiverIsStrictSubType ? resolveMethodOnClass(refinedHolder, method) : topMethod;
+ DexEncodedMethod topSingleTarget = refinedResolutionResult.asSingleTarget();
DexClass topHolder = definitionFor(topSingleTarget.method.holder);
// We need to know whether the top method is from an interface, as that would allow it to be
// shadowed by a default method from an interface further down.
boolean topIsFromInterface = topHolder.isInterface();
// Now look at all subtypes and search for overrides.
DexEncodedMethod result =
- findSingleTargetFromSubtypes(
- refinedReceiverType,
- method,
- topSingleTarget,
- !refinedHolder.accessFlags.isAbstract(),
- topIsFromInterface);
+ validateSingleVirtualTarget(
+ findSingleTargetFromSubtypes(
+ refinedReceiverType,
+ method,
+ topSingleTarget,
+ !refinedHolder.accessFlags.isAbstract(),
+ topIsFromInterface),
+ topMethod.asSingleTarget());
// Map the failure case of SENTINEL to null.
result = result == DexEncodedMethod.SENTINEL ? null : result;
method.setSingleVirtualMethodCache(refinedReceiverType, result);
@@ -1099,11 +1127,13 @@
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
- if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ if (resolutionResult.hasSingleTarget()
+ && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
if (refinedResolutionResult.hasSingleTarget()
&& refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
- return refinedResolutionResult.asSingleTarget();
+ return validateSingleVirtualTarget(
+ refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget());
}
}
return null;
@@ -1119,11 +1149,8 @@
}
// First check that there is a target for this invoke-interface to hit. If there is none,
// this will fail at runtime.
- ResolutionResult topTarget = resolveMethodOnInterface(holder, method);
- if (!topTarget.isValidVirtualTarget(app().options)) {
- return null;
- }
- if (topTarget.asResultOfResolve() == null) {
+ DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).asSingleTarget();
+ if (topTarget == null || !topTarget.isValidVirtualTarget(options())) {
return null;
}
// For kept types we cannot ensure a single target.
@@ -1152,19 +1179,18 @@
// override them, so we ignore interface methods here. Otherwise, we would look up
// default methods that are factually never used.
} else if (!clazz.accessFlags.isAbstract()) {
- ResolutionResult resolutionResult = resolveMethodOnClass(clazz, method);
- if (resolutionResult.hasSingleTarget()) {
- if ((result != null) && (result != resolutionResult.asSingleTarget())) {
- return null;
- } else {
- result = resolutionResult.asSingleTarget();
- }
- } else {
+ DexEncodedMethod resolutionResult = resolveMethodOnClass(clazz, method).asSingleTarget();
+ if (resolutionResult == null || isInvalidSingleVirtualTarget(resolutionResult, topTarget)) {
// This will fail at runtime.
return null;
}
+ if (result != null && result != resolutionResult) {
+ return null;
+ }
+ result = resolutionResult;
}
}
+ assert result == null || !isInvalidSingleVirtualTarget(result, topTarget);
return result == null || !result.isVirtualMethod() ? null : result;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
index 26a4f2f..df9ab8d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
@@ -4,53 +4,58 @@
package com.android.tools.r8.ir.optimize.inliner.interfacemethods;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
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.utils.AndroidApiLevel;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
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 InlineDefaultInterfaceMethodTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InlineDefaultInterfaceMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("Hello world!");
CodeInspector inspector =
- testForR8(Backend.DEX)
+ testForR8(parameters.getBackend())
.addInnerClasses(InlineDefaultInterfaceMethodTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(AndroidApiLevel.M)
+ .setMinApi(parameters.getApiLevel())
.enableClassInliningAnnotations()
.enableMergeAnnotations()
.noMinification()
- .run(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
- // TODO(b/124017330): interface methods should have been inlined into C.method().
- ClassSubject classSubject =
- inspector.clazz(I.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
- assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "hello"), isPresent());
- assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "space"), isPresent());
- assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "world"), isPresent());
+ // After inlining, only one class remains, namely TestClass.
+ assertEquals(1, inspector.allClasses().size());
}
static class TestClass {
public static void main(String[] args) {
- C obj = new C();
- obj.method();
+ new C().method();
}
}
@@ -73,7 +78,6 @@
@NeverClassInline
static class C implements I {
- @NeverInline
public void method() {
// invoke-virtual
hello();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
index 9eb57fb..49dee4b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
@@ -4,37 +4,47 @@
package com.android.tools.r8.ir.optimize.inliner.interfacemethods;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
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 InlineStaticInterfaceMethodTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InlineStaticInterfaceMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("Hello world!");
CodeInspector inspector =
- testForR8(Backend.DEX)
+ testForR8(parameters.getBackend())
.addInnerClasses(InlineStaticInterfaceMethodTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(AndroidApiLevel.M)
- .run(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
- // TODO(b/124017330): greet() should have been inlined into main().
- ClassSubject classSubject =
- inspector.clazz(I.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
- assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithName("greet"), isPresent());
+ // After inlining of I.greet(), only one class remains, namely TestClass.
+ assertEquals(1, inspector.allClasses().size());
}
static class TestClass {