Add support for materializing line info from native pc methods

Bug: b/213411850
Bug: b/284954852
Bug: b/232212653
Change-Id: I84a1e903a6365f0693ad26c4c09a8f1dcec26d63
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 37fad8c..073cc8d 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -947,10 +947,7 @@
             DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
             DexWritableCode rewrittenCode =
                 code.rewriteCodeWithJumboStrings(
-                    method,
-                    mapping,
-                    application.dexItemFactory,
-                    options.testing.forceJumboStringProcessing);
+                    method, mapping, appView, options.testing.forceJumboStringProcessing);
             method.setCode(rewrittenCode.asCode(), appView);
           });
     }
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index a68f99f..13f6812 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -53,6 +53,7 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.function.BooleanSupplier;
 
 public class JumboStringRewriter {
 
@@ -92,6 +93,7 @@
 
   private final DexEncodedMethod method;
   private final DexString firstJumboString;
+  private final BooleanSupplier materializeInfoForNativePc;
   private final DexItemFactory factory;
   private final Map<DexInstruction, List<DexInstruction>> instructionTargets =
       new IdentityHashMap<>();
@@ -105,12 +107,20 @@
   private final Map<TryHandler, List<DexInstruction>> handlerTargets = new IdentityHashMap<>();
 
   public JumboStringRewriter(
-      DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) {
+      DexEncodedMethod method,
+      DexString firstJumboString,
+      BooleanSupplier materializeInfoForNativePc,
+      DexItemFactory factory) {
     this.method = method;
     this.firstJumboString = firstJumboString;
+    this.materializeInfoForNativePc = materializeInfoForNativePc;
     this.factory = factory;
   }
 
+  private DexCode getCode() {
+    return method.getCode().asDexCode();
+  }
+
   public DexCode rewrite() {
     // Build maps from everything in the code that uses offsets or direct addresses to reference
     // instructions to the actual instruction referenced.
@@ -124,7 +134,7 @@
     TryHandler[] newHandlers = rewriteHandlerOffsets();
     DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets();
     // Set the new code on the method.
-    DexCode oldCode = method.getCode().asDexCode();
+    DexCode oldCode = getCode();
     DexCode newCode =
         new DexCode(
             oldCode.registerSize,
@@ -185,7 +195,7 @@
   }
 
   private Try[] rewriteTryOffsets() {
-    DexCode code = method.getCode().asDexCode();
+    DexCode code = getCode();
     Try[] result = new Try[code.tries.length];
     for (int i = 0; i < code.tries.length; i++) {
       Try theTry = code.tries[i];
@@ -197,7 +207,7 @@
   }
 
   private TryHandler[] rewriteHandlerOffsets() {
-    DexCode code = method.getCode().asDexCode();
+    DexCode code = getCode();
     TryHandler[] result = new TryHandler[code.handlers.length];
     for (int i = 0; i < code.handlers.length; i++) {
       TryHandler handler = code.handlers[i];
@@ -218,7 +228,7 @@
   }
 
   private DexDebugInfo rewriteDebugInfoOffsets() {
-    DexCode code = method.getCode().asDexCode();
+    DexCode code = getCode();
     if (!debugEventTargets.isEmpty()) {
       assert debugEventBasedInfo != null;
       int lastOriginalOffset = 0;
@@ -256,7 +266,7 @@
   @SuppressWarnings("JdkObsolete")
   private List<DexInstruction> expandCode() {
     LinkedList<DexInstruction> instructions = new LinkedList<>();
-    Collections.addAll(instructions, method.getCode().asDexCode().instructions);
+    Collections.addAll(instructions, getCode().instructions);
     int offsetDelta;
     do {
       ListIterator<DexInstruction> it = instructions.listIterator();
@@ -436,7 +446,7 @@
   }
 
   private void recordInstructionTargets(Int2ReferenceMap<DexInstruction> offsetToInstruction) {
-    DexInstruction[] instructions = method.getCode().asDexCode().instructions;
+    DexInstruction[] instructions = getCode().instructions;
     for (DexInstruction instruction : instructions) {
       if (instruction instanceof DexFormat22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe
         DexFormat22t condition = (DexFormat22t) instruction;
@@ -486,14 +496,15 @@
   }
 
   private void recordDebugEventTargets(Int2ReferenceMap<DexInstruction> offsetToInstruction) {
-    // TODO(b/213411850): Merging pc based D8 builds will map out of PC for any jumbo processed
-    //  method. Instead we should rather retain the PC encoding by bumping the max-pc and recording
-    //  the line number translation. We actually need to do so to support merging with native PC
-    //  support as in that case we can't reflect the change in the line table, only in the mapping.
-    EventBasedDebugInfo eventBasedInfo =
-        DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory);
+    EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(getCode(), factory);
     if (eventBasedInfo == null) {
-      return;
+      if (materializeInfoForNativePc.getAsBoolean()) {
+        eventBasedInfo =
+            DexDebugInfo.createEventBasedDebugInfoForNativePc(
+                method.getParameters().size(), getCode(), factory);
+      } else {
+        return;
+      }
     }
     debugEventBasedInfo = eventBasedInfo;
     int address = 0;
@@ -516,7 +527,7 @@
 
   private void recordTryAndHandlerTargets(
       Int2ReferenceMap<DexInstruction> offsetToInstruction, DexInstruction lastInstruction) {
-    DexCode code = method.getCode().asDexCode();
+    DexCode code = getCode();
     for (Try theTry : code.tries) {
       DexInstruction start = offsetToInstruction.get(theTry.startAddress);
       DexInstruction end = null;
@@ -553,7 +564,7 @@
 
   private void recordTargets() {
     Int2ReferenceMap<DexInstruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>();
-    DexInstruction[] instructions = method.getCode().asDexCode().instructions;
+    DexInstruction[] instructions = getCode().instructions;
     boolean containsPayloads = false;
     for (DexInstruction instruction : instructions) {
       offsetToInstruction.put(instruction.getOffset(), instruction);
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index b0e0aba..31b2efe 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -324,7 +324,7 @@
 
   @Override
   public DexWritableCode rewriteCodeWithJumboStrings(
-      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+      ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
     // Intentionally empty. This piece of code does not have any const-string instructions.
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 80a75b9..5932bb5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -212,7 +212,7 @@
 
   @Override
   public DexWritableCode rewriteCodeWithJumboStrings(
-      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+      ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
     DexString firstJumboString = null;
     if (force) {
       firstJumboString = mapping.getFirstString();
@@ -226,7 +226,12 @@
       }
     }
     return firstJumboString != null
-        ? new JumboStringRewriter(method.getDefinition(), firstJumboString, factory).rewrite()
+        ? new JumboStringRewriter(
+                method.getDefinition(),
+                firstJumboString,
+                () -> appView.options().shouldMaterializeLineInfoForNativePcEncoding(method),
+                appView.dexItemFactory())
+            .rewrite()
         : this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 8826ec4..1a0023a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -314,11 +314,18 @@
         code, pcBasedDebugInfo.maxPc);
     // Generate a line event at each throwing instruction.
     DexInstruction[] instructions = code.instructions;
-    return forceConvertToEventBasedDebugInfo(pcBasedDebugInfo, instructions, factory);
+    return forceConvertToEventBasedDebugInfo(
+        PcBasedDebugInfo.START_LINE, pcBasedDebugInfo.getParameterCount(), instructions, factory);
   }
 
-  public static EventBasedDebugInfo forceConvertToEventBasedDebugInfo(
-      PcBasedDebugInfo pcBasedDebugInfo, DexInstruction[] instructions, DexItemFactory factory) {
+  public static EventBasedDebugInfo createEventBasedDebugInfoForNativePc(
+      int parameterCount, DexCode code, DexItemFactory factory) {
+    assert code.getDebugInfo() == null;
+    return forceConvertToEventBasedDebugInfo(0, parameterCount, code.instructions, factory);
+  }
+
+  private static EventBasedDebugInfo forceConvertToEventBasedDebugInfo(
+      int startLine, int parameterCount, DexInstruction[] instructions, DexItemFactory factory) {
     List<DexDebugEvent> events = new ArrayList<>(instructions.length);
     int delta = 0;
     for (DexInstruction instruction : instructions) {
@@ -329,9 +336,7 @@
       delta += instruction.getSize();
     }
     return new EventBasedDebugInfo(
-        PcBasedDebugInfo.START_LINE,
-        new DexString[pcBasedDebugInfo.getParameterCount()],
-        events.toArray(DexDebugEvent.EMPTY_ARRAY));
+        startLine, new DexString[parameterCount], events.toArray(DexDebugEvent.EMPTY_ARRAY));
   }
 
   public static DexDebugInfoForWriting convertToWritable(DexDebugInfo debugInfo) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index 99f8679..afe2afb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -96,7 +96,7 @@
 
   /** Rewrites the code to have JumboString bytecode if required by mapping. */
   DexWritableCode rewriteCodeWithJumboStrings(
-      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force);
+      ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force);
 
   void setCallSiteContexts(ProgramMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 19d78c1..4905063 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -197,7 +197,7 @@
 
   @Override
   public DexWritableCode rewriteCodeWithJumboStrings(
-      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+      ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
     // Intentionally empty. This piece of code does not have any const-string instructions.
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index 707e45a..90c6d8d 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -219,7 +219,7 @@
 
   @Override
   public DexWritableCode rewriteCodeWithJumboStrings(
-      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+      ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
     // Intentionally empty. This piece of code does not have any const-string instructions.
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index b2ce050..89edec3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -52,6 +52,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
@@ -2515,6 +2516,10 @@
     throw new Unreachable();
   }
 
+  public boolean canUseDexPc2PcAsDebugInformation() {
+    return isGeneratingDex() && lineNumberOptimization == LineNumberOptimization.ON;
+  }
+
   // Debug entries may be dropped only if the source file content allows being omitted from
   // stack traces, or if the VM will report the source file even with a null valued debug info.
   public boolean allowDiscardingResidualDebugInfo() {
@@ -2522,8 +2527,10 @@
     return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile();
   }
 
-  public boolean canUseDexPc2PcAsDebugInformation() {
-    return isGeneratingDex() && lineNumberOptimization == LineNumberOptimization.ON;
+  public boolean allowDiscardingResidualDebugInfo(ProgramMethod method) {
+    // TODO(b/146565491): We can drop debug info once fixed at a known min-api.
+    DexString sourceFile = method.getHolder().getSourceFile();
+    return sourceFile == null || sourceFile.equals(itemFactory.defaultSourceFileAttribute);
   }
 
   public boolean canUseNativeDexPcInsteadOfDebugInfo() {
@@ -2532,6 +2539,19 @@
         && allowDiscardingResidualDebugInfo();
   }
 
+  public boolean canUseNativeDexPcInsteadOfDebugInfo(ProgramMethod method) {
+    return canUseDexPc2PcAsDebugInformation()
+        && hasMinApi(AndroidApiLevel.O)
+        && allowDiscardingResidualDebugInfo(method);
+  }
+
+  public boolean shouldMaterializeLineInfoForNativePcEncoding(ProgramMethod method) {
+    assert method.getDefinition().getCode().asDexCode() != null;
+    assert method.getDefinition().getCode().asDexCode().getDebugInfo() == null;
+    return method.getHolder().originatesFromDexResource()
+        && canUseNativeDexPcInsteadOfDebugInfo(method);
+  }
+
   public boolean isInterfaceMethodDesugaringEnabled() {
     // This condition is to filter out tests that never set program consumer.
     if (!hasConsumer()) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/composepc/ComposePcEncodingTest.java b/src/test/java/com/android/tools/r8/debuginfo/composepc/ComposePcEncodingTest.java
new file mode 100644
index 0000000..311ea41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/composepc/ComposePcEncodingTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2023, 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.debuginfo.composepc;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.util.OptionalInt;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ComposePcEncodingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ComposePcEncodingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private byte[] getTransformedClass() throws Exception {
+    return transformer(TestClass.class)
+        .removeLineNumberTable(MethodPredicate.onName("unusedKeptAndNoLineInfo"))
+        .transform();
+  }
+
+  private boolean isNativePcSupported() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+  }
+
+  @Test
+  public void test() throws Exception {
+    MethodReference unusedKeptAndNoLineInfo =
+        Reference.methodFromMethod(TestClass.class.getDeclaredMethod("unusedKeptAndNoLineInfo"));
+
+    // R8 compiles to DEX with pc2pc encoding or native-pc encoding.
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(getTransformedClass())
+            .addKeepMainRule(TestClass.class)
+            .addKeepMethodRules(unusedKeptAndNoLineInfo)
+            .setMinApi(parameters)
+            .addKeepAttributeLineNumberTable()
+            .compile()
+            .inspect(
+                inspector -> {
+                  // Expected residual line info of 1 for pc2pc encoding and some value for native.
+                  int residualLine = isNativePcSupported() ? 123 : 1;
+                  // Check the expected status of the DEX debug info object for the "no lines".
+                  MethodSubject methodNoLines = inspector.method(unusedKeptAndNoLineInfo);
+                  assertThat(methodNoLines, isPresent());
+                  // TODO(b/232212653): This should be true in pc2pc compilation with a single line.
+                  assertFalse(methodNoLines.hasLineNumberTable());
+                  // Check that "retracing" the pinned method with no lines maps to "noline/zero".
+                  RetraceFrameResult retraceResult =
+                      inspector
+                          .retrace()
+                          .retraceFrame(
+                              RetraceStackTraceContext.empty(),
+                              OptionalInt.of(residualLine),
+                              unusedKeptAndNoLineInfo);
+                  assertFalse(retraceResult.isAmbiguous());
+                  RetraceFrameElement frameElement = retraceResult.stream().findFirst().get();
+                  assertEquals(0, frameElement.getOuterFrames().size());
+                  RetracedMethodReference topFrame = frameElement.getTopFrame();
+                  assertTrue(topFrame.isKnown());
+                  // TODO(b/232212653): Retrace should map back to the "no line" value of zero.
+                  assertFalse(topFrame.hasPosition());
+                });
+
+    compileResult
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectStackTrace(ComposePcEncodingTest::checkStackTrace);
+
+    Path r8OutputDex = compileResult.writeToZip();
+    Path r8OutputMap =
+        FileUtils.writeTextFile(
+            temp.newFolder().toPath().resolve("out.map"), compileResult.getProguardMap());
+
+    // D8 (re)merges DEX with an artificial jumbo-string to force a remapping of PC values.
+    testForD8(parameters.getBackend())
+        .addProgramFiles(r8OutputDex)
+        .setMinApi(parameters)
+        // We only optimize line info in release mode and with a mapping file output enabled.
+        .release()
+        .internalEnableMappingOutput()
+        .apply(b -> b.getBuilder().setProguardInputMapFile(r8OutputMap))
+        // Forcing jumbo processing will shift the PC values on the methods.
+        .addOptionsModification(o -> o.testing.forceJumboStringProcessing = true)
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectFailure(
+            inspector -> {
+              MethodSubject methodNoLines = inspector.method(unusedKeptAndNoLineInfo);
+              assertThat(methodNoLines, isPresent());
+              // TODO(b/213411850): This should depend on native pc support.
+              assertTrue(methodNoLines.hasLineNumberTable());
+            })
+        .inspectStackTrace(ComposePcEncodingTest::checkStackTrace);
+  }
+
+  private static void checkStackTrace(StackTrace stackTrace) {
+    StackTraceLine.Builder builder =
+        StackTraceLine.builder()
+            .setClassName(typeName(TestClass.class))
+            .setFileName(TestClass.class.getSimpleName() + ".java");
+    assertThat(
+        stackTrace,
+        StackTrace.isSame(
+            StackTrace.builder()
+                .add(builder.setMethodName("bar").setLineNumber(15).build())
+                .add(builder.setMethodName("main").setLineNumber(25).build())
+                .build()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/composepc/TestClass.java b/src/test/java/com/android/tools/r8/debuginfo/composepc/TestClass.java
new file mode 100644
index 0000000..caae028
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/composepc/TestClass.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.debuginfo.composepc;
+
+class TestClass {
+
+  public static void foo() {
+    System.out.println("AAA");
+  }
+
+  public static void bar() {
+    System.out.println("BBB");
+    if (System.nanoTime() > 0) {
+      throw new RuntimeException(); // LINE 15 - update ComposePcEncodingTest if changed.
+    }
+  }
+
+  public static void baz() {
+    System.out.println("CCC");
+  }
+
+  public static void main(String[] args) {
+    foo();
+    bar(); // LINE 25 - update ComposePcEncodingTest if changed.
+    baz();
+  }
+
+  // Line removed by transform.
+  public static void unusedKeptAndNoLineInfo() {
+    System.out.println("DDDD");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index bf49805..ebf00c9 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.dex.code.DexIfNez;
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.dex.code.DexReturnVoid;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.graph.DexCode.TryHandler;
@@ -158,6 +159,13 @@
             .disableMethodNotNullCheck()
             .disableAndroidApiLevelCheck()
             .build();
-    return new JumboStringRewriter(method, string, factory).rewrite();
+    return new JumboStringRewriter(
+            method,
+            string,
+            () -> {
+              throw new Unreachable();
+            },
+            factory)
+        .rewrite();
   }
 }