Merge "CF frontend for testing: Handle state transfer for throw-instruction"
diff --git a/build.gradle b/build.gradle
index 7720cf3..7f41caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -61,7 +61,10 @@
     '-Xep:MissingDefault:ERROR',
     '-Xep:MultipleTopLevelClasses:ERROR',
     '-Xep:NarrowingCompoundAssignment:ERROR',
-    '-Xep:BoxedPrimitiveConstructor:ERROR']
+    '-Xep:BoxedPrimitiveConstructor:ERROR',
+    '-Xep:LogicalAssignment:ERROR',
+    '-Xep:FloatCast:ERROR',
+    '-Xep:ReturnValueIgnored:ERROR']
 
 apply from: 'copyAdditionalJctfCommonFiles.gradle'
 
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 831edbe..5f0fb83 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -264,9 +264,10 @@
 
   public void setCode(
       IRCode ir,
+      GraphLense graphLense,
       RegisterAllocator registerAllocator,
       InternalOptions options) {
-    final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
+    final DexBuilder builder = new DexBuilder(ir, graphLense, registerAllocator, options);
     code = builder.build(method.getArity());
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index ed99d25..2310891 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -557,7 +557,7 @@
 
     @Override
     public int hashCode() {
-      return (int) value * 19;
+      return (int) (value * 19);
     }
 
     @Override
@@ -615,7 +615,7 @@
 
     @Override
     public int hashCode() {
-      return (int) value * 29;
+      return (int) (value * 29);
     }
 
     @Override
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 8201562..ee7df48 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,6 +43,7 @@
 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;
@@ -81,6 +82,10 @@
   // 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;
 
@@ -116,11 +121,14 @@
 
   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;
   }
@@ -706,7 +714,12 @@
           assert i == handlerGroup.getGuards().size() - 1;
           catchAllOffset = targetOffset;
         } else {
-          pairs.add(new TypeAddrPair(type, targetOffset));
+          // 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));
         }
       }
       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 81dc436..df4e69c 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
@@ -532,7 +532,7 @@
     assert code.isConsistentSSA();
     code.traceBlocks();
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, registerAllocator, options);
+    method.setCode(code, graphLense, registerAllocator, options);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
           method.toSourceString(), logCode(options, method));
@@ -784,7 +784,7 @@
   private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, registerAllocator, options);
