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 {}
+}