Fix illegal inlining of non-forwarding constructor call to constructor in same class
Bug: 176766039
Change-Id: I6383d317522e33393372707643850fd7c21708ff
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 4534c9b..79f36ad 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
@@ -439,7 +439,10 @@
@Override
public boolean canInlineInstanceInitializer(
- IRCode inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ IRCode code,
+ IRCode inlinee,
+ InvokeDirect invoke,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
// In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
// Newly Created Objects" it says:
//
@@ -451,9 +454,12 @@
// is expected to adhere to the VM specification.
DexType callerMethodHolder = method.getHolderType();
DexType calleeMethodHolder = inlinee.method().getHolderType();
- // Calling a constructor on the same class from a constructor can always be inlined.
+
+ // Forwarding constructor calls that target a constructor in the same class can always be
+ // inlined.
if (method.getDefinition().isInstanceInitializer()
- && callerMethodHolder == calleeMethodHolder) {
+ && callerMethodHolder == calleeMethodHolder
+ && invoke.getReceiver() == code.getThis()) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index eea6b7c..c25755c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
@@ -107,7 +108,10 @@
@Override
public boolean canInlineInstanceInitializer(
- IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ IRCode code,
+ IRCode inlinee,
+ InvokeDirect invoke,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 96c9234..8f510fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -1049,7 +1049,7 @@
assert !singleTargetMethod.isClassInitializer();
if (singleTargetMethod.isInstanceInitializer()
&& !strategy.canInlineInstanceInitializer(
- inlinee.code, whyAreYouNotInliningReporter)) {
+ code, inlinee.code, invoke.asInvokeDirect(), whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index cda0a6b..1dc076f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
@@ -22,7 +23,10 @@
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
boolean canInlineInstanceInitializer(
- IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
+ IRCode code,
+ IRCode inlinee,
+ InvokeDirect invoke,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
/** Return true if there is still budget for inlining into this method. */
boolean stillHasBudget(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
new file mode 100644
index 0000000..aad40d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 IllegalInliningOfMergedConstructorTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevels().build();
+ }
+
+ public IllegalInliningOfMergedConstructorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(Reprocess.class))
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertMergedInto(B.class, A.class))
+ .addOptionsModification(options -> options.inliningInstructionLimit = 4)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello w0rld!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A((System.currentTimeMillis() > 0 ? Reprocess.A : Reprocess.B)).foo();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ A(Reprocess r) {
+ new B().foo();
+
+ // This will force this constructor to be reprocessed, which will cause the inliner to attempt
+ // to inline B.<init>() into A.<init>(). Since B.<init>() has been moved to A as a result of
+ // horizontal class merging, the inliner will check if B.<init>() is eligible for inlining,
+ // which it is not in this case, due the assignment of $r8$classId.
+ System.out.print(r.ordinal());
+ }
+
+ @NeverInline
+ void foo() {
+ System.out.println("rld!");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ public B() {}
+
+ @NeverInline
+ void foo() {
+ System.out.print("Hello w");
+ }
+ }
+
+ enum Reprocess {
+ A,
+ B
+ }
+}