Insert casts needed for the program to type check after vertical class merging
Bug: 199561570
Change-Id: I1fb0e1fb49f0f1c840ac8da0c4c1d40ae2bd0f49
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 0225c1d..99b9cb7 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -561,6 +561,10 @@
}
}
+ public boolean hasVerticallyMergedClasses() {
+ return verticallyMergedClasses != null;
+ }
+
/**
* Get the result of vertical class merging. Returns null if vertical class merging has not been
* run.
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 82f09f8..450bc51 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -217,7 +217,7 @@
}
public int getNumberOfArguments() {
- return getReference().getArity() + BooleanUtils.intValue(isInstance());
+ return getReference().getNumberOfArguments(isStatic());
}
public CompilationState getCompilationState() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index c2eeffa..d7f282f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.structural.CompareToVisitor;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -69,6 +70,10 @@
return getParameter(argumentIndex - 1);
}
+ public int getNumberOfArguments(boolean isStatic) {
+ return getArity() + BooleanUtils.intValue(!isStatic);
+ }
+
public DexType getParameter(int index) {
return proto.getParameter(index);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 0332c6e..85ac375 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -221,6 +221,7 @@
newValue.addUser(this);
}
}
+ oldValue.removeUser(this);
}
public void replaceValue(int index, Value newValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 6c22403..92998f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -46,6 +47,29 @@
this.method = target;
}
+ public static InvokeMethod create(
+ Type type, DexMethod target, Value result, List<Value> arguments, boolean itf) {
+ switch (type) {
+ case DIRECT:
+ return new InvokeDirect(target, result, arguments, itf);
+ case INTERFACE:
+ return new InvokeInterface(target, result, arguments);
+ case STATIC:
+ return new InvokeStatic(target, result, arguments, itf);
+ case SUPER:
+ return new InvokeSuper(target, result, arguments, itf);
+ case VIRTUAL:
+ assert !itf;
+ return new InvokeVirtual(target, result, arguments);
+ case CUSTOM:
+ case MULTI_NEW_ARRAY:
+ case NEW_ARRAY:
+ case POLYMORPHIC:
+ default:
+ throw new Unreachable("Unexpected invoke type: " + type);
+ }
+ }
+
public abstract boolean getInterfaceBit();
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 303d31c..3ec06bd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -62,6 +62,7 @@
import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstClass;
@@ -97,12 +98,12 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
@@ -149,7 +150,9 @@
DexItemFactory factory = appView.dexItemFactory();
// Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
// we track all phi's that needs to be re-computed.
- ListIterator<BasicBlock> blocks = code.listIterator();
+ BasicBlockIterator blocks = code.listIterator();
+ InterfaceTypeToClassTypeLensCodeRewriterHelper interfaceTypeToClassTypeRewriterHelper =
+ InterfaceTypeToClassTypeLensCodeRewriterHelper.create(appView, code, methodProcessor);
boolean mayHaveUnreachableBlocks = false;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
@@ -370,15 +373,17 @@
boolean isInterface =
getBooleanOrElse(
appView.definitionFor(actualTarget.holder), DexClass::isInterface, false);
- Invoke newInvoke =
- Invoke.create(
- actualInvokeType,
- actualTarget,
- null,
- newOutValue,
- newInValues,
- isInterface);
+ InvokeMethod newInvoke =
+ InvokeMethod.create(
+ actualInvokeType, actualTarget, newOutValue, newInValues, isInterface);
+
iterator.replaceCurrentInstruction(newInvoke);
+
+ // Insert casts for the program to type check if interfaces has been vertically
+ // merged into their unique (non-interface) subclass. See also b/199561570.
+ interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+ invoke, newInvoke, lensLookup, blocks, block, iterator);
+
if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
@@ -698,6 +703,10 @@
if (!affectedPhis.isEmpty()) {
new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
}
+
+ // Finalize cast insertion.
+ interfaceTypeToClassTypeRewriterHelper.processWorklist();
+
assert code.isConsistentSSABeforeTypesAreCorrect();
assert code.hasNoMergedClasses(appView);
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper.java b/src/main/java/com/android/tools/r8/verticalclassmerging/EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper.java
new file mode 100644
index 0000000..0d4cb33
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper.java
@@ -0,0 +1,31 @@
+// 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.verticalclassmerging;
+
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+
+public class EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper
+ extends InterfaceTypeToClassTypeLensCodeRewriterHelper {
+
+ @Override
+ public void insertCastsForOperandsIfNeeded(
+ InvokeMethod originalInvoke,
+ InvokeMethod rewrittenInvoke,
+ MethodLookupResult lookupResult,
+ BasicBlockIterator blockIterator,
+ BasicBlock block,
+ InstructionListIterator instructionIterator) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void processWorklist() {
+ // Intentionally empty.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
new file mode 100644
index 0000000..6712600
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
@@ -0,0 +1,57 @@
+// 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.verticalclassmerging;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+
+/**
+ * Inserts check-cast instructions after vertical class merging when this is needed for the program
+ * to type check.
+ *
+ * <p>Any class type is assignable to any interface type. If an interface I is merged into its
+ * unique (non-interface) subtype C, then assignments that used to be valid may no longer be valid
+ * due to the stronger type checking imposed by the JVM. Therefore, casts are inserted where
+ * necessary for the program to type check after vertical class merging.
+ *
+ * <p>Example: If the interface I is merged into its unique subclass C, then the invoke-interface
+ * instruction will be rewritten by the {@link com.android.tools.r8.ir.conversion.LensCodeRewriter}
+ * to an invoke-virtual instruction. After this rewriting, the program no longer type checks, and
+ * therefore a cast is inserted before the invoke-virtual instruction: {@code C c = (C) o}.
+ *
+ * <pre>
+ * Object o = get();
+ * o.m(); // invoke-interface {o}, void I.m()
+ * </pre>
+ */
+public abstract class InterfaceTypeToClassTypeLensCodeRewriterHelper {
+
+ public static InterfaceTypeToClassTypeLensCodeRewriterHelper create(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCode code,
+ MethodProcessor methodProcessor) {
+ if (methodProcessor.isPrimaryMethodProcessor() && appView.hasVerticallyMergedClasses()) {
+ return new InterfaceTypeToClassTypeLensCodeRewriterHelperImpl(appView, code);
+ }
+ return new EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper();
+ }
+
+ public abstract void insertCastsForOperandsIfNeeded(
+ InvokeMethod originalInvoke,
+ InvokeMethod rewrittenInvoke,
+ MethodLookupResult lookupResult,
+ BasicBlockIterator blockIterator,
+ BasicBlock block,
+ InstructionListIterator instructionIterator);
+
+ public abstract void processWorklist();
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelperImpl.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelperImpl.java
new file mode 100644
index 0000000..a376d91
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelperImpl.java
@@ -0,0 +1,204 @@
+// 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.verticalclassmerging;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+// TODO(b/199561570): Extend this to insert casts for users that are not an instance of
+// invoke-method (e.g., array-put, instance-put, static-put, return).
+public class InterfaceTypeToClassTypeLensCodeRewriterHelperImpl
+ extends InterfaceTypeToClassTypeLensCodeRewriterHelper {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final IRCode code;
+
+ private final Map<Instruction, Deque<WorklistItem>> worklist = new IdentityHashMap<>();
+
+ public InterfaceTypeToClassTypeLensCodeRewriterHelperImpl(
+ AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code) {
+ this.appView = appView;
+ this.code = code;
+ }
+
+ @Override
+ public void insertCastsForOperandsIfNeeded(
+ InvokeMethod originalInvoke,
+ InvokeMethod rewrittenInvoke,
+ MethodLookupResult lookupResult,
+ BasicBlockIterator blockIterator,
+ BasicBlock block,
+ InstructionListIterator instructionIterator) {
+ DexMethod originalInvokedMethod = originalInvoke.getInvokedMethod();
+ DexMethod rewrittenInvokedMethod = rewrittenInvoke.getInvokedMethod();
+ if (lookupResult.getPrototypeChanges().getArgumentInfoCollection().hasRemovedArguments()) {
+ // There is no argument removal before the primary optimization pass.
+ assert false;
+ return;
+ }
+
+ // Intentionally iterate the arguments of the original invoke, since the rewritten invoke could
+ // have extra arguments added.
+ for (int operandIndex = 0; operandIndex < originalInvoke.arguments().size(); operandIndex++) {
+ Value operand = rewrittenInvoke.getArgument(operandIndex);
+ DexType originalType =
+ originalInvokedMethod.getArgumentType(operandIndex, originalInvoke.isInvokeStatic());
+ DexType rewrittenType =
+ rewrittenInvokedMethod.getArgumentType(operandIndex, rewrittenInvoke.isInvokeStatic());
+ if (needsCastForOperand(operand, block, originalType, rewrittenType).isPossiblyTrue()) {
+ worklist
+ .computeIfAbsent(rewrittenInvoke, ignoreKey(ArrayDeque::new))
+ .addLast(new WorklistItem(operandIndex, originalType, rewrittenType));
+ }
+ }
+ }
+
+ @Override
+ public void processWorklist() {
+ if (worklist.isEmpty()) {
+ return;
+ }
+
+ BasicBlockIterator blockIterator = code.listIterator();
+ boolean isCodeFullyRewrittenWithLens = true;
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ Deque<WorklistItem> worklistItems = worklist.get(instruction);
+ if (worklistItems == null) {
+ continue;
+ }
+ for (WorklistItem worklistItem : worklistItems) {
+ Value operand = instruction.getOperand(worklistItem.operandIndex);
+ DexType originalType = worklistItem.originalType;
+ DexType rewrittenType = worklistItem.rewrittenType;
+ OptionalBool needsCastForOperand =
+ needsCastForOperand(
+ operand, block, originalType, rewrittenType, isCodeFullyRewrittenWithLens);
+ assert !needsCastForOperand.isUnknown();
+ if (needsCastForOperand.isTrue()) {
+ insertCastForOperand(
+ operand, rewrittenType, instruction, blockIterator, block, instructionIterator);
+ }
+ }
+ }
+ }
+ }
+
+ private void insertCastForOperand(
+ Value operand,
+ DexType castType,
+ Instruction rewrittenUser,
+ BasicBlockIterator blockIterator,
+ BasicBlock block,
+ InstructionListIterator instructionIterator) {
+ Instruction previous = instructionIterator.previous();
+ assert previous == rewrittenUser;
+
+ CheckCast checkCast =
+ CheckCast.builder()
+ .setCastType(castType)
+ .setObject(operand)
+ .setFreshOutValue(code, castType.toTypeElement(appView), operand.getLocalInfo())
+ .setPosition(rewrittenUser)
+ .build();
+ if (block.hasCatchHandlers()) {
+ instructionIterator
+ .splitCopyCatchHandlers(code, blockIterator, appView.options())
+ .listIterator(code)
+ .add(checkCast);
+ } else {
+ instructionIterator.add(checkCast);
+ }
+ rewrittenUser.replaceValue(operand, checkCast.outValue());
+
+ Instruction next = instructionIterator.next();
+ assert next == rewrittenUser;
+ }
+
+ private boolean isOperandRewrittenWithLens(
+ Value operand, BasicBlock blockWithUser, boolean isCodeFullyRewrittenWithLens) {
+ if (isCodeFullyRewrittenWithLens) {
+ return true;
+ }
+ if (operand.isPhi()) {
+ return false;
+ }
+ Instruction definition = operand.getDefinition();
+ return definition.isArgument() || operand.getBlock() == blockWithUser;
+ }
+
+ private OptionalBool needsCastForOperand(
+ Value operand, BasicBlock blockWithUser, DexType originalType, DexType rewrittenType) {
+ return needsCastForOperand(operand, blockWithUser, originalType, rewrittenType, false);
+ }
+
+ private OptionalBool needsCastForOperand(
+ Value operand,
+ BasicBlock blockWithUser,
+ DexType originalType,
+ DexType rewrittenType,
+ boolean isCodeFullyRewrittenWithLens) {
+ if (!originalType.isClassType() || !rewrittenType.isClassType()) {
+ return OptionalBool.FALSE;
+ }
+ // The original type should be an interface type.
+ DexProgramClass originalClass = asProgramClassOrNull(appView.definitionFor(originalType));
+ if (originalClass == null || !originalClass.isInterface()) {
+ return OptionalBool.FALSE;
+ }
+ // The rewritten type should be a (non-interface) class type.
+ DexProgramClass rewrittenClass = asProgramClassOrNull(appView.definitionFor(rewrittenType));
+ if (rewrittenClass == null || rewrittenClass.isInterface()) {
+ return OptionalBool.FALSE;
+ }
+ // If the operand has not yet been rewritten with the lens, we delay the type check until
+ // after lens code rewriting.
+ if (!isOperandRewrittenWithLens(operand, blockWithUser, isCodeFullyRewrittenWithLens)) {
+ assert !isCodeFullyRewrittenWithLens;
+ return OptionalBool.UNKNOWN;
+ }
+ // The operand should not be subtype of the rewritten type.
+ TypeElement rewrittenTypeElement = rewrittenType.toTypeElement(appView);
+ return OptionalBool.of(
+ !operand.getType().lessThanOrEqualUpToNullability(rewrittenTypeElement, appView));
+ }
+
+ private static class WorklistItem {
+
+ final int operandIndex;
+ final DexType originalType;
+ final DexType rewrittenType;
+
+ WorklistItem(int operandIndex, DexType originalType, DexType rewrittenType) {
+ this.operandIndex = operandIndex;
+ this.originalType = originalType;
+ this.rewrittenType = rewrittenType;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
index 88e264b..301870a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
@@ -67,11 +67,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
- // TODO(b/199561570): Should succeed with 0.
- .applyIf(
- enableInlining || !enableVerticalClassMerging,
- runResult -> runResult.assertSuccessWithOutputLines("0"),
- runResult -> runResult.assertFailureWithErrorThatThrows(VerifyError.class));
+ .assertSuccessWithOutputLines("0");
}
private static byte[] getTransformedMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
new file mode 100644
index 0000000..0c9a135
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
@@ -0,0 +1,160 @@
+// 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.ir.optimize.inliner;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticInvokeWithMultipleObjectsForInterfaceTypesTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableInlining;
+
+ @Parameter(1)
+ public boolean enableVerticalClassMerging;
+
+ @Parameter(2)
+ public TestParameters parameters;
+
+ @Parameters(name = "{2}, inlining: {0}, vertical class merging: {1}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ assumeFalse(enableInlining);
+ assumeFalse(enableVerticalClassMerging);
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, J.class, A.class, B.class)
+ .addProgramClassFileData(getTransformedMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0", "0");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class, A.class, B.class)
+ .addProgramClassFileData(getTransformedMain())
+ .addKeepMainRule(Main.class)
+ // Keep getA() and getB() to prevent that we optimize it into having static return type A/B.
+ .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get?(...); }")
+ .addInliningAnnotations()
+ .addNoVerticalClassMergingAnnotations()
+ .applyIf(!enableInlining, R8TestBuilder::enableInliningAnnotations)
+ .applyIf(
+ !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0", "0");
+ }
+
+ private static byte[] getTransformedMain() throws IOException {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.startsWith("get")) {
+ visitor.visitMethodInsn(opcode, owner, name, "(I)Ljava/lang/Object;", isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .setReturnType(MethodPredicate.onName("getA"), Object.class.getTypeName())
+ .setReturnType(MethodPredicate.onName("getB"), Object.class.getTypeName())
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // Transformed from `I getA(int)` to `Object getA(int)` and `J getB(int)` to
+ // `Object getB(int)`.
+ test(getA(args.length), getB(args.length));
+ }
+
+ // @Keep
+ static /*Object*/ I getA(int f) {
+ return new A(f);
+ }
+
+ // @Keep
+ static /*Object*/ J getB(int f) {
+ return new B(f);
+ }
+
+ @NeverInline
+ static void test(I i, J j) {
+ i.m();
+ j.m();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ @NoVerticalClassMerging
+ interface I {
+
+ void m();
+ }
+
+ @NoHorizontalClassMerging
+ static class A implements I {
+
+ int f;
+
+ A(int f) {
+ this.f = f;
+ }
+
+ @Override
+ public void m() {
+ System.out.println(f);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ @NoVerticalClassMerging
+ interface J {
+
+ void m();
+ }
+
+ @NoHorizontalClassMerging
+ static class B implements J {
+
+ int f;
+
+ B(int f) {
+ this.f = f;
+ }
+
+ @Override
+ public void m() {
+ System.out.println(f);
+ }
+ }
+}