Test outlining with default interface method desugaring
Change-Id: Ifac852b67944ffdf667abd1d95165940198bd4b0
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 6e6cdf0..c9e7f2d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1011,19 +1011,28 @@
return builder.build();
}
- public static void setDebugInfoWithFakeThisParameter(Code code, int arity, AppView<?> appView) {
+ public static Code mutateOrCreateCodeWithFakeThisParameter(
+ Code code, int arity, AppView<?> appView) {
if (code.isDexCode()) {
DexCode dexCode = code.asDexCode();
DexDebugInfo newDebugInfo = dexCode.debugInfoWithFakeThisParameter(appView.dexItemFactory());
assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount());
dexCode.setDebugInfo(newDebugInfo);
+ return dexCode;
} else if (code.isCfCode()) {
CfCode cfCode = code.asCfCode();
cfCode.addFakeThisParameter(appView.dexItemFactory());
+ return cfCode;
} else if (code.isLirCode()) {
- assert appView.options().isRelease();
- assert code.asLirCode().getDebugLocalInfoTable() == null;
+ // TODO(b/443663978): Add support for patching up LIR debug info.
+ assert appView.options().isRelease()
+ || appView.options().getThrowBlockOutlinerOptions().forceDebug;
+ assert code.asLirCode().getDebugLocalInfoTable() == null
+ || appView.options().getThrowBlockOutlinerOptions().forceDebug;
+ return code.asLirCode().newCodeWithoutDebugLocalInfoTable();
}
+ assert false;
+ return code;
}
private LirCode<?> toLirCodeThatLogsError(AppView<? extends AppInfoWithClassHierarchy> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 670fe06..ac9fb52 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -766,8 +766,10 @@
forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
encodedMethod.getReference(), callTarget);
- DexEncodedMethod.setDebugInfoWithFakeThisParameter(
- newMethod.getCode(), callTarget.getArity(), appView);
+ Code codeWithPatchedDebugInfo =
+ DexEncodedMethod.mutateOrCreateCodeWithFakeThisParameter(
+ newMethod.getCode(), callTarget.getArity(), appView);
+ newMethod.setCode(codeWithPatchedDebugInfo, newMethod.getParameterInfo());
return newMethod;
});
if (replacement != null) {
@@ -876,6 +878,7 @@
assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
assert !appView.options().isGeneratingDex()
|| replacement.getCode().isDexCode()
+ || replacement.getCode().isLirCode()
|| appView.options().partialSubCompilationConfiguration != null;
return new ProgramMethod(implMethodHolder, replacement);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 40b386a..46e0795 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -232,8 +232,9 @@
method.getDefinition().isD8R8Synthesized(),
appView.dexItemFactory());
if (!definition.isStatic()) {
- DexEncodedMethod.setDebugInfoWithFakeThisParameter(
- code, companion.getReference().getArity(), appView);
+ code =
+ DexEncodedMethod.mutateOrCreateCodeWithFakeThisParameter(
+ code, companion.getReference().getArity(), appView);
}
companion.setCode(code, appView);
method.setCode(InvalidCode.getInstance(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
index 5ef1f93..232c840 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
@@ -183,6 +183,14 @@
assert block.streamInstructions().noneMatch(Instruction::isThrowBlockOutlineMarker);
}
+ // TODO(b/443663978): Workaround the fact that we do not correctly patch up the debug info for
+ // LIR code in interface method desugaring. Remove when resolved.
+ for (DebugLocalRead dlr : code.<DebugLocalRead>instructions(Instruction::isDebugLocalRead)) {
+ if (dlr.getDebugValues().isEmpty()) {
+ dlr.remove();
+ }
+ }
+
// Run the dead code remover to ensure code that has been moved into the outline is removed
// (e.g., constants, the allocation of the exception).
deadCodeRemover.run(code, Timing.empty());
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index 8bacebb..97a81f0 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -810,6 +810,23 @@
}
}
+ public LirCode<EV> newCodeWithoutDebugLocalInfoTable() {
+ if (debugLocalInfoTable == null) {
+ return this;
+ }
+ return new LirCode<>(
+ constants,
+ positionTable,
+ argumentCount,
+ instructions,
+ instructionCount,
+ tryCatchTable,
+ null,
+ strategyInfo,
+ useDexEstimationStrategy,
+ metadataMap);
+ }
+
public LirCode<EV> newCodeWithRewrittenConstantPool(Function<LirConstant, LirConstant> rewriter) {
LirConstant[] rewrittenConstants = ArrayUtils.map(constants, rewriter, new LirConstant[0]);
if (constants == rewrittenConstants) {
diff --git a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
index b8bc577..497c66a 100644
--- a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
+++ b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
@@ -351,8 +351,9 @@
factory);
}
if (!method.getAccessFlags().isStatic()) {
- DexEncodedMethod.setDebugInfoWithFakeThisParameter(
- code, syntheticMethod.getArity(), appView);
+ code =
+ DexEncodedMethod.mutateOrCreateCodeWithFakeThisParameter(
+ code, syntheticMethod.getArity(), appView);
}
return code;
}));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerInterfaceMethodDesugaringTest.java
new file mode 100644
index 0000000..3b9a6e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerInterfaceMethodDesugaringTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2025, 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.outliner.exceptions;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collection;
+import org.junit.Test;
+
+public class ThrowBlockOutlinerInterfaceMethodDesugaringTest extends ThrowBlockOutlinerTestBase {
+
+ @Test
+ public void test() throws Exception {
+ TestCompileResult<?, ?> compileResult =
+ testForD8(parameters)
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(
+ transformer(I.class)
+ .setPrivate(I.class.getDeclaredMethod("privateMethod", boolean.class))
+ .transform())
+ .apply(this::configure)
+ .compile()
+ .inspect(this::inspectOutput);
+ compileResult
+ .run(parameters.getRuntime(), Main.class, "default")
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("Default method"));
+ compileResult
+ .run(parameters.getRuntime(), Main.class, "private")
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("Private method"));
+ compileResult
+ .run(parameters.getRuntime(), Main.class, "static")
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .assertFailureWithErrorThatMatches(containsString("Static method"));
+ compileResult.run(parameters.getRuntime(), Main.class, "skip").assertSuccessWithEmptyOutput();
+ }
+
+ @Override
+ public void inspectOutlines(Collection<ThrowBlockOutline> outlines, DexItemFactory factory) {
+ // Verify that we have a single outline with three users.
+ assertEquals(1, outlines.size());
+ ThrowBlockOutline outline = outlines.iterator().next();
+ assertEquals(3, outline.getNumberOfUsers());
+ }
+
+ private void inspectOutput(CodeInspector inspector) {
+ // Main, I, I$-CC, A and the synthetic outline class.
+ assertEquals(
+ parameters.canUseDefaultAndStaticInterfaceMethods() ? 4 : 5, inspector.allClasses().size());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = new A();
+ i.defaultMethod(args[0].equals("default"));
+ i.callPrivateMethod(args[0].equals("private"));
+ I.staticMethod(args[0].equals("static"));
+ }
+ }
+
+ interface I {
+
+ default void defaultMethod(boolean fail) {
+ if (fail) {
+ throw new RuntimeException("Default method");
+ }
+ }
+
+ // Made private by transformer.
+ default void privateMethod(boolean fail) {
+ if (fail) {
+ throw new RuntimeException("Private method");
+ }
+ }
+
+ default void callPrivateMethod(boolean fail) {
+ privateMethod(fail);
+ }
+
+ static void staticMethod(boolean fail) {
+ if (fail) {
+ throw new RuntimeException("Static method");
+ }
+ }
+ }
+
+ static class A implements I {}
+}