Rename merged exception classes in DeadCodeRemover

This CL renames the guards in catch handlers in the DeadCodeRemover, and removes catch handlers that are clearly not needed after renaming.

Example:

  try { ... }
  catch (ExceptionB e) { ... }
  catch (ExceptionA e) {
    // If ExceptionA gets merged into ExceptionB, then this handler is clearly dead
    ...
  }

Bug: 73958515
Change-Id: I85613f28ee45fe1f6edad2644252fb3ba14a747a
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 5f0fb83..831edbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -264,10 +264,9 @@
 
   public void setCode(
       IRCode ir,
-      GraphLense graphLense,
       RegisterAllocator registerAllocator,
       InternalOptions options) {
-    final DexBuilder builder = new DexBuilder(ir, graphLense, registerAllocator, options);
+    final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
     code = builder.build(method.getArity());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 113d522..eb11aac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -9,7 +9,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -20,10 +22,13 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -173,7 +178,7 @@
   public void removeSuccessor(BasicBlock block) {
     int index = successors.indexOf(block);
     assert index >= 0 : "removeSuccessor did not find the successor to remove";
-    removeSuccessorsByIndex(Collections.singletonList(index));
+    removeSuccessorsByIndex(new IntArrayList(new int[] {index}));
   }
 
   public void removePredecessor(BasicBlock block) {
@@ -328,7 +333,7 @@
     assert false : "replaceSuccessor did not find the predecessor to replace";
   }
 
-  public void removeSuccessorsByIndex(List<Integer> successorsToRemove) {
+  public void removeSuccessorsByIndex(IntList successorsToRemove) {
     if (successorsToRemove.isEmpty()) {
       return;
     }
@@ -678,6 +683,47 @@
     catchHandlers = new CatchHandlers<>(guards, successorIndexes);
   }
 
+  // Due to class merging, it is possible that two exception classes have been merged into one.
+  // This function renames the guards according to the given graph lense.
+  public void renameGuardsInCatchHandlers(GraphLense graphLense) {
+    assert hasCatchHandlers();
+    List<DexType> newGuards = new ArrayList<>(catchHandlers.getGuards().size());
+    for (DexType guard : catchHandlers.getGuards()) {
+      // The type may have changed due to class merging.
+      // TODO(christofferqa): This assumes that the graph lense is context insensitive for the
+      // given type (which is always the case). Consider removing the context-argument from
+      // GraphLense.lookupType, since we do not currently have a use case for it.
+      newGuards.add(graphLense.lookupType(guard, null));
+    }
+    this.catchHandlers = new CatchHandlers<>(newGuards, catchHandlers.getAllTargets());
+  }
+
+  public boolean consistentCatchHandlers() {
+    // Check that catch handlers are always the first successors of a block.
+    if (hasCatchHandlers()) {
+      assert exit().isGoto() || exit().isThrow();
+      CatchHandlers<Integer> catchHandlers = getCatchHandlersWithSuccessorIndexes();
+      // If there is a catch-all guard it must be the last.
+      List<DexType> guards = catchHandlers.getGuards();
+      int lastGuardIndex = guards.size() - 1;
+      for (int i = 0; i < guards.size(); i++) {
+        assert guards.get(i) != DexItemFactory.catchAllType || i == lastGuardIndex;
+      }
+      // Check that all successors except maybe the last are catch successors.
+      List<Integer> sortedHandlerIndices = new ArrayList<>(catchHandlers.getAllTargets());
+      sortedHandlerIndices.sort(Comparator.naturalOrder());
+      int firstIndex = sortedHandlerIndices.get(0);
+      int lastIndex = sortedHandlerIndices.get(sortedHandlerIndices.size() - 1);
+      assert firstIndex == 0;
+      assert lastIndex < sortedHandlerIndices.size();
+      int lastSuccessorIndex = getSuccessors().size() - 1;
+      assert lastIndex == lastSuccessorIndex // All successors are catch successors.
+          || lastIndex == lastSuccessorIndex - 1; // All but one successors are catch successors.
+      assert lastIndex == lastSuccessorIndex || !exit().isThrow();
+    }
+    return true;
+  }
+
   public void clearCurrentDefinitions() {
     currentDefinitions = null;
     for (Phi phi : getPhis()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index a5d3e4a..c832991 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -5,8 +5,6 @@
 
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
@@ -473,28 +471,7 @@
 
   private boolean consistentCatchHandlers() {
     for (BasicBlock block : blocks) {
-      // Check that catch handlers are always the first successors of a block.
-      if (block.hasCatchHandlers()) {
-        assert block.exit().isGoto() || block.exit().isThrow();
-        CatchHandlers<Integer> catchHandlers = block.getCatchHandlersWithSuccessorIndexes();
-        // If there is a catch-all guard it must be the last.
-        List<DexType> guards = catchHandlers.getGuards();
-        int lastGuardIndex = guards.size() - 1;
-        for (int i = 0; i < guards.size(); i++) {
-          assert guards.get(i) != DexItemFactory.catchAllType || i == lastGuardIndex;
-        }
-        // Check that all successors except maybe the last are catch successors.
-        List<Integer> sortedHandlerIndices = new ArrayList<>(catchHandlers.getAllTargets());
-        sortedHandlerIndices.sort(Comparator.naturalOrder());
-        int firstIndex = sortedHandlerIndices.get(0);
-        int lastIndex = sortedHandlerIndices.get(sortedHandlerIndices.size() - 1);
-        assert firstIndex == 0;
-        assert lastIndex < sortedHandlerIndices.size();
-        int lastSuccessorIndex = block.getSuccessors().size() - 1;
-        assert lastIndex == lastSuccessorIndex  // All successors are catch successors.
-            || lastIndex == lastSuccessorIndex - 1; // All but one successors are catch successors.
-        assert lastIndex == lastSuccessorIndex || !block.exit().isThrow();
-      }
+      assert block.consistentCatchHandlers();
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e798fd2..de86cb4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -118,13 +119,16 @@
   }
 
   public CfCode build(
-      CodeRewriter rewriter, InternalOptions options, AppInfoWithSubtyping appInfo) {
+      CodeRewriter rewriter,
+      GraphLense graphLense,
+      InternalOptions options,
+      AppInfoWithSubtyping appInfo) {
     computeInitializers();
     types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
     splitExceptionalBlocks();
     LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, types);
     loadStoreHelper.insertLoadsAndStores();
-    DeadCodeRemover.removeDeadCode(code, rewriter, options);
+    DeadCodeRemover.removeDeadCode(code, rewriter, graphLense, options);
     removeUnneededLoadsAndStores();
     registerAllocator = new CfRegisterAllocator(code, options);
     registerAllocator.allocateRegisters();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index ee7df48..8201562 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -43,7 +43,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -82,10 +81,6 @@
   // The IR representation of the code to build.
   private final IRCode ir;
 
-  // Graph lense for building the exception handlers. Needed since program classes that inherit
-  // from Throwable may get merged into their subtypes during class merging.
-  private final GraphLense graphLense;
-
   // The register allocator providing register assignments for the code to build.
   private final RegisterAllocator registerAllocator;
 
@@ -121,14 +116,11 @@
 
   public DexBuilder(
       IRCode ir,
-      GraphLense graphLense,
       RegisterAllocator registerAllocator,
       InternalOptions options) {
     assert ir != null;
-    assert graphLense != null;
     assert registerAllocator != null;
     this.ir = ir;
-    this.graphLense = graphLense;
     this.registerAllocator = registerAllocator;
     this.options = options;
   }
@@ -714,12 +706,7 @@
           assert i == handlerGroup.getGuards().size() - 1;
           catchAllOffset = targetOffset;
         } else {
-          // The type may have changed due to class merging.
-          // TODO(christofferqa): This assumes that the graph lense is context insensitive for the
-          // given type (which is always the case). Consider removing the context-argument from
-          // GraphLense.lookupType, since we do not currently have a use case for it.
-          DexType actualType = graphLense.lookupType(type, null);
-          pairs.add(new TypeAddrPair(actualType, targetOffset));
+          pairs.add(new TypeAddrPair(type, targetOffset));
         }
       }
       TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[pairs.size()]);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index df4e69c..a52637f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -482,7 +482,7 @@
                 // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
                 // unused out-values.
                 codeRewriter.rewriteMoveResult(code);
-                DeadCodeRemover.removeDeadCode(code, codeRewriter, options);
+                DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
                 consumer.accept(code, method);
                 return null;
               }));