+    method.setCode(code, graphLense, registerAllocator, options);
     updateHighestSortingStrings(method);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index ed9cbdb..c4276dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -812,45 +812,45 @@
         skipWhitespace();
         switch (peekChar()) {
           case 'a':
-            if (found = acceptString("abstract")) {
+            if ((found = acceptString("abstract"))) {
               flags.setAbstract();
             }
             break;
           case 'f':
-            if (found = acceptString("final")) {
+            if ((found = acceptString("final"))) {
               flags.setFinal();
             }
             break;
           case 'n':
-            if (found = acceptString("native")) {
+            if ((found = acceptString("native"))) {
               flags.setNative();
             }
             break;
           case 'p':
-            if (found = acceptString("public")) {
+            if ((found = acceptString("public"))) {
               flags.setPublic();
-            } else if (found = acceptString("private")) {
+            } else if ((found = acceptString("private"))) {
               flags.setPrivate();
-            } else if (found = acceptString("protected")) {
+            } else if ((found = acceptString("protected"))) {
               flags.setProtected();
             }
             break;
           case 's':
-            if (found = acceptString("synchronized")) {
+            if ((found = acceptString("synchronized"))) {
               flags.setSynchronized();
-            } else if (found = acceptString("static")) {
+            } else if ((found = acceptString("static"))) {
               flags.setStatic();
-            } else if (found = acceptString("strictfp")) {
+            } else if ((found = acceptString("strictfp"))) {
               flags.setStrict();
             }
             break;
           case 't':
-            if (found = acceptString("transient")) {
+            if ((found = acceptString("transient"))) {
               flags.setTransient();
             }
             break;
           case 'v':
-            if (found = acceptString("volatile")) {
+            if ((found = acceptString("volatile"))) {
               flags.setVolatile();
             }
             break;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index b938596..f10ff69 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
+import com.android.tools.r8.naming.ClassNaming.Builder;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -187,6 +188,22 @@
     }
   }
 
+  // We will be remapping positional debug events and collect them as MappedPositions.
+  private static class MappedPosition {
+    private final DexMethod method;
+    private final int originalLine;
+    private final Position caller;
+    private final int obfuscatedLine;
+
+    private MappedPosition(
+        DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
+      this.method = method;
+      this.originalLine = originalLine;
+      this.caller = caller;
+      this.obfuscatedLine = obfuscatedLine;
+    }
+  }
+
   public static ClassNameMapper run(
       DexApplication application, NamingLens namingLens, boolean identityMapping) {
     IdentityHashMap<DexString, List<DexProgramClass>> classesOfFiles = new IdentityHashMap<>();
@@ -199,25 +216,8 @@
         continue;
       }
 
-      // Group methods by name
       IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
-          new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
-      clazz.forEachMethod(
-          method -> {
-            // Add method only if renamed or contains positions.
-            if (namingLens.lookupName(method.method) != method.method.name
-                || doesContainPositions(method)) {
-              methodsByName.compute(
-                  method.method.name,
-                  (name, methods) -> {
-                    if (methods == null) {
-                      methods = new ArrayList<>();
-                    }
-                    methods.add(method);
-                    return methods;
-                  });
-            }
-          });
+          groupMethodsByName(namingLens, clazz);
 
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -230,24 +230,11 @@
                       DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
                       clazz.toString()));
 
-      // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
-      if (!clazz.toString().equals(renamedClassName.toString())) {
-        // Not using return value, it's registered in classNameMapperBuilder
-        onDemandClassNamingBuilder.get();
-      }
+      // If the class is renamed add it to the classNamingBuilder.
+      addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
-      clazz.forEachField(
-          dexEncodedField -> {
-            DexField dexField = dexEncodedField.field;
-            DexString renamedName = namingLens.lookupName(dexField);
-            if (renamedName != dexField.name) {
-              FieldSignature signature =
-                  new FieldSignature(dexField.name.toString(), dexField.type.toString());
-              MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
-              onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
-            }
-          });
+      addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
 
       // Then process the methods.
       for (List<DexEncodedMethod> methods : methodsByName.values()) {
@@ -256,47 +243,13 @@
           // deterministic behaviour: the algorithm will assign new line numbers in this order.
           // Methods with different names can share the same line numbers, that's why they don't
           // need to be sorted.
-          methods.sort(
-              (lhs, rhs) -> {
-                // Sort by startline, then DexEncodedMethod.slowCompare.
-                // Use startLine = 0 if no debuginfo.
-                Code lhsCode = lhs.getCode();
-                Code rhsCode = rhs.getCode();
-                DexCode lhsDexCode =
-                    lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
-                DexCode rhsDexCode =
-                    rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
-                DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
-                DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
-                int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
-                int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
-                int startLineDiff = lhsStartLine - rhsStartLine;
-                if (startLineDiff != 0) return startLineDiff;
-                return DexEncodedMethod.slowCompare(lhs, rhs);
-              });
+          sortMethods(methods);
         }
 
         PositionRemapper positionRemapper =
             identityMapping ? new IdentityPositionRemapper() : new OptimizingPositionRemapper();
 
         for (DexEncodedMethod method : methods) {
-
-          // We will be remapping positional debug events and collect them as MappedPositions.
-          class MappedPosition {
-            private final DexMethod method;
-            private final int originalLine;
-            private final Position caller;
-            private final int obfuscatedLine;
-
-            private MappedPosition(
-                DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
-              this.method = method;
-              this.originalLine = originalLine;
-              this.caller = caller;
-              this.obfuscatedLine = obfuscatedLine;
-            }
-          }
-
           List<MappedPosition> mappedPositions = new ArrayList<>();
 
           if (doesContainPositions(method)) {
@@ -429,6 +382,75 @@
     return classNameMapperBuilder.build();
   }
 
+  // Sort by startline, then DexEncodedMethod.slowCompare.
+  // Use startLine = 0 if no debuginfo.
+  private static void sortMethods(List<DexEncodedMethod> methods) {
+    methods.sort(
+        (lhs, rhs) -> {
+          Code lhsCode = lhs.getCode();
+          Code rhsCode = rhs.getCode();
+          DexCode lhsDexCode =
+              lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
+          DexCode rhsDexCode =
+              rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
+          DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
+          DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
+          int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
+          int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
+          int startLineDiff = lhsStartLine - rhsStartLine;
+          if (startLineDiff != 0) return startLineDiff;
+          return DexEncodedMethod.slowCompare(lhs, rhs);
+        });
+  }
+
+  @SuppressWarnings("ReturnValueIgnored")
+  private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+      Supplier<Builder> onDemandClassNamingBuilder) {
+    // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
+    if (!clazz.toString().equals(renamedClassName.toString())) {
+      // Not using return value, it's registered in classNameMapperBuilder
+      onDemandClassNamingBuilder.get();
+    }
+  }
+
+  private static void addFieldsToClassNaming(NamingLens namingLens, DexProgramClass clazz,
+      Supplier<Builder> onDemandClassNamingBuilder) {
+    clazz.forEachField(
+        dexEncodedField -> {
+          DexField dexField = dexEncodedField.field;
+          DexString renamedName = namingLens.lookupName(dexField);
+          if (renamedName != dexField.name) {
+            FieldSignature signature =
+                new FieldSignature(dexField.name.toString(), dexField.type.toString());
+            MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
+            onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
+          }
+        });
+  }
+
+  private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+      NamingLens namingLens, DexProgramClass clazz) {
+    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+        new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+    clazz.forEachMethod(
+        method -> {
+          // Add method only if renamed or contains positions.
+          if (namingLens.lookupName(method.method) != method.method.name
+              || doesContainPositions(method)) {
+            methodsByName.compute(
+                method.method.name,
+                (name, methods) -> {
+                  if (methods == null) {
+                    methods = new ArrayList<>();
+                  }
+                  methods.add(method);
+                  return methods;
+                });
+          }
+        });
+    return methodsByName;
+  }
+
   private static boolean doesContainPositions(DexEncodedMethod method) {
     Code code = method.getCode();
     if (code == null || !code.isDexCode()) {
diff --git a/src/test/examples/classmerging/ExceptionTest.java b/src/test/examples/classmerging/ExceptionTest.java
new file mode 100644
index 0000000..b365b2a
--- /dev/null
+++ b/src/test/examples/classmerging/ExceptionTest.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2018, 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 classmerging;
+
+public class ExceptionTest {
+  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!");
+    } catch (ExceptionA exception) {
+      System.out.println("Caught exception: " + exception.getMessage());
+    }
+  }
+
+  // Will be merged into ExceptionB when class merging is enabled.
+  public static class ExceptionA extends Exception {
+    public ExceptionA(String message) {
+      super(message);
+    }
+  }
+
+  public static class ExceptionB extends ExceptionA {
+    public ExceptionB(String message) {
+      super(message);
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 1cc5b87..f9e6d52 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
 -keep public class classmerging.Test {
   public static void main(...);
 }
+-keep public class classmerging.ExceptionTest {
+  public static void main(...);
+}
 
 # TODO(herhut): Consider supporting merging of inner-class attributes.
 # -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fbf2d02..5200a09 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -151,6 +151,11 @@
     return builder.build();
   }
 
+  /** Build an AndroidApp from the specified program files. */
+  protected AndroidApp readProgramFiles(Path... programFiles) throws IOException {
+    return AndroidApp.builder().addProgramFiles(programFiles).build();
+  }
+
   /**
    * Create a temporary JAR file containing the specified test classes.
    */
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 d5d2c39..69f925c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,29 +3,34 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.classmerging;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 
-public class ClassMergingTest {
+public class ClassMergingTest extends TestBase {
 
+  private static final Path CF_DIR =
+      Paths.get(ToolHelper.BUILD_DIR).resolve("classes/examples/classmerging");
   private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
       .resolve("classmerging.jar");
   private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
@@ -33,17 +38,14 @@
   private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
       .resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
   private void configure(InternalOptions options) {
     options.enableClassMerging = true;
     options.enableClassInlining = false;
+    options.enableMinification = false;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, ProguardRuleParserException, ExecutionException,
-      CompilationFailedException {
+      throws IOException, ExecutionException, CompilationFailedException {
     ToolHelper.runR8(
         R8Command.builder()
             .setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
@@ -73,13 +75,12 @@
       assertFalse(inspector.clazz(candidate).isPresent());
     }
     assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
-    assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
     assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
     assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
   }
 
   @Test
-  public void testClassesShouldNotMerged() throws Exception {
+  public void testClassesHaveNotBeenMerged() throws Exception {
     runR8(DONT_OPTIMIZE, null);
     for (String candidate : CAN_BE_MERGED) {
       assertTrue(inspector.clazz(candidate).isPresent());
@@ -100,4 +101,39 @@
     assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
   }
 
+  // If an exception class A is merged into another exception class B, then all exception tables
+  // should be updated, and class A should be removed entirely.
+  @Test
+  public void testExceptionTables() throws Exception {
+    String main = "classmerging.ExceptionTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("ExceptionTest.class"),
+          CF_DIR.resolve("ExceptionTest$ExceptionA.class"),
+          CF_DIR.resolve("ExceptionTest$ExceptionB.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of("classmerging.ExceptionTest", "classmerging.ExceptionTest$ExceptionB");
+    runTest(main, programFiles, preservedClassNames);
+  }
+
+  private void runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
+      throws Exception {
+    AndroidApp input = readProgramFiles(programFiles);
+    AndroidApp output = compileWithR8(input, EXAMPLE_KEEP, this::configure);
+    DexInspector inspector = new DexInspector(output);
+    // Check that all classes in [preservedClassNames] are in fact preserved.
+    for (String className : preservedClassNames) {
+      assertTrue(
+          "Class " + className + " should be present", inspector.clazz(className).isPresent());
+    }
+    // Check that all other classes have been removed.
+    for (FoundClassSubject classSubject : inspector.allClasses()) {
+      String className = classSubject.getDexClass().toSourceString();
+      assertTrue(
+          "Class " + className + " should be absent", preservedClassNames.contains(className));
+    }
+    // Check that the R8-generated code produces the same result as D8-generated code.
+    assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+  }
 }
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 4214a67..d6e3612 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -38,6 +38,7 @@
 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;
@@ -652,7 +653,7 @@
                 code);
         IRCode ir = code.buildIR(method, null, options, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
-        method.setCode(ir, allocator, options);
+        method.setCode(ir, GraphLense.getIdentityLense(), allocator, options);
         directMethods[i] = method;
       }
       builder.addProgramClass(