@@ -532,7 +532,7 @@
     assert code.isConsistentSSA();
     code.traceBlocks();
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, graphLense, registerAllocator, options);
+    method.setCode(code, registerAllocator, options);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
           method.toSourceString(), logCode(options, method));
@@ -705,7 +705,7 @@
     // Dead code removal. Performed after simplifications to remove code that becomes dead
     // as a result of those simplifications. The following optimizations could reveal more
     // dead code which is removed right before register allocation in performRegisterAllocation.
-    DeadCodeRemover.removeDeadCode(code, codeRewriter, options);
+    DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
     assert code.isConsistentSSA();
 
     if (options.enableDesugaring && enableTryWithResourcesDesugaring()) {
@@ -776,7 +776,7 @@
   private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert !method.getCode().isDexCode();
     CfBuilder builder = new CfBuilder(method, code, options.itemFactory);
-    CfCode result = builder.build(codeRewriter, options, appInfo.withSubtyping());
+    CfCode result = builder.build(codeRewriter, graphLense, options, appInfo.withSubtyping());
     method.setCode(result);
     markProcessed(method, code, feedback);
   }
@@ -784,7 +784,7 @@
   private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, graphLense, registerAllocator, options);
+    method.setCode(code, registerAllocator, options);
     updateHighestSortingStrings(method);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
@@ -818,7 +818,7 @@
   private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
-    DeadCodeRemover.removeDeadCode(code, codeRewriter, options);
+    DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
     materializeInstructionBeforeLongOperationsWorkaround(code, options);
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
     registerAllocator.allocateRegisters(options.debug);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 531989e..83844c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
@@ -11,15 +13,19 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Queue;
+import java.util.Set;
 
 public class DeadCodeRemover {
 
   public static void removeDeadCode(
-      IRCode code, CodeRewriter codeRewriter, InternalOptions options) {
-    removeUnneededCatchHandlers(code);
+      IRCode code, CodeRewriter codeRewriter, GraphLense graphLense, InternalOptions options) {
+    removeUnneededCatchHandlers(code, graphLense, options);
     Queue<BasicBlock> worklist = new LinkedList<>();
     worklist.addAll(code.blocks);
     for (BasicBlock block = worklist.poll(); block != null; block = worklist.poll()) {
@@ -98,15 +104,52 @@
     }
   }
 
-  private static void removeUnneededCatchHandlers(IRCode code) {
+  private static void removeUnneededCatchHandlers(
+      IRCode code, GraphLense graphLense, InternalOptions options) {
     for (BasicBlock block : code.blocks) {
-      if (block.hasCatchHandlers() && !block.canThrow()) {
-        CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
-        for (BasicBlock target : handlers.getUniqueTargets()) {
-          target.unlinkCatchHandler();
+      if (block.hasCatchHandlers()) {
+        if (block.canThrow()) {
+          if (options.enableClassMerging) {
+            // Handle the case where an exception class has been merged into its sub class.
+            block.renameGuardsInCatchHandlers(graphLense);
+            unlinkDeadCatchHandlers(block, graphLense);
+          }
+        } else {
+          CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
+          for (BasicBlock target : handlers.getUniqueTargets()) {
+            target.unlinkCatchHandler();
+          }
         }
       }
     }
     code.removeUnreachableBlocks();
   }
+
+  // Due to class merging, it is possible that two exception classes have been merged into one. This
+  // function removes catch handlers where the guards ended up being the same as a previous one.
+  private static void unlinkDeadCatchHandlers(BasicBlock block, GraphLense graphLense) {
+    assert block.hasCatchHandlers();
+    CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
+    List<DexType> guards = catchHandlers.getGuards();
+    List<BasicBlock> targets = catchHandlers.getAllTargets();
+
+    Set<DexType> previouslySeenGuards = new HashSet<>();
+    List<BasicBlock> deadCatchHandlers = new ArrayList<>();
+    for (int i = 0; i < guards.size(); i++) {
+      // The type may have changed due to class merging.
+      // TODO(christofferqa): This assumes that the graph lense is context insensitive for the
+      // given type (which is always the case). Consider removing the context-argument from
+      // GraphLense.lookupType, since we do not currently have a use case for it.
+      DexType guard = graphLense.lookupType(guards.get(i), null);
+      boolean guardSeenBefore = !previouslySeenGuards.add(guard);
+      if (guardSeenBefore) {
+        deadCatchHandlers.add(targets.get(i));
+      }
+    }
+    // Remove the guards that are guaranteed to be dead.
+    for (BasicBlock deadCatchHandler : deadCatchHandlers) {
+      deadCatchHandler.unlinkCatchHandler();
+    }
+    assert block.consistentCatchHandlers();
+  }
 }
diff --git a/src/test/examples/classmerging/ExceptionTest.java b/src/test/examples/classmerging/ExceptionTest.java
index b365b2a..28442f3 100644
--- a/src/test/examples/classmerging/ExceptionTest.java
+++ b/src/test/examples/classmerging/ExceptionTest.java
@@ -8,12 +8,27 @@
   public static void main(String[] args) {
     // The following will lead to a catch handler for ExceptionA, which is merged into ExceptionB.
     try {
-      throw new ExceptionB("Ouch!");
+      doSomethingThatMightThrowExceptionB();
+      doSomethingThatMightThrowException2();
+    } catch (ExceptionB exception) {
+      System.out.println("Caught exception: " + exception.getMessage());
     } catch (ExceptionA exception) {
       System.out.println("Caught exception: " + exception.getMessage());
+    } catch (Exception2 exception) {
+      System.out.println("Caught exception: " + exception.getMessage());
+    } catch (Exception1 exception) {
+      System.out.println("Caught exception: " + exception.getMessage());
     }
   }
 
+  private static void doSomethingThatMightThrowExceptionB() throws ExceptionB {
+    throw new ExceptionB("Ouch!");
+  }
+
+  private static void doSomethingThatMightThrowException2() throws Exception2 {
+    throw new Exception2("Ouch!");
+  }
+
   // Will be merged into ExceptionB when class merging is enabled.
   public static class ExceptionA extends Exception {
     public ExceptionA(String message) {
@@ -26,4 +41,16 @@
       super(message);
     }
   }
+
+  public static class Exception1 extends Exception {
+    public Exception1(String message) {
+      super(message);
+    }
+  }
+
+  public static class Exception2 extends Exception1 {
+    public Exception2(String message) {
+      super(message);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 69f925c..d2643cb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.classmerging;
 
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -12,9 +14,14 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.MoveException;
+import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -110,14 +117,36 @@
         new Path[] {
           CF_DIR.resolve("ExceptionTest.class"),
           CF_DIR.resolve("ExceptionTest$ExceptionA.class"),
-          CF_DIR.resolve("ExceptionTest$ExceptionB.class")
+          CF_DIR.resolve("ExceptionTest$ExceptionB.class"),
+          CF_DIR.resolve("ExceptionTest$Exception1.class"),
+          CF_DIR.resolve("ExceptionTest$Exception2.class")
         };
     Set<String> preservedClassNames =
-        ImmutableSet.of("classmerging.ExceptionTest", "classmerging.ExceptionTest$ExceptionB");
-    runTest(main, programFiles, preservedClassNames);
+        ImmutableSet.of(
+            "classmerging.ExceptionTest",
+            "classmerging.ExceptionTest$ExceptionB",
+            "classmerging.ExceptionTest$Exception2");
+    DexInspector inspector = runTest(main, programFiles, preservedClassNames);
+
+    ClassSubject mainClass = inspector.clazz(main);
+    assertThat(mainClass, isPresent());
+
+    MethodSubject mainMethod =
+        mainClass.method("void", "main", ImmutableList.of("java.lang.String[]"));
+    assertThat(mainMethod, isPresent());
+
+    // Check that the second catch handler has been removed.
+    DexCode code = mainMethod.getMethod().getCode().asDexCode();
+    int numberOfMoveExceptionInstructions = 0;
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof MoveException) {
+        numberOfMoveExceptionInstructions++;
+      }
+    }
+    assertEquals(2, numberOfMoveExceptionInstructions);
   }
 
-  private void runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
+  private DexInspector runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
       throws Exception {
     AndroidApp input = readProgramFiles(programFiles);
     AndroidApp output = compileWithR8(input, EXAMPLE_KEEP, this::configure);
@@ -135,5 +164,6 @@
     }
     // Check that the R8-generated code produces the same result as D8-generated code.
     assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+    return inspector;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d6e3612..4214a67 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -38,7 +38,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -653,7 +652,7 @@
                 code);
         IRCode ir = code.buildIR(method, null, options, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
-        method.setCode(ir, GraphLense.getIdentityLense(), allocator, options);
+        method.setCode(ir, allocator, options);
         directMethods[i] = method;
       }
       builder.addProgramClass(