Merge "Ensure pretended members indeed do not exist while applying mappings."
diff --git a/build.gradle b/build.gradle
index e6432e4..f5a3337 100644
--- a/build.gradle
+++ b/build.gradle
@@ -478,13 +478,16 @@
     }
 }
 
-static configureRelocations(ShadowJar task) {
+static mergeServiceFiles(ShadowJar task) {
     // Everything under META-INF is not included by default.
     // Should include before 'relocate' so that the service file path and its content
     // are properly relocated as well.
     task.mergeServiceFiles {
         include 'META-INF/services/*'
     }
+}
+
+static configureRelocations(ShadowJar task) {
     task.relocate('com.google.common', 'com.android.tools.r8.com.google.common')
     task.relocate('com.google.gson', 'com.android.tools.r8.com.google.gson')
     task.relocate('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty')
@@ -500,18 +503,45 @@
 
 task repackageDeps(type: ShadowJar) {
     configurations = [project.configurations.compile]
-    configureRelocations(it)
+    mergeServiceFiles(it)
+    if (!project.hasProperty('lib_no_relocate')) {
+        configureRelocations(it)
+    }
     exclude { it.getRelativePath().getPathString() == "module-info.class" }
     exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
     baseName 'deps'
 }
 
+task repackageDepsForLib(type: ShadowJar) {
+    configurations = [project.configurations.compile]
+    mergeServiceFiles(it)
+    configureRelocations(it)
+    exclude { it.getRelativePath().getPathString() == "module-info.class" }
+    exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
+    baseName 'r8lib_deps'
+}
+
 task repackageSources(type: ShadowJar) {
     from sourceSets.main.output
-    configureRelocations(it)
+    mergeServiceFiles(it)
+    if (!project.hasProperty('lib_no_relocate')) {
+        configureRelocations(it)
+    }
     baseName 'sources'
 }
 
+task R8libWithDeps(type: ShadowJar) {
+    from consolidatedLicense.outputs.files
+    baseName 'r8lib_with_deps'
+    classifier = null
+    version = null
+    manifest {
+        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
+    }
+    from repackageSources.outputs.files
+    from repackageDepsForLib.outputs.files
+}
+
 task R8(type: ShadowJar) {
     from consolidatedLicense.outputs.files
     baseName 'r8'
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 297d6df..34a3a93 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
 import java.nio.file.Path;
@@ -36,7 +35,7 @@
     programConsumer = null;
     mode = null;
     minApiLevel = 0;
-    reporter = new Reporter(new DefaultDiagnosticsHandler());
+    reporter = new Reporter();
     enableDesugaring = true;
     optimizeMultidexForLinearAlloc = false;
   }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 964f699..2441034 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -38,20 +37,20 @@
     }
   }
 
-  private static class DefaultD8DiagnosticsHandler extends DefaultDiagnosticsHandler {
+  private static class DefaultD8DiagnosticsHandler implements DiagnosticsHandler {
 
     @Override
     public void error(Diagnostic error) {
       if (error instanceof DexFileOverflowDiagnostic) {
         DexFileOverflowDiagnostic overflowDiagnostic = (DexFileOverflowDiagnostic) error;
         if (!overflowDiagnostic.hasMainDexSpecification()) {
-          super.error(
+          DiagnosticsHandler.super.error(
               new StringDiagnostic(
                   overflowDiagnostic.getDiagnosticMessage() + ". Try supplying a main-dex list"));
           return;
         }
       }
-      super.error(error);
+      DiagnosticsHandler.super.error(error);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index 2d06da8..ec7bc53 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -199,7 +198,7 @@
     this.factory = new DexItemFactory();
     this.mainDexKeepRules = ImmutableList.of();
     this.mainDexListConsumer = null;
-    this.reporter = new Reporter(new DefaultDiagnosticsHandler());
+    this.reporter = new Reporter();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec863ee..b7c538f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -352,7 +352,7 @@
                   .rewrittenWithLense(application.asDirect(), appView.graphLense()));
           timing.end();
         }
-        appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
+        appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness, options).run());
         if (options.enableVerticalClassMerging) {
           timing.begin("ClassMerger");
           VerticalClassMerger verticalClassMerger =
@@ -476,21 +476,19 @@
 
       ProguardMapSupplier proguardMapSupplier;
 
-      if (options.lineNumberOptimization != LineNumberOptimization.OFF) {
-        timing.begin("Line number remapping");
-        ClassNameMapper classNameMapper =
-            LineNumberOptimizer.run(
-                application,
-                appView.graphLense(),
-                namingLens,
-                options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
-        timing.end();
-        proguardMapSupplier =
-            ProguardMapSupplier.fromClassNameMapper(classNameMapper, options.minApiLevel);
-      } else {
-        proguardMapSupplier =
-            ProguardMapSupplier.fromNamingLens(namingLens, application, options.minApiLevel);
-      }
+      timing.begin("Line number remapping");
+      // When line number optimization is turned off the identity mapping for line numbers is
+      // used. We still run the line number optimizer to collect line numbers and inline frame
+      // information for the mapping file.
+      ClassNameMapper classNameMapper =
+          LineNumberOptimizer.run(
+              application,
+              appView.graphLense(),
+              namingLens,
+              options.lineNumberOptimization == LineNumberOptimization.OFF);
+      timing.end();
+      proguardMapSupplier =
+          ProguardMapSupplier.fromClassNameMapper(classNameMapper, options.minApiLevel);
 
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 78a9d3f..7b24770 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -58,21 +57,21 @@
   @Keep
   public static class Builder extends BaseCompilerCommand.Builder<R8Command, Builder> {
 
-    private static class DefaultR8DiagnosticsHandler extends DefaultDiagnosticsHandler {
+    private static class DefaultR8DiagnosticsHandler implements DiagnosticsHandler {
 
       @Override
       public void error(Diagnostic error) {
         if (error instanceof DexFileOverflowDiagnostic) {
           DexFileOverflowDiagnostic overflowDiagnostic = (DexFileOverflowDiagnostic) error;
           if (!overflowDiagnostic.hasMainDexSpecification()) {
-            super.error(
+            DiagnosticsHandler.super.error(
                 new StringDiagnostic(
                     overflowDiagnostic.getDiagnosticMessage()
                         + ". Try supplying a main-dex list or main-dex rules"));
             return;
           }
         }
-        super.error(error);
+        DiagnosticsHandler.super.error(error);
       }
     }
 
@@ -651,6 +650,9 @@
 
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
     if (internal.debug) {
+      internal.proguardConfiguration.getKeepAttributes().lineNumberTable = true;
+      internal.proguardConfiguration.getKeepAttributes().localVariableTable = true;
+      internal.proguardConfiguration.getKeepAttributes().localVariableTypeTable = true;
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.enableInlining = false;
       internal.enableClassInlining = false;
diff --git a/src/main/java/com/android/tools/r8/ReadKeepFile.java b/src/main/java/com/android/tools/r8/ReadKeepFile.java
index 07d5044..c953c8d 100644
--- a/src/main/java/com/android/tools/r8/ReadKeepFile.java
+++ b/src/main/java/com/android/tools/r8/ReadKeepFile.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
 import java.nio.file.Paths;
@@ -22,8 +21,7 @@
   private void readProguardKeepFile(String fileName) {
     System.out.println("  - reading " + fileName);
     timing.begin("Reading " + fileName);
-    new ProguardConfigurationParser(new DexItemFactory(),
-        new Reporter(new DefaultDiagnosticsHandler()))
+    new ProguardConfigurationParser(new DexItemFactory(), new Reporter())
         .parse(Paths.get(fileName));
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index d640a30..83f2e8e 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -52,23 +52,23 @@
       return types.iterator().next();
     }
     Iterator<DexType> iterator = types.iterator();
-    TypeLatticeElement join = getLatticeElement(iterator.next());
+    TypeLatticeElement result = getLatticeElement(iterator.next());
     while (iterator.hasNext()) {
-      join = TypeLatticeElement.join(appInfo, join, getLatticeElement(iterator.next()));
+      result = result.join(getLatticeElement(iterator.next()), appInfo);
     }
     // All types are reference types so the join is either a class or an array.
-    if (join.isClassType()) {
-      return join.asClassTypeLatticeElement().getClassType();
-    } else if (join.isArrayType()) {
-      return join.asArrayTypeLatticeElement().getArrayType();
+    if (result.isClassType()) {
+      return result.asClassTypeLatticeElement().getClassType();
+    } else if (result.isArrayType()) {
+      return result.asArrayTypeLatticeElement().getArrayType();
     }
-    throw new CompilationError("Unexpected join " + join + " of types: " +
+    throw new CompilationError("Unexpected join " + result + " of types: " +
         String.join(", ",
             types.stream().map(DexType::toSourceString).collect(Collectors.toList())));
   }
 
   private TypeLatticeElement getLatticeElement(DexType type) {
-    return TypeLatticeElement.fromDexType(type, appInfo, true);
+    return TypeLatticeElement.fromDexType(type, true, appInfo);
   }
 
   public Map<Value, DexType> computeVerificationTypes() {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index d762646..657e029 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -59,7 +59,6 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
@@ -97,8 +96,9 @@
   }
 
   private static DexSection[] parseMapFrom(DexReader dexReader) {
-    DexParser dexParser = new DexParser(dexReader,
-        ClassKind.PROGRAM, new DexItemFactory(), new DefaultDiagnosticsHandler());
+    DexParser dexParser =
+        new DexParser(
+            dexReader, ClassKind.PROGRAM, new DexItemFactory(), new DiagnosticsHandler() {});
     return dexParser.dexSections;
   }
 
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index 321482e..f83012f 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureClassMapping;
@@ -87,7 +86,7 @@
 
   @Keep
   public static final class Options {
-    private final DiagnosticsHandler diagnosticsHandler = new DefaultDiagnosticsHandler();
+    private final DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
     private List<String> inputArchives = new ArrayList<>();
     private List<FeatureJar> featureJars = new ArrayList<>();
     private List<String> baseJars = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 3df31fd..75ce373 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -412,6 +412,14 @@
     return null;
   }
 
+  public boolean isSerializable(AppInfo appInfo) {
+    return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
+  }
+
+  public boolean isExternalizable(AppInfo appInfo) {
+    return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.externalizableType);
+  }
+
   public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
     return Arrays.stream(staticFields())
         .anyMatch(field -> !field.getStaticValue().mayTriggerAllocation());
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 4b52a9e..0da9ead 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -95,17 +95,6 @@
     this.debugInfo = debugInfo;
   }
 
-  public boolean hasDebugPositions() {
-    if (debugInfo != null) {
-      for (DexDebugEvent event : debugInfo.events) {
-        if (event instanceof DexDebugEvent.Default) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
   public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
     if (debugInfo == null) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 1264a26..779678d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -47,7 +47,7 @@
       writer.putUleb128(delta);
     }
 
-    AdvancePC(int delta) {
+    public AdvancePC(int delta) {
       this.delta = delta;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index fa856cb..6c6fda4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -206,7 +206,10 @@
     if (localsChanged()) {
       assert emittedPc != pc;
       int pcDelta = emittedPc == NO_PC_INFO ? pc : pc - emittedPc;
-      events.add(factory.createAdvancePC(pcDelta));
+      assert pcDelta > 0 || emittedPc == NO_PC_INFO;
+      if (pcDelta > 0) {
+        events.add(factory.createAdvancePC(pcDelta));
+      }
       emittedPc = pc;
       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
       pendingLocalChanges = false;
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 f2cc53f..22a771b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -401,12 +401,6 @@
     }
   }
 
-  public boolean hasDebugPositions() {
-    checkIfObsolete();
-    assert code != null && code.isDexCode();
-    return code.asDexCode().hasDebugPositions();
-  }
-
   public int getClassFileVersion() {
     checkIfObsolete();
     assert classFileVersion >= 0;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 7254577..10e960c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -217,6 +217,7 @@
   public final LongMethods longMethods = new LongMethods();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final ClassMethods classMethods = new ClassMethods();
+  public final EnumMethods enumMethods = new EnumMethods();
   public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
       new PrimitiveTypesBoxedTypeFields();
   public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
@@ -256,6 +257,7 @@
   public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
+  public final DexType externalizableType = createType("Ljava/io/Externalizable;");
   public final DexType comparableType = createType("Ljava/lang/Comparable;");
 
   public final DexMethod metafactoryMethod =
@@ -407,6 +409,20 @@
     }
   }
 
+  public class EnumMethods {
+
+    public DexMethod valueOf;
+
+    private EnumMethods() {
+      valueOf =
+          createMethod(
+              enumDescriptor,
+              valueOfMethodName,
+              enumDescriptor,
+              new DexString[] {classDescriptor, stringDescriptor});
+    }
+  }
+
   /**
    * All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
    * for the primitive type.
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index fe63f46..138e07e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -37,6 +37,9 @@
    */
   private Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
 
+  // Caching what interfaces this type is implementing. This includes super-interface hierarchy.
+  private Set<DexType> implementedInterfaces;
+
   DexType(DexString descriptor) {
     assert !descriptor.toString().contains(".");
     this.descriptor = descriptor;
@@ -234,9 +237,12 @@
    * @return a set of interfaces of {@link DexType}.
    */
   public Set<DexType> implementedInterfaces(AppInfo appInfo) {
-    Set<DexType> interfaces = Sets.newIdentityHashSet();
-    implementedInterfaces(appInfo, interfaces);
-    return interfaces;
+    if (implementedInterfaces == null) {
+      Set<DexType> interfaces = Sets.newIdentityHashSet();
+      implementedInterfaces(appInfo, interfaces);
+      implementedInterfaces = interfaces;
+    }
+    return implementedInterfaces;
   }
 
   private void implementedInterfaces(AppInfo appInfo, Set<DexType> interfaces) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 400e5ba..0f0b5e5 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static org.objectweb.asm.ClassReader.SKIP_CODE;
+import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
 import static org.objectweb.asm.Opcodes.ASM6;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -93,9 +95,18 @@
     input.reset();
 
     ClassReader reader = new ClassReader(input);
+
+    int parsingOptions = SKIP_FRAMES | SKIP_CODE;
+
+    // If the source-file and source-debug-extension attributes are not kept we can skip all debug
+    // related attributes when parsing the class structure.
+    ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
+    if (!keep.sourceFile && !keep.sourceDebugExtension) {
+      parsingOptions |= SKIP_DEBUG;
+    }
     reader.accept(
         new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
-        SKIP_FRAMES | SKIP_CODE);
+        parsingOptions);
   }
 
   private static int cleanAccessFlags(int access) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index b978408..0668d05 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.PrintWriter;
@@ -163,7 +164,7 @@
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
-    if (!options.debug || options.testing.removeLocalsTable) {
+    if (!options.debug || !options.proguardConfiguration.getKeepAttributes().localVariableTable) {
       node.localVariables.clear();
     }
     JarSourceCode source =
@@ -241,9 +242,16 @@
   }
 
   private void parseCode(ReparseContext context, boolean useJsrInliner) {
+    // If the keep attributes do not specify keeping LocalVariableTable, LocalVariableTypeTable or
+    // LineNumberTable, then we can skip parsing all the debug related attributes during code read.
+    int parsingOptions = ClassReader.SKIP_FRAMES;
+    ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
+    if (!keep.localVariableTable && !keep.localVariableTypeTable && !keep.lineNumberTable) {
+      parsingOptions |= ClassReader.SKIP_DEBUG;
+    }
     SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
     try {
-      new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+      new ClassReader(context.classCache).accept(classVisitor, parsingOptions);
     } catch (Exception exception) {
       throw new CompilationError(
           "Unable to parse method `" + method.toSourceString() + "`", exception);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index 6b368b3..128e640 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -52,7 +52,7 @@
 
   @Override
   public TypeLatticeElement arrayGet(AppInfo appInfo) {
-    return fromDexType(getArrayElementType(appInfo.dexItemFactory), appInfo, true);
+    return fromDexType(getArrayElementType(appInfo.dexItemFactory), true, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index b8988c1..9711079 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -84,12 +84,12 @@
         TypeLatticeElement derived;
         if (argumentsSeen < 0) {
           // Receiver
-          derived = fromDexType(encodedMethod.method.holder, appInfo,
+          derived = fromDexType(encodedMethod.method.holder,
               // Now we try inlining even when the receiver could be null.
-              encodedMethod != context);
+              encodedMethod != context, appInfo);
         } else {
           DexType argType = encodedMethod.method.proto.parameters.values[argumentsSeen];
-          derived = fromDexType(argType, appInfo, true);
+          derived = fromDexType(argType, true, appInfo);
         }
         argumentsSeen++;
         updateTypeOfValue(outValue, derived);
@@ -149,8 +149,7 @@
 
   private TypeLatticeElement computePhiType(Phi phi) {
     // Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
-    return TypeLatticeElement.join(
-        appInfo, phi.getOperands().stream().map(Value::getTypeLattice));
+    return TypeLatticeElement.join(phi.getOperands().stream().map(Value::getTypeLattice), appInfo);
   }
 
   public static DexType getRefinedReceiverType(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index a09039c..b63dbb2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Value;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Streams;
 import java.util.ArrayDeque;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -81,57 +82,55 @@
   /**
    * Computes the least upper bound of the current and the other elements.
    *
+   * @param other {@link TypeLatticeElement} to join.
    * @param appInfo {@link AppInfo}.
-   * @param l1 {@link TypeLatticeElement} to join.
-   * @param l2 {@link TypeLatticeElement} to join.
-   * @return {@link TypeLatticeElement}, a least upper bound of {@param l1} and {@param l2}.
+   * @return {@link TypeLatticeElement}, a least upper bound of {@param this} and {@param other}.
    */
-  public static TypeLatticeElement join(
-      AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
-    if (l1.isBottom()) {
-      return l2;
+  public TypeLatticeElement join(TypeLatticeElement other, AppInfo appInfo) {
+    if (isBottom()) {
+      return other;
     }
-    if (l2.isBottom()) {
-      return l1;
+    if (other.isBottom()) {
+      return this;
     }
-    if (l1.isTop() || l2.isTop()) {
+    if (isTop() || other.isTop()) {
       return TOP;
     }
-    if (l1.isNull()) {
-      return l2.asNullable();
+    if (isNull()) {
+      return other.asNullable();
     }
-    if (l2.isNull()) {
-      return l1.asNullable();
+    if (other.isNull()) {
+      return asNullable();
     }
-    if (l1.isPrimitive()) {
-      return l2.isPrimitive()
+    if (isPrimitive()) {
+      return other.isPrimitive()
           ? PrimitiveTypeLatticeElement.join(
-              l1.asPrimitiveTypeLatticeElement(), l2.asPrimitiveTypeLatticeElement())
+              asPrimitiveTypeLatticeElement(), other.asPrimitiveTypeLatticeElement())
           : TOP;
     }
-    if (l2.isPrimitive()) {
-      // By the above case, !(l1.isPrimitive())
+    if (other.isPrimitive()) {
+      // By the above case, !(isPrimitive())
       return TOP;
     }
-    // From now on, l1 and l2 are reference types, but might be imprecise yet.
-    assert l1.isReference() && l2.isReference();
-    if (!l1.isPreciseType() || !l2.isPreciseType()) {
-      if (l1.isReferenceInstance()) {
-        return l1;
+    // From now on, this and other are reference types, but might be imprecise yet.
+    assert isReference() && other.isReference();
+    if (!isPreciseType() || !other.isPreciseType()) {
+      if (isReferenceInstance()) {
+        return this;
       }
-      assert l2.isReferenceInstance();
-      return l2;
+      assert other.isReferenceInstance();
+      return other;
     }
-    // From now on, l1 and l2 are precise reference types, i.e., either ArrayType or ClassType.
-    boolean isNullable = l1.isNullable() || l2.isNullable();
-    if (l1.getClass() != l2.getClass()) {
+    // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
+    boolean isNullable = isNullable() || other.isNullable();
+    if (getClass() != other.getClass()) {
       return objectClassType(appInfo, isNullable);
     }
-    // From now on, l1.getClass() == l2.getClass()
-    if (l1.isArrayType()) {
-      assert l2.isArrayType();
-      ArrayTypeLatticeElement a1 = l1.asArrayTypeLatticeElement();
-      ArrayTypeLatticeElement a2 = l2.asArrayTypeLatticeElement();
+    // From now on, getClass() == other.getClass()
+    if (isArrayType()) {
+      assert other.isArrayType();
+      ArrayTypeLatticeElement a1 = asArrayTypeLatticeElement();
+      ArrayTypeLatticeElement a2 = other.asArrayTypeLatticeElement();
       // Identical types are the same elements
       if (a1.getArrayType() == a2.getArrayType()) {
         return a1.isNullable() ? a1 : a2;
@@ -166,10 +165,10 @@
       DexType arrayTypeLub = appInfo.dexItemFactory.createArrayType(a1Nesting, lub);
       return new ArrayTypeLatticeElement(arrayTypeLub, isNullable);
     }
-    if (l1.isClassType()) {
-      assert l2.isClassType();
-      ClassTypeLatticeElement c1 = l1.asClassTypeLatticeElement();
-      ClassTypeLatticeElement c2 = l2.asClassTypeLatticeElement();
+    if (isClassType()) {
+      assert other.isClassType();
+      ClassTypeLatticeElement c1 = asClassTypeLatticeElement();
+      ClassTypeLatticeElement c2 = other.asClassTypeLatticeElement();
       DexType lubType =
           c1.getClassType().computeLeastUpperBoundOfClasses(appInfo, c2.getClassType());
       return new ClassTypeLatticeElement(lubType, isNullable,
@@ -258,47 +257,43 @@
   }
 
   public static BinaryOperator<TypeLatticeElement> joiner(AppInfo appInfo) {
-    return (l1, l2) -> join(appInfo, l1, l2);
+    return (l1, l2) -> l1.join(l2, appInfo);
   }
 
-  public static TypeLatticeElement join(AppInfo appInfo, Stream<TypeLatticeElement> types) {
+  public static TypeLatticeElement join(Stream<TypeLatticeElement> types, AppInfo appInfo) {
     BinaryOperator<TypeLatticeElement> joiner = joiner(appInfo);
     return types.reduce(BottomTypeLatticeElement.getInstance(), joiner, joiner);
   }
 
-  public static TypeLatticeElement join(
-      AppInfo appInfo, Stream<DexType> types, boolean isNullable) {
-    return join(appInfo, types.map(t -> fromDexType(t, appInfo, isNullable)));
+  public static TypeLatticeElement joinTypes(
+      Iterable<DexType> types, boolean isNullable, AppInfo appInfo) {
+    return join(Streams.stream(types).map(t -> fromDexType(t, isNullable, appInfo)), appInfo);
   }
 
   /**
    * Determines the strict partial order of the given {@link TypeLatticeElement}s.
    *
+   * @param other expected to be *strictly* bigger than {@param this}
    * @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
-   * @param l1 subject {@link TypeLatticeElement}
-   * @param l2 expected to be *strict* bigger than {@param l1}
-   * @return {@code true} if {@param l1} is strictly less than {@param l2}.
+   * @return {@code true} if {@param this} is strictly less than {@param other}.
    */
-  public static boolean strictlyLessThan(
-      AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
-    if (l1.equals(l2)) {
+  public boolean strictlyLessThan(TypeLatticeElement other, AppInfo appInfo) {
+    if (equals(other)) {
       return false;
     }
-    TypeLatticeElement lub = join(appInfo, Stream.of(l1, l2));
-    return !l1.equals(lub) && l2.equals(lub);
+    TypeLatticeElement lub = join(other, appInfo);
+    return !equals(lub) && other.equals(lub);
   }
 
   /**
    * Determines the partial order of the given {@link TypeLatticeElement}s.
    *
+   * @param other expected to be bigger than or equal to {@param this}
    * @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
-   * @param l1 subject {@link TypeLatticeElement}
-   * @param l2 expected to be bigger than or equal to {@param l1}
-   * @return {@code true} if {@param l1} is less than or equal to {@param l2}.
+   * @return {@code true} if {@param this} is less than or equal to {@param other}.
    */
-  public static boolean lessThanOrEqual(
-      AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
-    return l1.equals(l2) || strictlyLessThan(appInfo, l1, l2);
+  public boolean lessThanOrEqual(TypeLatticeElement other, AppInfo appInfo) {
+    return equals(other) || strictlyLessThan(other, appInfo);
   }
 
   /**
@@ -404,14 +399,14 @@
   }
 
   public static TypeLatticeElement classClassType(AppInfo appInfo) {
-    return fromDexType(appInfo.dexItemFactory.classType, appInfo, false);
+    return fromDexType(appInfo.dexItemFactory.classType, false, appInfo);
   }
 
   public static TypeLatticeElement stringClassType(AppInfo appInfo) {
-    return fromDexType(appInfo.dexItemFactory.stringType, appInfo, false);
+    return fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
   }
 
-  public static TypeLatticeElement fromDexType(DexType type, AppInfo appInfo, boolean isNullable) {
+  public static TypeLatticeElement fromDexType(DexType type, boolean isNullable, AppInfo appInfo) {
     if (type == DexItemFactory.nullValueType) {
       return NULL;
     }
@@ -483,8 +478,8 @@
   }
 
   public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
-    TypeLatticeElement castTypeLattice = fromDexType(castType, appInfo, isNullable());
-    if (lessThanOrEqual(appInfo, this, castTypeLattice)) {
+    TypeLatticeElement castTypeLattice = fromDexType(castType, isNullable(), appInfo);
+    if (lessThanOrEqual(castTypeLattice, appInfo)) {
       return this;
     }
     return castTypeLattice;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 7caaff9..705bb97 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -366,7 +366,7 @@
     if (downcast != null) {
       Value receiver = invoke.inValues().get(0);
       TypeLatticeElement castTypeLattice = TypeLatticeElement.fromDexType(
-          downcast, appInfo, receiver.getTypeLattice().isNullable());
+          downcast, receiver.getTypeLattice().isNullable(), appInfo);
       CheckCast castInstruction =
           new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
       castInstruction.setPosition(invoke.getPosition());
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 8d461bd..03d718f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -133,12 +133,12 @@
 
     TypeLatticeElement outType = outValue().getTypeLattice();
     TypeLatticeElement castType =
-        TypeLatticeElement.fromDexType(getType(), appInfo, inType.isNullable());
+        TypeLatticeElement.fromDexType(getType(), inType.isNullable(), appInfo);
 
-    if (TypeLatticeElement.lessThanOrEqual(appInfo, inType, castType)) {
+    if (inType.lessThanOrEqual(castType, appInfo)) {
       // Cast can be removed. Check that it is sound to replace all users of the out-value by the
       // in-value.
-      assert TypeLatticeElement.lessThanOrEqual(appInfo, inType, outType);
+      assert inType.lessThanOrEqual(outType, appInfo);
 
       // TODO(b/72693244): Consider checking equivalence. This requires that the types are always
       // as precise as possible, though, meaning that almost all changes to the IR must be followed
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 401656c..7aa9a62 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -105,7 +105,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.classType, appInfo, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.classType, false, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index ffc4359..95aa81c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -91,7 +91,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, appInfo, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, false, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index 81d7b28..d258839 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -91,7 +91,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, appInfo, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, false, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 85fd784..e05a55f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -133,6 +133,6 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, appInfo, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 0c8c372..3d30b82 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -135,7 +135,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(field.type, appInfo, true);
+    return TypeLatticeElement.fromDexType(field.type, true, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d3355b4..44a7a8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -406,7 +406,13 @@
     if (other.getClass() != getClass()) {
       return false;
     }
-    if (!identicalNonValueParts(other)) {
+    // In debug mode or if the instruction can throw we must account for positions, in release mode
+    // we do want to share non-throwing instructions even if their positions differ.
+    if (instructionTypeCanThrow() || allocator.getOptions().debug) {
+      if (!identicalNonValueParts(other)) {
+        return false;
+      }
+    } else if (!identicalNonValueNonPositionParts(other)) {
       return false;
     }
     if (isInvokeDirect() && !asInvokeDirect().sameConstructorReceiverValue(other.asInvoke())) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index b755336..468f3d05 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -280,6 +280,6 @@
     if (returnType.isVoidType()) {
       throw new Unreachable("void methods have no type.");
     }
-    return TypeLatticeElement.fromDexType(returnType, appInfo, true);
+    return TypeLatticeElement.fromDexType(returnType, true, appInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 562b33d..8166f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -105,7 +105,7 @@
         if (targets.get(i) == currentBlock) {
           DexType guard = guards.get(i);
           exceptionTypes.add(
-              guard == dexItemFactory.catchAllType
+              guard == DexItemFactory.catchAllType
                   ? dexItemFactory.throwableType
                   : guard);
         }
@@ -122,8 +122,6 @@
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
     Set<DexType> exceptionTypes = collectExceptionTypes(getBlock(), appInfo.dexItemFactory);
-    return TypeLatticeElement.join(
-        appInfo,
-        exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(t, appInfo, false)));
+    return TypeLatticeElement.joinTypes(exceptionTypes, false, appInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 29b263f..18a60d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -107,7 +107,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(clazz, appInfo, false);
+    return TypeLatticeElement.fromDexType(clazz, false, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 9ef7ed4..e767246 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -145,7 +145,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(field.type, appInfo, true);
+    return TypeLatticeElement.fromDexType(field.type, true, appInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index b471e12..1853e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -680,8 +680,7 @@
     Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
     if (moveExceptionDest >= 0) {
       Set<DexType> exceptionTypes = MoveException.collectExceptionTypes(currentBlock, getFactory());
-      TypeLatticeElement typeLattice = TypeLatticeElement.join(appInfo,
-          exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(t, appInfo, false)));
+      TypeLatticeElement typeLattice = TypeLatticeElement.joinTypes(exceptionTypes, false, appInfo);
       Value out = writeRegister(moveExceptionDest, typeLattice, ThrowingInfo.NO_THROW, null);
       MoveException moveException = new MoveException(out);
       moveException.setPosition(position);
@@ -731,7 +730,7 @@
     DebugLocalInfo local = getOutgoingLocal(register);
     // TODO(b/72693244): Update nullability if this is for building inlinee's IR.
     TypeLatticeElement receiver =
-        TypeLatticeElement.fromDexType(method.method.getHolder(), appInfo, false);
+        TypeLatticeElement.fromDexType(method.method.getHolder(), false, appInfo);
     Value value = writeRegister(register, receiver, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
     value.markAsThis();
@@ -872,7 +871,7 @@
   public void addCheckCast(int value, DexType type) {
     Value in = readRegister(value, ValueType.OBJECT);
     TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(type, appInfo, in.getTypeLattice().isNullable());
+        TypeLatticeElement.fromDexType(type, in.getTypeLattice().isNullable(), appInfo);
     Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
     CheckCast instruction = new CheckCast(out, in, type);
     assert instruction.instructionTypeCanThrow();
@@ -931,7 +930,7 @@
           null /* sourceString */);
     }
     TypeLatticeElement typeLattice =
-        TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, appInfo, false);
+        TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, false, appInfo);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
     add(instruction);
@@ -945,7 +944,7 @@
           null /* sourceString */);
     }
     TypeLatticeElement typeLattice =
-        TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, appInfo, false);
+        TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, false, appInfo);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodType instruction = new ConstMethodType(out, methodType);
     add(instruction);
@@ -1104,7 +1103,7 @@
     MemberType type = MemberType.fromDexType(field.type);
     Value in = readRegister(object, ValueType.OBJECT);
     Value out = writeRegister(
-        dest, TypeLatticeElement.fromDexType(field.type, appInfo, true), ThrowingInfo.CAN_THROW);
+        dest, TypeLatticeElement.fromDexType(field.type, true, appInfo), ThrowingInfo.CAN_THROW);
     out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     InstanceGet instruction = new InstanceGet(type, out, in, field);
     assert instruction.instructionTypeCanThrow();
@@ -1415,7 +1414,7 @@
   public void addNewArrayEmpty(int dest, int size, DexType type) {
     assert type.isArrayType();
     Value in = readRegister(size, ValueType.INT);
-    TypeLatticeElement arrayTypeLattice = TypeLatticeElement.fromDexType(type, appInfo, false);
+    TypeLatticeElement arrayTypeLattice = TypeLatticeElement.fromDexType(type, false, appInfo);
     Value out = writeRegister(dest, arrayTypeLattice, ThrowingInfo.CAN_THROW);
     NewArrayEmpty instruction = new NewArrayEmpty(out, in, type);
     assert instruction.instructionTypeCanThrow();
@@ -1427,7 +1426,7 @@
   }
 
   public void addNewInstance(int dest, DexType type) {
-    TypeLatticeElement instanceType = TypeLatticeElement.fromDexType(type, appInfo, false);
+    TypeLatticeElement instanceType = TypeLatticeElement.fromDexType(type, false, appInfo);
     Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
     NewInstance instruction = new NewInstance(type, out);
     assert instruction.instructionTypeCanThrow();
@@ -1455,7 +1454,7 @@
   public void addStaticGet(int dest, DexField field) {
     MemberType type = MemberType.fromDexType(field.type);
     Value out = writeRegister(
-        dest, TypeLatticeElement.fromDexType(field.type, appInfo, true), ThrowingInfo.CAN_THROW);
+        dest, TypeLatticeElement.fromDexType(field.type, true, appInfo), ThrowingInfo.CAN_THROW);
     out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     StaticGet instruction = new StaticGet(type, out, field);
     assert instruction.instructionTypeCanThrow();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index c773eff..d3b57cd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -46,10 +46,12 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class LensCodeRewriter {
@@ -69,18 +71,24 @@
     this.options = options;
   }
 
-  private Value makeOutValue(Instruction insn, IRCode code) {
+  private Value makeOutValue(
+      Instruction insn,
+      IRCode code,
+      ImmutableSet.Builder<Value> collector) {
     if (insn.outValue() == null) {
       return null;
     } else {
-      return code.createValue(insn.outValue().getTypeLattice(), insn.getLocalInfo());
+      Value newValue = code.createValue(insn.outValue().getTypeLattice(), insn.getLocalInfo());
+      collector.add(newValue);
+      return newValue;
     }
   }
 
   /**
    * Replace type appearances, invoke targets and field accesses with actual definitions.
    */
-  public void rewrite(IRCode code, DexEncodedMethod method) {
+  public Set<Value> rewrite(IRCode code, DexEncodedMethod method) {
+    ImmutableSet.Builder<Value> valueCollector = ImmutableSet.builder();
     ListIterator<BasicBlock> blocks = code.blocks.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
@@ -118,10 +126,7 @@
               handle, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
           if (newHandle != handle) {
             ConstMethodHandle newInstruction =
-                new ConstMethodHandle(
-                    code.createValue(
-                        current.outValue().getTypeLattice(), current.getLocalInfo()),
-                    newHandle);
+                new ConstMethodHandle(makeOutValue(current, code, valueCollector), newHandle);
             iterator.replaceCurrentInstruction(newInstruction);
           }
         } else if (current.isInvokeMethod()) {
@@ -145,8 +150,7 @@
             // Fix up the return type if needed.
             if (actualTarget.proto.returnType != invokedMethod.proto.returnType
                 && newInvoke.outValue() != null) {
-              Value newValue = code.createValue(
-                  newInvoke.outValue().getTypeLattice(), invoke.getLocalInfo());
+              Value newValue = makeOutValue(newInvoke, code, valueCollector);
               newInvoke.outValue().replaceUsers(newValue);
               CheckCast cast =
                   new CheckCast(
@@ -204,60 +208,66 @@
           CheckCast checkCast = current.asCheckCast();
           DexType newType = graphLense.lookupType(checkCast.getType());
           if (newType != checkCast.getType()) {
-            CheckCast newCheckCast =
-                new CheckCast(makeOutValue(checkCast, code), checkCast.object(), newType);
+            CheckCast newCheckCast = new CheckCast(
+                makeOutValue(checkCast, code, valueCollector), checkCast.object(), newType);
             iterator.replaceCurrentInstruction(newCheckCast);
           }
         } else if (current.isConstClass()) {
           ConstClass constClass = current.asConstClass();
           DexType newType = graphLense.lookupType(constClass.getValue());
           if (newType != constClass.getValue()) {
-            ConstClass newConstClass = new ConstClass(makeOutValue(constClass, code), newType);
+            ConstClass newConstClass = new ConstClass(
+                makeOutValue(constClass, code, valueCollector), newType);
             iterator.replaceCurrentInstruction(newConstClass);
           }
         } else if (current.isInstanceOf()) {
           InstanceOf instanceOf = current.asInstanceOf();
           DexType newType = graphLense.lookupType(instanceOf.type());
           if (newType != instanceOf.type()) {
-            InstanceOf newInstanceOf = new InstanceOf(makeOutValue(instanceOf, code),
-                instanceOf.value(), newType);
+            InstanceOf newInstanceOf = new InstanceOf(
+                makeOutValue(instanceOf, code, valueCollector), instanceOf.value(), newType);
             iterator.replaceCurrentInstruction(newInstanceOf);
           }
         } else if (current.isInvokeMultiNewArray()) {
           InvokeMultiNewArray multiNewArray = current.asInvokeMultiNewArray();
           DexType newType = graphLense.lookupType(multiNewArray.getArrayType());
           if (newType != multiNewArray.getArrayType()) {
-            InvokeMultiNewArray newMultiNewArray = new InvokeMultiNewArray(
-                newType, makeOutValue(multiNewArray, code), multiNewArray.inValues());
+            InvokeMultiNewArray newMultiNewArray =
+                new InvokeMultiNewArray(
+                    newType,
+                    makeOutValue(multiNewArray, code, valueCollector),
+                    multiNewArray.inValues());
             iterator.replaceCurrentInstruction(newMultiNewArray);
           }
         } else if (current.isInvokeNewArray()) {
           InvokeNewArray newArray = current.asInvokeNewArray();
           DexType newType = graphLense.lookupType(newArray.getArrayType());
           if (newType != newArray.getArrayType()) {
-            InvokeNewArray newNewArray = new InvokeNewArray(newType, makeOutValue(newArray, code),
-                newArray.inValues());
+            InvokeNewArray newNewArray = new InvokeNewArray(
+                newType, makeOutValue(newArray, code, valueCollector), newArray.inValues());
             iterator.replaceCurrentInstruction(newNewArray);
           }
         } else if (current.isNewArrayEmpty()) {
           NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
           DexType newType = graphLense.lookupType(newArrayEmpty.type);
           if (newType != newArrayEmpty.type) {
-            NewArrayEmpty newNewArray = new NewArrayEmpty(makeOutValue(newArrayEmpty, code),
-                newArrayEmpty.size(), newType);
+            NewArrayEmpty newNewArray = new NewArrayEmpty(
+                makeOutValue(newArrayEmpty, code, valueCollector), newArrayEmpty.size(), newType);
             iterator.replaceCurrentInstruction(newNewArray);
           }
         } else if (current.isNewInstance()) {
           NewInstance newInstance= current.asNewInstance();
           DexType newClazz = graphLense.lookupType(newInstance.clazz);
           if (newClazz != newInstance.clazz) {
-            NewInstance newNewInstance = new NewInstance(newClazz, makeOutValue(newInstance, code));
+            NewInstance newNewInstance = new NewInstance(
+                newClazz, makeOutValue(newInstance, code, valueCollector));
             iterator.replaceCurrentInstruction(newNewInstance);
           }
         }
       }
     }
     assert code.isConsistentSSA();
+    return valueCollector.build();
   }
 
   // If the given invoke is on the form "invoke-direct A.<init>, v0, ..." and the definition of
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 129ed58..22113f8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -277,7 +277,7 @@
     if (lambdaInstanceValue == null) {
       // The out value might be empty in case it was optimized out.
       lambdaInstanceValue = code.createValue(
-          TypeLatticeElement.fromDexType(lambdaClass.type, appInfo, true));
+          TypeLatticeElement.fromDexType(lambdaClass.type, true, appInfo));
     }
 
     // For stateless lambdas we replace InvokeCustom instruction with StaticGet
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index c805494..1b13398 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -337,7 +337,7 @@
 
       // new-instance v0, StringBuilder
       TypeLatticeElement stringBuilderTypeLattice =
-          TypeLatticeElement.fromDexType(factory.stringBuilderType, appInfo, false);
+          TypeLatticeElement.fromDexType(factory.stringBuilderType, false, appInfo);
       Value sbInstance = code.createValue(stringBuilderTypeLattice);
       appendInstruction(new NewInstance(factory.stringBuilderType, sbInstance));
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index f8b85de..93df273 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -297,7 +297,7 @@
       BasicBlock rethrowBlock = BasicBlock.createRethrowBlock(
           code,
           lastSelfRecursiveCall.getPosition(),
-          TypeLatticeElement.fromDexType(guard, appInfo, true));
+          TypeLatticeElement.fromDexType(guard, true, appInfo));
       code.blocks.add(rethrowBlock);
       // Add catch handler to the block containing the last recursive call.
       newBlock.addCatchHandler(rethrowBlock, guard);
@@ -1660,11 +1660,11 @@
       if (inTypeLattice.isPreciseType() || inTypeLattice.isNull()) {
         TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
         TypeLatticeElement castTypeLattice =
-            TypeLatticeElement.fromDexType(castType, appInfo, inTypeLattice.isNullable());
+            TypeLatticeElement.fromDexType(castType, inTypeLattice.isNullable(), appInfo);
 
         assert inTypeLattice.nullElement().lessThanOrEqual(outTypeLattice.nullElement());
 
-        if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
+        if (inTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
           // 1) Trivial cast.
           //   A a = ...
           //   A a' = (A) a;
@@ -1672,7 +1672,7 @@
           //   A < B
           //   A a = ...
           //   B b = (B) a;
-          assert TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, outTypeLattice);
+          assert inTypeLattice.lessThanOrEqual(outTypeLattice, appInfo);
           needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
           removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
         } else {
@@ -3013,7 +3013,7 @@
     DexType javaLangSystemType = dexItemFactory.createType("Ljava/lang/System;");
     DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;");
     Value out = code.createValue(
-        TypeLatticeElement.fromDexType(javaIoPrintStreamType, appInfo, false));
+        TypeLatticeElement.fromDexType(javaIoPrintStreamType, false, appInfo));
 
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 37cfb44..e7b6ca8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -110,14 +110,13 @@
           Value receiver = invoke.getReceiver();
           TypeLatticeElement receiverTypeLattice = receiver.getTypeLattice();
           TypeLatticeElement castTypeLattice =
-              TypeLatticeElement.fromDexType(holderType, appInfo, receiverTypeLattice.isNullable());
+              TypeLatticeElement.fromDexType(holderType, receiverTypeLattice.isNullable(), appInfo);
           // Avoid adding trivial cast and up-cast.
           // We should not use strictlyLessThan(castType, receiverType), which detects downcast,
           // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
           // TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
           if (!receiverTypeLattice.isPreciseType()
-              || !TypeLatticeElement.lessThanOrEqual(
-                  appInfo, receiverTypeLattice, castTypeLattice)) {
+              || !receiverTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
             Value newReceiver = null;
             // If this value is ever downcast'ed to the same holder type before, and that casted
             // value is safely accessible, i.e., the current line is dominated by that cast, use it.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index eed1c47..9d36037 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -108,31 +108,12 @@
         // goto A
         //
         // A: ...y // blockWithNonNullInstruction
-        //
+        boolean split = block.hasCatchHandlers();
         BasicBlock blockWithNonNullInstruction =
-            block.hasCatchHandlers() ? iterator.split(code, blockIterator) : block;
-        // Next, add non-null fake IR, e.g.,
-        // ...x
-        // invoke(rcv, ...)
-        // goto A
-        // ...
-        // A: non_null_rcv <- non-null(rcv)
-        // ...y
-        Value nonNullValue = code.createValue(
-            knownToBeNonNullValue.getTypeLattice(),
-            knownToBeNonNullValue.getLocalInfo());
-        nonNullValueCollector.add(nonNullValue);
-        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
-        nonNull.setPosition(current.getPosition());
-        if (blockWithNonNullInstruction !=  block) {
-          // If we split, add non-null IR on top of the new split block.
-          blockWithNonNullInstruction.listIterator().add(nonNull);
-        } else {
-          // Otherwise, just add it to the current block at the position of the iterator.
-          iterator.add(nonNull);
-        }
-        // Then, replace all users of the original value that are dominated by either the current
-        // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
+            split ? iterator.split(code, blockIterator) : block;
+
+        // Find all users of the original value that are dominated by either the current block
+        // or the new split-off block. Since NPE can be explicitly caught, nullness should be
         // propagated through dominance.
         Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
         Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
@@ -142,14 +123,13 @@
         for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
           dominatedBlocks.add(dominatee);
           InstructionListIterator dominateeIterator = dominatee.listIterator();
-          if (dominatee == blockWithNonNullInstruction) {
-            // In the block with the inserted non null instruction, skip instructions up to and
-            // including the newly inserted instruction.
-            dominateeIterator.nextUntil(instruction -> instruction == nonNull);
+          if (dominatee == blockWithNonNullInstruction && !split) {
+            // In the block where the non null instruction will be inserted, skip instructions up
+            // to and including the insertion point.
+            dominateeIterator.nextUntil(instruction -> instruction == current);
           }
           while (dominateeIterator.hasNext()) {
             Instruction potentialUser = dominateeIterator.next();
-            assert potentialUser != nonNull;
             if (users.contains(potentialUser)) {
               dominatedUsers.add(potentialUser);
             }
@@ -162,8 +142,35 @@
             dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
           }
         }
-        knownToBeNonNullValue.replaceSelectiveUsers(
-            nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+
+        // Only insert non-null instruction if it is ever used.
+        if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+          // Add non-null fake IR, e.g.,
+          // ...x
+          // invoke(rcv, ...)
+          // goto A
+          // ...
+          // A: non_null_rcv <- non-null(rcv)
+          // ...y
+          Value nonNullValue =
+              code.createValue(
+                  knownToBeNonNullValue.getTypeLattice(), knownToBeNonNullValue.getLocalInfo());
+          nonNullValueCollector.add(nonNullValue);
+          NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
+          nonNull.setPosition(current.getPosition());
+          if (blockWithNonNullInstruction != block) {
+            // If we split, add non-null IR on top of the new split block.
+            blockWithNonNullInstruction.listIterator().add(nonNull);
+          } else {
+            // Otherwise, just add it to the current block at the position of the iterator.
+            iterator.add(nonNull);
+          }
+
+          // Replace all users of the original value that are dominated by either the current
+          // block or the new split-off block.
+          knownToBeNonNullValue.replaceSelectiveUsers(
+              nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+        }
       }
 
       // Add non-null on top of the successor block if the current block ends with a null check.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index eb140c4..e6791f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -253,7 +253,8 @@
             changed = true;
             int otherPredIndex = blockToIndex.get(wrapper);
             BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
-            assert Objects.equals(pred.getPosition(), otherPred.getPosition());
+            assert !allocator.getOptions().debug
+                || Objects.equals(pred.getPosition(), otherPred.getPosition());
             pred.clearCatchHandlers();
             pred.getInstructions().clear();
             equivalence.clearComputedHash(pred);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index 538d7be..51da91d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -115,7 +115,7 @@
     NewInstance patchedNewInstance = new NewInstance(
         group.getGroupClassType(),
         context.code.createValue(
-            TypeLatticeElement.fromDexType(newInstance.clazz, context.appInfo, false)));
+            TypeLatticeElement.fromDexType(newInstance.clazz, false, context.appInfo)));
     context.instructions().replaceCurrentInstruction(patchedNewInstance);
   }
 
@@ -160,7 +160,7 @@
     // Since all captured values of non-primitive types are stored in fields of type
     // java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
     TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(fieldType, context.appInfo, false);
+        TypeLatticeElement.fromDexType(fieldType, false, context.appInfo);
     Value newValue = context.code.createValue(castTypeLattice, newInstanceGet.getLocalInfo());
     newInstanceGet.outValue().replaceUsers(newValue);
     CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), fieldType);
@@ -187,7 +187,7 @@
         new StaticGet(
             staticGet.getType(),
             context.code.createValue(
-                TypeLatticeElement.fromDexType(staticGet.getField().type, context.appInfo, true)),
+                TypeLatticeElement.fromDexType(staticGet.getField().type, true, context.appInfo)),
             mapSingletonInstanceField(context.factory, staticGet.getField())));
   }
 
@@ -222,7 +222,7 @@
   private Value createValueForType(CodeProcessor context, DexType returnType) {
     return returnType == context.factory.voidType ? null :
         context.code.createValue(
-            TypeLatticeElement.fromDexType(returnType, context.appInfo, true));
+            TypeLatticeElement.fromDexType(returnType, true, context.appInfo));
   }
 
   private List<Value> mapInitializerArgs(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 669baf9..ac3a8af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -273,7 +273,7 @@
               new StaticGet(
                   MemberType.fromDexType(field.type),
                   code.createValue(
-                      TypeLatticeElement.fromDexType(field.type, classStaticizer.appInfo, true),
+                      TypeLatticeElement.fromDexType(field.type, true, classStaticizer.appInfo),
                       outValue.getLocalInfo()),
                   field
               )
@@ -303,7 +303,7 @@
           Value newOutValue = method.proto.returnType.isVoidType() ? null
               : code.createValue(
                   TypeLatticeElement.fromDexType(
-                      method.proto.returnType, classStaticizer.appInfo, true),
+                      method.proto.returnType, true, classStaticizer.appInfo),
                   outValue == null ? null : outValue.getLocalInfo());
           it.replaceCurrentInstruction(
               new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index f45a3de..c47a7bd 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -401,7 +401,7 @@
         builder.append(minifiedRange).append(':');
       }
       builder.append(signature);
-      if (originalRange != null) {
+      if (originalRange != null && !minifiedRange.equals(originalRange)) {
         builder.append(":").append(originalRange);
       }
       builder.append(" -> ").append(renamedName);
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 0b6a84d..3c300b6 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -12,8 +12,10 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collections;
@@ -27,12 +29,15 @@
 
   private final AppInfoWithLiveness appInfo;
   private final GraphLense lense;
+  private final InternalOptions options;
+
   private final MemberRebindingLense.Builder builder;
 
-  public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
+  public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
     assert appView.graphLense().isContextFreeForMethods();
     this.appInfo = appView.appInfo();
     this.lense = appView.graphLense();
+    this.options = options;
     this.builder = MemberRebindingLense.builder(appInfo);
   }
 
@@ -114,8 +119,8 @@
     return appInfo.resolveMethod(method.getHolder(), method).asResultOfResolve();
   }
 
-  private void computeMethodRebinding(Set<DexMethod> methods,
-      Function<DexMethod, DexEncodedMethod> lookupTarget) {
+  private void computeMethodRebinding(
+      Set<DexMethod> methods, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) {
     for (DexMethod method : methods) {
       // We can safely ignore array types, as the corresponding methods are defined in a library.
       if (!method.getHolder().isClassType()) {
@@ -130,33 +135,107 @@
       // Rebind to the lowest library class or program class.
       if (target != null && target.method != method) {
         DexClass targetClass = appInfo.definitionFor(target.method.holder);
+
+        // In Java bytecode, it is only possible to target interface methods that are in one of
+        // the immediate super-interfaces via a super-invocation (see IndirectSuperInterfaceTest).
+        // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
+        // bridge method when we are about to rebind to an interface method that is not the
+        // original target.
+        if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
+          target =
+              insertBridgeForInterfaceMethod(
+                  method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+        }
+
         // If the target class is not public but the targeted method is, we might run into
         // visibility problems when rebinding.
-        if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) {
-          // If the original class is public and this method is public, it might have been called
-          // from anywhere, so we need a bridge. Likewise, if the original is in a different
-          // package, we might need a bridge, too.
-          String packageDescriptor =
-              originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
-          if (packageDescriptor == null
-              || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
-            DexProgramClass bridgeHolder = findBridgeMethodHolder(originalClass, targetClass,
-                packageDescriptor);
-            assert bridgeHolder != null;
-            DexEncodedMethod bridgeMethod =
-                target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
-            bridgeHolder.addMethod(bridgeMethod);
-            assert lookupTarget.apply(method) == bridgeMethod;
-            target = bridgeMethod;
-          }
+        if (mayNeedBridgeForVisibility(target, targetClass)) {
+          target =
+              insertBridgeForVisibilityIfNeeded(
+                  method, target, originalClass, targetClass, lookupTarget);
         }
+
         builder.map(method, lense.lookupMethod(validTargetFor(target.method, method)));
       }
     }
   }
 
-  private DexProgramClass findBridgeMethodHolder(DexClass originalClass, DexClass targetClass,
-      String packageDescriptor) {
+  private boolean needsBridgeForInterfaceMethod(
+      DexClass originalClass, DexClass targetClass, Type invokeType) {
+    return options.isGeneratingClassFiles()
+        && invokeType == Type.SUPER
+        && targetClass != originalClass
+        && targetClass.accessFlags.isInterface();
+  }
+
+  private DexEncodedMethod insertBridgeForInterfaceMethod(
+      DexMethod method,
+      DexEncodedMethod target,
+      DexProgramClass originalClass,
+      DexClass targetClass,
+      Function<DexMethod, DexEncodedMethod> lookupTarget) {
+    // If `targetClass` is a class, then insert the bridge method on the upper-most super class that
+    // implements the interface. Otherwise, if it is an interface, then insert the bridge method
+    // directly on the interface (because that interface must be the immediate super type, assuming
+    // that the super-invocation is not broken in advance).
+    //
+    // Note that, to support compiling from DEX to CF, we would need to rewrite the targets of
+    // invoke-super instructions that hit indirect interface methods such that they always target
+    // a method in an immediate super-interface, since this works on Art but not on the JVM.
+    DexProgramClass bridgeHolder =
+        findHolderForInterfaceMethodBridge(originalClass, targetClass.type);
+    assert bridgeHolder != null;
+    assert bridgeHolder != targetClass;
+    DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+    bridgeHolder.addMethod(bridgeMethod);
+    assert lookupTarget.apply(method) == bridgeMethod;
+    return bridgeMethod;
+  }
+
+  private DexProgramClass findHolderForInterfaceMethodBridge(DexProgramClass clazz, DexType iface) {
+    if (clazz.accessFlags.isInterface()) {
+      return clazz;
+    }
+    DexClass superClass = appInfo.definitionFor(clazz.superType);
+    if (superClass == null
+        || superClass.isLibraryClass()
+        || !superClass.type.isSubtypeOf(iface, appInfo)) {
+      return clazz;
+    }
+    return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
+  }
+
+  private boolean mayNeedBridgeForVisibility(DexEncodedMethod target, DexClass targetClass) {
+    return !targetClass.accessFlags.isPublic() && target.accessFlags.isPublic();
+  }
+
+  private DexEncodedMethod insertBridgeForVisibilityIfNeeded(
+      DexMethod method,
+      DexEncodedMethod target,
+      DexClass originalClass,
+      DexClass targetClass,
+      Function<DexMethod, DexEncodedMethod> lookupTarget) {
+    // If the original class is public and this method is public, it might have been called
+    // from anywhere, so we need a bridge. Likewise, if the original is in a different
+    // package, we might need a bridge, too.
+    String packageDescriptor =
+        originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
+    if (packageDescriptor == null
+        || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
+      DexProgramClass bridgeHolder =
+          findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
+      assert bridgeHolder != null;
+      DexEncodedMethod bridgeMethod =
+          target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+      bridgeHolder.addMethod(bridgeMethod);
+      assert lookupTarget.apply(method) == bridgeMethod;
+      return bridgeMethod;
+    }
+    return target;
+  }
+
+  private DexProgramClass findHolderForVisibilityBridge(
+      DexClass originalClass, DexClass targetClass, String packageDescriptor) {
     if (originalClass == targetClass || originalClass.isLibraryClass()) {
       return null;
     }
@@ -164,12 +243,12 @@
     // Recurse through supertype chain.
     if (originalClass.superType.isSubtypeOf(targetClass.type, appInfo)) {
       DexClass superClass = appInfo.definitionFor(originalClass.superType);
-      newHolder = findBridgeMethodHolder(superClass, targetClass, packageDescriptor);
+      newHolder = findHolderForVisibilityBridge(superClass, targetClass, packageDescriptor);
     } else {
       for (DexType iface : originalClass.interfaces.values) {
         if (iface.isSubtypeOf(targetClass.type, appInfo)) {
           DexClass interfaceClass = appInfo.definitionFor(iface);
-          newHolder = findBridgeMethodHolder(interfaceClass, targetClass, packageDescriptor);
+          newHolder = findHolderForVisibilityBridge(interfaceClass, targetClass, packageDescriptor);
         }
       }
     }
@@ -234,15 +313,15 @@
 
   public GraphLense run() {
     // Virtual invokes are on classes, so use class resolution.
-    computeMethodRebinding(appInfo.virtualInvokes, this::classLookup);
+    computeMethodRebinding(appInfo.virtualInvokes, this::classLookup, Type.VIRTUAL);
     // Interface invokes are always on interfaces, so use interface resolution.
-    computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup);
+    computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup, Type.INTERFACE);
     // Super invokes can be on both kinds, decide using the holder class.
-    computeMethodRebinding(appInfo.superInvokes, this::anyLookup);
+    computeMethodRebinding(appInfo.superInvokes, this::anyLookup, Type.SUPER);
     // Direct invokes (private/constructor) can also be on both kinds.
-    computeMethodRebinding(appInfo.directInvokes, this::anyLookup);
+    computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
     // Likewise static invokes.
-    computeMethodRebinding(appInfo.staticInvokes, this::anyLookup);
+    computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
 
     computeFieldRebinding(
         mergeFieldAccessContexts(appInfo.staticFieldReads, appInfo.staticFieldWrites),
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index bfb2b95..e0cf415 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -68,6 +68,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -253,7 +254,6 @@
 
   private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
     items.entrySet().forEach(this::enqueueRootItem);
-    pinnedItems.addAll(items.keySet());
   }
 
   private void enqueueRootItem(Entry<DexDefinition, ProguardKeepRule> root) {
@@ -261,17 +261,25 @@
   }
 
   private void enqueueRootItem(DexDefinition item, ProguardKeepRule rule) {
-    KeepReason reason = KeepReason.dueToKeepRule(rule);
+    enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
+  }
+
+  private void enqueueRootItem(DexDefinition item, KeepReason reason) {
     if (item.isDexClass()) {
       DexClass clazz = item.asDexClass();
       workList.add(Action.markInstantiated(clazz, reason));
-      if (forceProguardCompatibility && clazz.hasDefaultInitializer()) {
-        ProguardKeepRule compatRule =
+      if (clazz.hasDefaultInitializer()) {
+        if (forceProguardCompatibility) {
+          ProguardKeepRule compatRule =
             ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
-        proguardCompatibilityWorkList.add(
-            Action.markMethodLive(
-                clazz.getDefaultInitializer(),
-                KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
+          proguardCompatibilityWorkList.add(
+              Action.markMethodLive(
+                  clazz.getDefaultInitializer(),
+                  KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
+        }
+        if (clazz.isExternalizable(appInfo)) {
+          workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+        }
       }
     } else if (item.isDexEncodedField()) {
       workList.add(Action.markFieldKept(item.asDexEncodedField(), reason));
@@ -280,6 +288,19 @@
     } else {
       throw new IllegalArgumentException(item.toString());
     }
+    pinnedItems.add(item);
+  }
+
+  private void enqueueFirstNonSerializableClassInitializer(DexClass clazz, KeepReason reason) {
+    assert clazz.isProgramClass() && clazz.isSerializable(appInfo);
+    // Clime up the class hierarchy. Break out if the definition is not found, or hit the library
+    // classes, which are kept by definition, or encounter the first non-serializable class.
+    while (clazz != null && clazz.isProgramClass() && clazz.isSerializable(appInfo)) {
+      clazz = appInfo.definitionFor(clazz.superType);
+    }
+    if (clazz != null && clazz.isProgramClass() && clazz.hasDefaultInitializer()) {
+      workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+    }
   }
 
   private void enqueueHolderIfDependentNonStaticMember(
@@ -386,6 +407,10 @@
         // Revisit the current method to implicitly add -keep rule for items with reflective access.
         pendingReflectiveUses.add(currentMethod);
       }
+      // See comment in handleJavaLangEnumValueOf.
+      if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
+        pendingReflectiveUses.add(currentMethod);
+      }
       if (!registerItemWithTarget(staticInvokes, method)) {
         return false;
       }
@@ -684,14 +709,19 @@
       }
       // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
       // static field initialization (and other class-load-time sideeffects) will not happen.
+      KeepReason reason = KeepReason.reachableFromLiveType(type);
       if (!holder.isLibraryClass() && holder.hasNonTrivialClassInitializer()) {
         DexEncodedMethod clinit = holder.getClassInitializer();
         if (clinit != null) {
           assert clinit.method.holder == holder.type;
-          markDirectStaticOrConstructorMethodAsLive(clinit, KeepReason.reachableFromLiveType(type));
+          markDirectStaticOrConstructorMethodAsLive(clinit, reason);
         }
       }
 
+      if (holder.isProgramClass() && holder.isSerializable(appInfo)) {
+        enqueueFirstNonSerializableClassInitializer(holder, reason);
+      }
+
       // If this type has deferred annotations, we have to process those now, too.
       Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
       if (annotations != null) {
@@ -1145,6 +1175,25 @@
     }
   }
 
+  private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
+    DexType arrayOfEnumClass =
+        appInfo.dexItemFactory.createType(
+            appInfo.dexItemFactory.createString("[" + enumClass.type.toDescriptorString()));
+    DexProto proto = appInfo.dexItemFactory.createProto(arrayOfEnumClass);
+    return appInfo.dexItemFactory.createMethod(
+        enumClass.type, proto, appInfo.dexItemFactory.createString("values"));
+  }
+
+  private void markEnumValuesAsReachable(DexClass clazz, KeepReason reason) {
+    DexEncodedMethod valuesMethod = clazz.lookupMethod(generatedEnumValuesMethod(clazz));
+    if (valuesMethod != null) {
+      // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
+      // marking of not renaming is in the root set.
+      enqueueRootItem(valuesMethod, reason);
+      rootSet.noObfuscation.add(valuesMethod);
+    }
+  }
+
   private static void fillWorkList(Deque<DexType> worklist, DexType type) {
     if (type.isInterface()) {
       // We need to check if the method is shadowed by a class that directly implements
@@ -1529,15 +1578,23 @@
     DexType originHolder = method.method.holder;
     Origin origin = appInfo.originFor(originHolder);
     IRCode code = method.buildIR(appInfo, appView.graphLense(), options, origin);
-    code.instructionIterator().forEachRemaining(this::handleReflectiveBehavior);
+    Iterator<Instruction> iterator = code.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      handleReflectiveBehavior(method, instruction);
+    }
   }
 
-  private void handleReflectiveBehavior(Instruction instruction) {
+  private void handleReflectiveBehavior(DexEncodedMethod method, Instruction instruction) {
     if (!instruction.isInvokeMethod()) {
       return;
     }
     InvokeMethod invoke = instruction.asInvokeMethod();
     DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod == appInfo.dexItemFactory.enumMethods.valueOf) {
+      handleJavaLangEnumValueOf(method, invoke);
+      return;
+    }
     if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
       return;
     }
@@ -1572,6 +1629,20 @@
     }
   }
 
+  private void handleJavaLangEnumValueOf(DexEncodedMethod method, InvokeMethod invoke) {
+    // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
+    // access the values() method of the enum class passed as the first argument. The method
+    // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
+    // call this method.
+    if (invoke.inValues().get(0).isConstClass()) {
+      DexClass clazz =
+          appInfo.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
+      if (clazz.accessFlags.isEnum() && clazz.superType == appInfo.dexItemFactory.enumType) {
+        markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
+      }
+    }
+  }
+
   private static class Action {
 
     final Kind kind;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index fe31b14..49f99dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -16,6 +16,9 @@
   public static final String ENCLOSING_METHOD = "EnclosingMethod";
   public static final String SIGNATURE = "Signature";
   public static final String EXCEPTIONS = "Exceptions";
+  public static final String LINE_NUMBER_TABLE = "LineNumberTable";
+  public static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable";
+  public static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable";
   public static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension";
   public static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations";
   public static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations";
@@ -36,6 +39,9 @@
   public boolean enclosingMethod = false;
   public boolean signature = false;
   public boolean exceptions = false;
+  public boolean lineNumberTable = false;
+  public boolean localVariableTable = false;
+  public boolean localVariableTypeTable = false;
   public boolean sourceDebugExtension = false;
   public boolean runtimeVisibleAnnotations = false;
   public boolean runtimeInvisibleAnnotations = false;
@@ -108,6 +114,9 @@
     innerClasses = update(innerClasses, INNER_CLASSES, patterns);
     enclosingMethod = update(enclosingMethod, ENCLOSING_METHOD, patterns);
     signature = update(signature, SIGNATURE, patterns);
+    lineNumberTable = update(lineNumberTable, LINE_NUMBER_TABLE, patterns);
+    localVariableTable = update(localVariableTable, LOCAL_VARIABLE_TABLE, patterns);
+    localVariableTypeTable = update(localVariableTypeTable, LOCAL_VARIABLE_TYPE_TABLE, patterns);
     exceptions = update(exceptions, EXCEPTIONS, patterns);
     sourceDebugExtension = update(sourceDebugExtension, SOURCE_DEBUG_EXTENSION, patterns);
     runtimeVisibleAnnotations = update(runtimeVisibleAnnotations, RUNTIME_VISIBLE_ANNOTATIONS,
@@ -146,6 +155,18 @@
       throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check "
           + "-keepattributes directive.");
     }
+    if (forceProguardCompatibility && localVariableTable && !lineNumberTable) {
+      // If locals are kept, assume line numbers should be kept too.
+      lineNumberTable = true;
+      compatibility.addKeepAttributePatterns(
+          ImmutableList.of(ProguardKeepAttributes.LINE_NUMBER_TABLE));
+    }
+    if (localVariableTable && !lineNumberTable) {
+      throw new CompilationError(
+          "Attribute " + LOCAL_VARIABLE_TABLE
+              + " requires " + LINE_NUMBER_TABLE
+              + ". Check -keepattributes directive.");
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 66801c0..e592a9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1288,6 +1288,21 @@
 
       return field.toTypeSubstitutedField(newSignature);
     }
+
+    private void makeStatic(DexEncodedMethod method) {
+      method.accessFlags.setStatic();
+
+      Code code = method.getCode();
+      if (code.isJarCode()) {
+        MethodNode node = code.asJarCode().getNode();
+        node.access |= Opcodes.ACC_STATIC;
+        node.desc = method.method.proto.toDescriptorString();
+      } else {
+        // Due to member rebinding we may have inserted bridge methods with synthesized code.
+        // Currently, there is no easy way to make such code static.
+        abortMerge = true;
+      }
+    }
   }
 
   private static void makePrivate(DexEncodedMethod method) {
@@ -1297,17 +1312,6 @@
     method.accessFlags.setPrivate();
   }
 
-  private static void makeStatic(DexEncodedMethod method) {
-    method.accessFlags.setStatic();
-
-    Code code = method.getCode();
-    if (code.isJarCode()) {
-      MethodNode node = code.asJarCode().getNode();
-      node.access |= Opcodes.ACC_STATIC;
-      node.desc = method.method.proto.toDescriptorString();
-    }
-  }
-
   private DexProto getStaticProto(DexType receiverType, DexProto proto) {
     DexType[] parameterTypes = new DexType[proto.parameters.size() + 1];
     parameterTypes[0] = receiverType;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index f087295..a4f55c3 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -79,7 +79,7 @@
   }
 
   static Reporter defaultReporter() {
-    return new Reporter(new DefaultDiagnosticsHandler());
+    return new Reporter();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/utils/DefaultDiagnosticsHandler.java b/src/main/java/com/android/tools/r8/utils/DefaultDiagnosticsHandler.java
deleted file mode 100644
index 68c2840..0000000
--- a/src/main/java/com/android/tools/r8/utils/DefaultDiagnosticsHandler.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2017, 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.utils;
-
-import com.android.tools.r8.DiagnosticsHandler;
-
-public class DefaultDiagnosticsHandler implements DiagnosticsHandler {
-}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index d77adfd..8fa85bf 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -65,7 +65,7 @@
       } catch (ResourceException e) {
         throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
       } catch (AssertionError e) {
-        throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()));
+        throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()), e);
       }
       reporter.failIfPendingErrors();
     } catch (AbortException e) {
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 44cfc89..dcf521d 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -85,7 +85,7 @@
   }
 
   public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
-    return fromSpecification(file, new DefaultDiagnosticsHandler());
+    return fromSpecification(file, new DiagnosticsHandler() {});
   }
 
   public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter)
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 e0e4227..e6fd878 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -41,8 +41,7 @@
 
   public enum LineNumberOptimization {
     OFF,
-    ON,
-    IDENTITY_MAPPING
+    ON
   }
 
   public final DexItemFactory itemFactory;
@@ -57,7 +56,7 @@
 
   // Constructor for testing and/or other utilities.
   public InternalOptions() {
-    reporter = new Reporter(new DefaultDiagnosticsHandler());
+    reporter = new Reporter();
     itemFactory = new DexItemFactory();
     proguardConfiguration = ProguardConfiguration.defaultConfiguration(itemFactory, reporter);
   }
@@ -462,7 +461,6 @@
     public boolean nondeterministicCycleElimination = false;
     public Set<Inliner.Reason> validInliningReasons = null;
     public boolean suppressExperimentalCfBackendWarning = false;
-    public boolean removeLocalsTable = false;
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
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 66490b2..e81d920 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -10,6 +10,16 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
+import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugEvent.EndLocal;
+import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
+import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
+import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexDebugEventVisitor;
 import com.android.tools.r8.graph.DexDebugInfo;
@@ -41,86 +51,6 @@
 
 public class LineNumberOptimizer {
 
-  // EventFilter is a visitor for DebugEvents, splits events into two sinks:
-  // - Forwards non-positional events unchanged into a BypassedEventReceiver
-  // - Forwards positional events, accumulated into DexDebugPositionStates, into
-  //   positionEventReceiver.
-  private static class EventFilter implements DexDebugEventVisitor {
-    private final BypassedEventReceiver bypassedEventReceiver;
-    private final PositionEventReceiver positionEventReceiver;
-
-    private interface BypassedEventReceiver {
-      void receiveBypassedEvent(DexDebugEvent event);
-    }
-
-    private interface PositionEventReceiver {
-      void receivePositionEvent(DexDebugPositionState positionState);
-    }
-
-    private final DexDebugPositionState positionState;
-
-    private EventFilter(
-        int startLine,
-        DexMethod method,
-        BypassedEventReceiver bypassedEventReceiver,
-        PositionEventReceiver positionEventReceiver) {
-      positionState = new DexDebugPositionState(startLine, method);
-      this.bypassedEventReceiver = bypassedEventReceiver;
-      this.positionEventReceiver = positionEventReceiver;
-    }
-
-    @Override
-    public void visit(DexDebugEvent.SetPrologueEnd event) {
-      bypassedEventReceiver.receiveBypassedEvent(event);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.SetEpilogueBegin event) {
-      bypassedEventReceiver.receiveBypassedEvent(event);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.StartLocal event) {
-      bypassedEventReceiver.receiveBypassedEvent(event);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.EndLocal event) {
-      bypassedEventReceiver.receiveBypassedEvent(event);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.RestartLocal event) {
-      bypassedEventReceiver.receiveBypassedEvent(event);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.AdvancePC advancePC) {
-      positionState.visit(advancePC);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.AdvanceLine advanceLine) {
-      positionState.visit(advanceLine);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.SetInlineFrame setInlineFrame) {
-      positionState.visit(setInlineFrame);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.Default defaultEvent) {
-      positionState.visit(defaultEvent);
-      positionEventReceiver.receivePositionEvent(positionState);
-    }
-
-    @Override
-    public void visit(DexDebugEvent.SetFile setFile) {
-      positionState.visit(setFile);
-    }
-  }
-
   // PositionRemapper is a stateful function which takes a position (represented by a
   // DexDebugPositionState) and returns a remapped Position.
   private interface PositionRemapper {
@@ -165,6 +95,11 @@
       this.processedEvents = processedEvents;
     }
 
+    private void emitAdvancePc(int pc) {
+      processedEvents.add(new AdvancePC(pc - previousPc));
+      previousPc = pc;
+    }
+
     private void emitPositionEvents(int currentPc, Position currentPosition) {
       if (previousPosition == null) {
         startLine = currentPosition.line;
@@ -255,7 +190,6 @@
 
         for (DexEncodedMethod method : methods) {
           List<MappedPosition> mappedPositions = new ArrayList<>();
-
           Code code = method.getCode();
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
@@ -469,36 +403,96 @@
     DexDebugInfo debugInfo = dexCode.getDebugInfo();
     List<DexDebugEvent> processedEvents = new ArrayList<>();
 
-    // Our pipeline will be:
-    // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
-    // [processedEvents]
     PositionEventEmitter positionEventEmitter =
         new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
 
-    EventFilter eventFilter =
-        new EventFilter(
-            debugInfo.startLine,
-            method.method,
-            processedEvents::add,
-            positionState -> {
-              int currentLine = positionState.getCurrentLine();
-              assert currentLine >= 0;
-              Position position =
-                  positionRemapper.createRemappedPosition(
-                      positionState.getCurrentLine(),
-                      positionState.getCurrentFile(),
-                      positionState.getCurrentMethod(),
-                      positionState.getCurrentCallerPosition());
-              mappedPositions.add(
-                  new MappedPosition(
-                      positionState.getCurrentMethod(),
-                      currentLine,
-                      positionState.getCurrentCallerPosition(),
-                      position.line));
-              positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
-            });
+    // Debug event visitor to map line numbers.
+    // TODO(117268618): Cleanup the duplicate pc tracking.
+    DexDebugEventVisitor visitor =
+        new DexDebugEventVisitor() {
+          DexDebugPositionState state =
+              new DexDebugPositionState(debugInfo.startLine, method.method);
+          int currentPc = 0;
+
+          private void flushPc() {
+            if (currentPc != state.getCurrentPc()) {
+              positionEventEmitter.emitAdvancePc(state.getCurrentPc());
+              currentPc = state.getCurrentPc();
+            }
+          }
+
+          @Override
+          public void visit(AdvancePC advancePC) {
+            state.visit(advancePC);
+          }
+
+          @Override
+          public void visit(AdvanceLine advanceLine) {
+            state.visit(advanceLine);
+          }
+
+          @Override
+          public void visit(SetInlineFrame setInlineFrame) {
+            state.visit(setInlineFrame);
+          }
+
+          @Override
+          public void visit(Default defaultEvent) {
+            state.visit(defaultEvent);
+            int currentLine = state.getCurrentLine();
+            assert currentLine >= 0;
+            Position position =
+                positionRemapper.createRemappedPosition(
+                    state.getCurrentLine(),
+                    state.getCurrentFile(),
+                    state.getCurrentMethod(),
+                    state.getCurrentCallerPosition());
+            mappedPositions.add(
+                new MappedPosition(
+                    state.getCurrentMethod(),
+                    currentLine,
+                    state.getCurrentCallerPosition(),
+                    position.line));
+            positionEventEmitter.emitPositionEvents(state.getCurrentPc(), position);
+            currentPc = state.getCurrentPc();
+          }
+
+          @Override
+          public void visit(SetFile setFile) {
+            processedEvents.add(setFile);
+          }
+
+          @Override
+          public void visit(SetPrologueEnd setPrologueEnd) {
+            processedEvents.add(setPrologueEnd);
+          }
+
+          @Override
+          public void visit(SetEpilogueBegin setEpilogueBegin) {
+            processedEvents.add(setEpilogueBegin);
+          }
+
+          @Override
+          public void visit(StartLocal startLocal) {
+            flushPc();
+            processedEvents.add(startLocal);
+          }
+
+          @Override
+          public void visit(EndLocal endLocal) {
+            flushPc();
+            processedEvents.add(endLocal);
+          }
+
+          @Override
+          public void visit(RestartLocal restartLocal) {
+            flushPc();
+            processedEvents.add(restartLocal);
+          }
+        };
+
     for (DexDebugEvent event : debugInfo.events) {
-      event.accept(eventFilter);
+      event.accept(visitor);
     }
 
     DexDebugInfo optimizedDebugInfo =
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 37f57da..928058e 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -20,6 +20,10 @@
   private Diagnostic lastError;
   private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
 
+  public Reporter() {
+    this(new DiagnosticsHandler() {});
+  }
+
   public Reporter(DiagnosticsHandler clientHandler) {
     this.clientHandler = clientHandler;
   }
@@ -104,7 +108,7 @@
   }
 
   private <T extends Throwable> T addSuppressedExceptions(T t) {
-    suppressedExceptions.forEach(throwable -> t.addSuppressed(throwable));
+    suppressedExceptions.forEach(t::addSuppressed);
     return t;
   }
 
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 97be613..5e70808 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -2,6 +2,9 @@
 # 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.
 
+# Keep line numbers to ensure method mappings in the map file.
+-keepattributes LineNumberTable
+
 # Keep the application entry point. Get rid of everything that is not
 # reachable from there.
 -keep public class classmerging.Test {
@@ -57,6 +60,3 @@
 }
 
 -printmapping
-
-# TODO(herhut): Consider supporting merging of inner-class attributes.
-# -keepattributes *
\ No newline at end of file
diff --git a/src/test/examples/shaking1/keep-rules.txt b/src/test/examples/shaking1/keep-rules.txt
index 66cf1c6..82786d5 100644
--- a/src/test/examples/shaking1/keep-rules.txt
+++ b/src/test/examples/shaking1/keep-rules.txt
@@ -2,6 +2,8 @@
 # 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.
 
+-keepattributes LineNumberTable
+
 # Keep the application entry point. Get rid of everything that is not
 # reachable from there.
 -keep public class shaking1.Shaking {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fa35d0d..6f93df4 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.SmaliWriter;
@@ -711,7 +710,7 @@
 
   protected ProcessResult runOnVMRaw(AndroidApp app, Class<?> mainClass, Backend backend)
       throws IOException {
-    return runOnVMRaw(app, mainClass.getCanonicalName(), backend);
+    return runOnVMRaw(app, mainClass.getTypeName(), backend);
   }
 
   protected ProcessResult runOnVMRaw(AndroidApp app, String mainClass, Backend backend)
@@ -830,13 +829,6 @@
         .map(kind::cast);
   }
 
-  protected Stream<CfInstruction> filterInstructionKind(
-      CfCode code, Class<? extends CfInstruction> kind) {
-    return code.getInstructions().stream()
-        .filter(kind::isInstance)
-        .map(kind::cast);
-  }
-
   public enum MinifyMode {
     NONE,
     JAVA,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f5eaa25..515d191 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -106,7 +105,11 @@
   private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
   private static final String PROGUARD = PROGUARD5_2_1;
 
+  private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
+  private static final String RETRACE = RETRACE6_0_1;
+
   public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+  public static final Path R8_LIB_JAR = Paths.get(LIBS_DIR, "r8lib_with_deps.jar");
 
   public enum DexVm {
     ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
@@ -525,6 +528,13 @@
     return PROGUARD6_0_1 + ".sh";
   }
 
+  private static String getRetraceScript() {
+    if (isWindows()) {
+      return RETRACE + ".bat";
+    }
+    return RETRACE + ".sh";
+  }
+
   private static Path getDxExecutablePath() {
     String toolsDir = toolsDir();
     String executableName = toolsDir.equals("windows") ? "dx.bat" : "dx";
@@ -808,7 +818,7 @@
   public static ProguardConfiguration loadProguardConfiguration(
       DexItemFactory factory, List<Path> configPaths)
       throws IOException, ProguardRuleParserException {
-    Reporter reporter = new Reporter(new DefaultDiagnosticsHandler());
+    Reporter reporter = new Reporter();
     if (configPaths.isEmpty()) {
       return ProguardConfiguration.defaultConfiguration(factory, reporter);
     }
@@ -949,7 +959,7 @@
   }
 
   public static ProcessResult runJava(Class clazz) throws Exception {
-    String main = clazz.getCanonicalName();
+    String main = clazz.getTypeName();
     Path path = getClassPathForTests();
     return runJava(path, main);
   }
@@ -1499,6 +1509,23 @@
     return runProguard(getProguard6Script(), inJar, outJar, configs, map);
   }
 
+  public static ProcessResult runRetraceRaw(Path map, Path stackTrace) throws IOException {
+    List<String> command = new ArrayList<>();
+    command.add(getRetraceScript());
+    command.add(map.toString());
+    command.add(stackTrace.toString());
+    ProcessBuilder builder = new ProcessBuilder(command);
+    return ToolHelper.runProcess(builder);
+  }
+
+  public static String runRetrace(Path map, Path stackTrace) throws IOException {
+    ProcessResult result = runRetraceRaw(map, stackTrace);
+    if (result.exitCode != 0) {
+      fail("Retrace failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return result.stdout;
+  }
+
   public static class ProcessResult {
 
     public final int exitCode;
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index 02df3d1..7202914 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -35,6 +35,10 @@
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+/**
+ * This test relies on a freshly built from builds/libs/r8lib_with_deps.jar. If this test fails
+ * rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
+ */
 public class BootstrapCurrentEqualityTest extends TestBase {
 
   private static final String R8_NAME = "com.android.tools.r8.R8";
@@ -45,7 +49,7 @@
     "-keep class " + HELLO_NAME + " {", "  public static void main(...);", "}",
   };
 
-  private class R8Result {
+  private static class R8Result {
 
     final ProcessResult processResult;
     final Path outputJar;
@@ -70,18 +74,19 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    r8R8Debug = compileR8(CompilationMode.DEBUG);
-    r8R8Release = compileR8(CompilationMode.RELEASE);
+    r8R8Debug = compileR8("--debug");
+    r8R8Release = compileR8("--release");
   }
 
-  private static Path compileR8(CompilationMode mode) throws Exception {
+  private static Path compileR8(String mode) throws Exception {
     // Run R8 on r8.jar.
-    Path output = runR8(ToolHelper.R8_JAR, testFolder.newFolder().toPath(), mode);
+    R8Result output = runExternalR8(
+        ToolHelper.R8_LIB_JAR, ToolHelper.R8_LIB_JAR, testFolder.newFolder().toPath(), MAIN_KEEP, mode);
     // Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods
     // from their supertypes. This is a sanity check for the tree shaking and minification.
-    AndroidApp app = AndroidApp.builder().addProgramFile(output).build();
+    AndroidApp app = AndroidApp.builder().addProgramFile(output.outputJar).build();
     new ClassHierarchyVerifier(new CodeInspector(app)).run();
-    return output;
+    return output.outputJar;
   }
 
   @Test
@@ -95,10 +100,10 @@
   private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
       throws Exception {
     R8Result runR8Debug =
-        runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+        runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--debug");
     assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
     R8Result runR8Release =
-        runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--release");
+        runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--release");
     assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
     RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
     RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
@@ -115,25 +120,17 @@
     assertProgramsEqual(result.outputJar, runR8R8.outputJar);
   }
 
-  private static Path runR8(Path inputJar, Path outputPath, CompilationMode mode) throws Exception {
-    Path outputJar = outputPath.resolve("output.jar");
-    ToolHelper.runR8(
-        R8Command.builder()
-            .setMode(mode)
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
-            .addProgramFiles(inputJar)
-            .addProguardConfigurationFiles(MAIN_KEEP)
-            .build());
-    return outputJar;
-  }
-
-  private R8Result runExternalR8(
+  private static R8Result runExternalR8(
       Path r8Jar, Path inputJar, Path output, String[] keepRules, String mode) throws Exception {
     Path pgConfigFile = output.resolve("keep.rules");
+    FileUtils.writeTextFile(pgConfigFile, keepRules);
+    return runExternalR8(r8Jar, inputJar, output, pgConfigFile, mode);
+  }
+
+  private static R8Result runExternalR8(
+      Path r8Jar, Path inputJar, Path output, Path keepRules, String mode) throws Exception {
     Path outputJar = output.resolve("output.jar");
     Path pgMapFile = output.resolve("map.txt");
-    FileUtils.writeTextFile(pgConfigFile, keepRules);
     ProcessResult processResult =
         ToolHelper.runJava(
             r8Jar,
@@ -145,7 +142,7 @@
             "--output",
             outputJar.toString(),
             "--pg-conf",
-            pgConfigFile.toString(),
+            keepRules.toString(),
             mode,
             "--pg-map-output",
             pgMapFile.toString());
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 6333638..b85b15b 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
 import java.nio.file.Path;
@@ -184,7 +183,7 @@
       if (frontend == Frontend.CF && compilationMode == CompilationMode.DEBUG) {
         // TODO(b/79725635): Investigate why these tests fail on the buildbot.
         // Use a Reporter to extract origin info to standard error.
-        new Reporter(new DefaultDiagnosticsHandler()).error(e);
+        new Reporter().error(e);
         // Print the stack trace since this is not always printed by JUnit.
         e.printStackTrace();
         Assume.assumeNoException(
diff --git a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
index 308e8d7..e0b953a 100644
--- a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
@@ -27,7 +27,7 @@
     DebugTestConfig d8NoLocals = new D8DebugTestConfig().compileAndAdd(
         temp,
         Collections.singletonList(ToolHelper.getClassFileForTestClass(CLASS)),
-        options -> options.testing.removeLocalsTable = true);
+        options -> options.proguardConfiguration.getKeepAttributes().localVariableTable = false);
 
     new DebugStreamComparator()
         .add("CF", streamDebugTest(cf, NAME, NO_FILTER))
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 616256e..78e9a56 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -96,12 +96,6 @@
   }
 
   @Test
-  public void testIdentityCompilation() throws Throwable {
-    // Compilation will fail if the identity translation does.
-    makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false, runtimeKind);
-  }
-
-  @Test
   public void testNotOptimized() throws Throwable {
     testRelease(
         makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index a518a36..7e3c10a 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
@@ -136,8 +135,7 @@
     DexApplication application = builder.build();
 
     CollectInfoConsumer consumer = new CollectInfoConsumer();
-    InternalOptions options = new InternalOptions(dexItemFactory,
-        new Reporter(new DefaultDiagnosticsHandler()));
+    InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
     options.programConsumer = consumer;
     ApplicationWriter writer =
         new ApplicationWriter(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 109a6cb..bd0f8fb 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -120,9 +120,9 @@
       DexType mainClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
-          NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, false),
-          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+          InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+          NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, false, appInfo),
+          NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
@@ -136,9 +136,9 @@
       DexType mainClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
-          NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, false),
-          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+          InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+          NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, false, appInfo),
+          NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
@@ -153,8 +153,8 @@
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           // An element inside a non-null array could be null.
-          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
-          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+          NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
       forEachOutValue(irCode, (v, l) -> {
         if (l.isArrayType()) {
           ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
@@ -179,8 +179,8 @@
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           // An element inside a non-null array could be null.
-          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
-          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+          NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
       forEachOutValue(irCode, (v, l) -> {
         if (l.isArrayType()) {
           ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
@@ -206,11 +206,11 @@
       DexType testClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          Argument.class, fromDexType(testClass, appInfo, true),
-          NonNull.class, fromDexType(testClass, appInfo, false),
+          Argument.class, fromDexType(testClass, true, appInfo),
+          NonNull.class, fromDexType(testClass, false, appInfo),
           // instance may not be initialized.
-          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
-          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+          NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
@@ -226,11 +226,11 @@
       DexType testClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          Argument.class, fromDexType(testClass, appInfo, true),
-          NonNull.class, fromDexType(testClass, appInfo, false),
+          Argument.class, fromDexType(testClass, true, appInfo),
+          NonNull.class, fromDexType(testClass, false, appInfo),
           // instance may not be initialized.
-          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
-          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+          NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 0898891..08a220f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -75,7 +75,7 @@
   }
 
   private TypeLatticeElement element(DexType type) {
-    return TypeLatticeElement.fromDexType(type, appInfo, true);
+    return TypeLatticeElement.fromDexType(type, true, appInfo);
   }
 
   private ArrayTypeLatticeElement array(int nesting, DexType base) {
@@ -84,15 +84,15 @@
 
   private TypeLatticeElement join(TypeLatticeElement... elements) {
     assertTrue(elements.length > 1);
-    return TypeLatticeElement.join(appInfo, Arrays.stream(elements));
+    return TypeLatticeElement.join(Arrays.stream(elements), appInfo);
   }
 
   private boolean strictlyLessThan(TypeLatticeElement l1, TypeLatticeElement l2) {
-    return TypeLatticeElement.strictlyLessThan(appInfo, l1, l2);
+    return l1.strictlyLessThan(l2, appInfo);
   }
 
   private boolean lessThanOrEqual(TypeLatticeElement l1, TypeLatticeElement l2) {
-    return TypeLatticeElement.lessThanOrEqual(appInfo, l1, l2);
+    return l1.lessThanOrEqual(l2, appInfo);
   }
 
   @Test
@@ -456,7 +456,7 @@
     DexType collection = factory.createType("Ljava/util/Collection;");
     DexType set = factory.createType("Ljava/util/Set;");
     DexType list = factory.createType("Ljava/util/List;");
-    DexType serializable = factory.createType("Ljava/io/Serializable;");
+    DexType serializable = factory.serializableType;
 
     Set<DexType> lub = computeLeastUpperBoundOfInterfaces(appInfo,
         ImmutableSet.of(set), ImmutableSet.of(list));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 564a81c..e569ad2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -69,6 +69,8 @@
             || NonNullTracker.throwsOnNullInput(prev)
             || (prev.isIf() && prev.asIf().isZeroTest())
             || !curr.getBlock().getPredecessors().contains(prev.getBlock()));
+        // Make sure non-null is used.
+        assertTrue(curr.outValue().numberOfAllUsers() > 0);
         count++;
       }
     }
@@ -114,7 +116,7 @@
     buildAndTest(NonNullAfterInvoke.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
     MethodSignature bar =
         new MethodSignature("bar", "int", new String[]{"java.lang.String"});
-    buildAndTest(NonNullAfterInvoke.class, bar, 2, this::checkInvokeGetsNullReceiver);
+    buildAndTest(NonNullAfterInvoke.class, bar, 1, this::checkInvokeGetsNullReceiver);
   }
 
   @Test
@@ -176,6 +178,6 @@
     buildAndTest(NonNullAfterNullCheck.class, bar, 1, this::checkInvokeGetsNonNullReceiver);
     MethodSignature baz =
         new MethodSignature("baz", "int", new String[]{"java.lang.String"});
-    buildAndTest(NonNullAfterNullCheck.class, baz, 2, this::checkInvokeGetsNullReceiver);
+    buildAndTest(NonNullAfterNullCheck.class, baz, 1, this::checkInvokeGetsNullReceiver);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NullArrayAndNullObjectValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NullArrayAndNullObjectValueTest.java
index 7b1c369..faff5a4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NullArrayAndNullObjectValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NullArrayAndNullObjectValueTest.java
@@ -9,13 +9,13 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Assert;
@@ -127,7 +127,7 @@
         "return");
 
     Path riJar = temp.getRoot().toPath().resolve("ri-out.jar");
-    jasminBuilder.writeJar(riJar, new DefaultDiagnosticsHandler());
+    jasminBuilder.writeJar(riJar, new DiagnosticsHandler() {});
     ProcessResult riResult = ToolHelper.runJava(riJar, "TestClass");
     Assert.assertEquals(riResult.toString(), 0, riResult.exitCode);
 
@@ -174,7 +174,7 @@
         "return");
 
     Path riJar = temp.getRoot().toPath().resolve("ri-out.jar");
-    jasminBuilder.writeJar(riJar, new DefaultDiagnosticsHandler());
+    jasminBuilder.writeJar(riJar, new DiagnosticsHandler() {});
     ProcessResult riResult = ToolHelper.runJava(riJar, "TestClass");
     Assert.assertEquals(riResult.toString(), 1, riResult.exitCode);
     assertTrue(riResult.stderr.contains("VerifyError"));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index 44720ed..d7a5732 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -13,23 +13,23 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.code.CheckCast;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -77,12 +77,14 @@
     assertThat(classSubject, isPresent());
   }
 
+  @Ignore("todo: jsjeon")
   @Test
   public void test_differentLocals() throws Throwable {
     ClassSubject classSubject = inspector.clazz(MAIN);
     MethodSubject method = classSubject.method("void", "differentLocals", ImmutableList.of());
     assertThat(method, isPresent());
-    long count = countCheckCast(method.getMethod().getCode());
+    long count =
+        Streams.stream(method.iterateInstructions(InstructionSubject::isCheckCast)).count();
     assertEquals(1, count);
 
     DebugTestConfig config = backend == Backend.CF
@@ -129,12 +131,14 @@
     );
   }
 
+  @Ignore("todo: jsjeon")
   @Test
   public void test_sameLocal() throws Throwable {
     ClassSubject classSubject = inspector.clazz(MAIN);
     MethodSubject method = classSubject.method("void", "sameLocal", ImmutableList.of());
     assertThat(method, isPresent());
-    long count = countCheckCast(method.getMethod().getCode());
+    long count =
+        Streams.stream(method.iterateInstructions(InstructionSubject::isCheckCast)).count();
     assertEquals(1, count);
 
     DebugTestConfig config = backend == Backend.CF
@@ -166,13 +170,4 @@
     );
   }
 
-  private long countCheckCast(Code code) {
-    if (backend == Backend.DEX) {
-      return filterInstructionKind(code.asDexCode(), CheckCast.class).count();
-    } else {
-      assert backend == Backend.CF;
-      return filterInstructionKind(code.asCfCode(), CfCheckCast.class).count();
-    }
-  }
-
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index caa697b..e65ebca 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
@@ -429,10 +430,11 @@
   }
 
   private String getProguardConfig(String main) {
-    return keepMainProguardConfiguration(main)
-        + "\n"
-        + "-dontobfuscate\n"
-        + "-allowaccessmodification";
+    return StringUtils.joinLines(
+        keepMainProguardConfiguration(main),
+        "-dontobfuscate",
+        "-allowaccessmodification",
+        "-keepattributes LineNumberTable");
   }
 
   private void configure(InternalOptions options) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 67afdd7..6b7a7c2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.code.InvokeStatic;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
 import com.android.tools.r8.graph.DexClass;
@@ -19,9 +18,12 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -229,8 +231,8 @@
   private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) {
     assertNotNull(clazz);
     MethodSignature signature = new MethodSignature(methodName, "void", params);
-    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
-    return filterInstructionKind(code, InvokeStatic.class)
+    MethodSubject method = clazz.method(signature);
+    return Streams.stream(method.iterateInstructions(InstructionSubject::isInvokeStatic))
         .map(insn -> insn.getMethod().toSourceString())
         .sorted()
         .collect(Collectors.toList());
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 43bf86e..4f91609 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -59,7 +59,6 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -660,7 +659,7 @@
       List<String> classes, int minApi, boolean intermediate, int methodCount)
       throws IOException, ExecutionException {
     return generateApplication(
-        classes, minApi, intermediate, methodCount, new DefaultDiagnosticsHandler());
+        classes, minApi, intermediate, methodCount, new DiagnosticsHandler() {});
   }
 
   private static AndroidApp generateApplication(
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
new file mode 100644
index 0000000..9552a61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
@@ -0,0 +1,186 @@
+// 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 com.android.tools.r8.memberrebinding;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IndirectSuperInterfaceTest extends TestBase {
+
+  private static final List<Class<?>> CLASSES =
+      ImmutableList.of(
+          InterfaceA.class, InterfaceASub.class, A.class,
+          InterfaceB.class, InterfaceBSub.class, B.class,
+          InterfaceC.class, InterfaceCSub.class, C.class,
+          InterfaceD.class, InterfaceDSub.class, D.class,
+          TestClass.class);
+
+  // Test A: class A extends an empty class that implements a non-empty interface.
+
+  interface InterfaceA {
+    @NeverInline
+    default String method() {
+      return "InterfaceA::method";
+    }
+  }
+
+  static class InterfaceASub implements InterfaceA {
+    // Intentionally empty.
+  }
+
+  static class A extends InterfaceASub {
+    @Override
+    public String method() {
+      return "A::method -> " + super.method();
+    }
+  }
+
+  // Test B: class B implements an empty interface that extends a non-empty interface.
+
+  interface InterfaceB {
+    @NeverInline
+    default String method() {
+      return "InterfaceB::method";
+    }
+  }
+
+  interface InterfaceBSub extends InterfaceB {
+    // Intentionally empty.
+  }
+
+  static class B implements InterfaceBSub {
+    @Override
+    public String method() {
+      return "B::method -> " + InterfaceBSub.super.method();
+    }
+  }
+
+  // Test C: class C extends a non-empty class that implements a non-empty interface.
+
+  interface InterfaceC {
+    @NeverInline
+    default String method() {
+      return "InterfaceC::method";
+    }
+  }
+
+  static class InterfaceCSub implements InterfaceC {
+    // This method is intentionally not annotated with @NeverInline. If we were to inline this
+    // method we would risk introducing a super-invocation to InterfaceC.method() in C.method,
+    // which would lead to an IncompatibleClassChangeError on the JVM.
+    // (See also Art978_virtual_interfaceTest.)
+    @Override
+    public String method() {
+      return InterfaceC.super.method();
+    }
+  }
+
+  static class C extends InterfaceCSub {
+    @Override
+    public String method() {
+      return "C::method -> " + super.method();
+    }
+  }
+
+  // Test D: class D implements a non-empty empty interface that extends a non-empty interface.
+
+  interface InterfaceD {
+    @NeverInline
+    default String method() {
+      return "InterfaceD::method";
+    }
+  }
+
+  interface InterfaceDSub extends InterfaceD {
+    // This method is intentionally not annotated with @NeverInline. If we were to inline this
+    // method we would risk introducing a super-invocation to InterfaceC.method() in C.method,
+    // which would lead to an IncompatibleClassChangeError on the JVM.
+    // (See also Art978_virtual_interfaceTest.)
+    @Override
+    default String method() {
+      return InterfaceD.super.method();
+    }
+  }
+
+  static class D implements InterfaceDSub {
+    @Override
+    public String method() {
+      return "D::method -> " + InterfaceDSub.super.method();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new A().method());
+      System.out.println(new B().method());
+      System.out.println(new C().method());
+      System.out.print(new D().method());
+    }
+  }
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] setup() {
+    return Backend.values();
+  }
+
+  public IndirectSuperInterfaceTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expected =
+        String.join(
+            System.lineSeparator(),
+            "A::method -> InterfaceA::method",
+            "B::method -> InterfaceB::method",
+            "C::method -> InterfaceC::method",
+            "D::method -> InterfaceD::method");
+    assertEquals(expected, runOnJava(TestClass.class));
+
+    AndroidAppConsumers sink = new AndroidAppConsumers();
+    Builder builder = R8Command.builder();
+    for (Class<?> clazz : CLASSES) {
+      builder.addClassProgramData(ToolHelper.getClassAsBytes(clazz), Origin.unknown());
+    }
+    builder
+        .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+        .addLibraryFiles(runtimeJar(backend))
+        .addProguardConfiguration(
+            ImmutableList.of(
+                // Keep all classes to prevent changes to the class hierarchy (e.g., due to
+                // vertical class merging).
+                "-keep class " + InterfaceA.class.getPackage().getName() + ".*",
+                keepMainProguardConfigurationWithInliningAnnotation(TestClass.class)),
+            Origin.unknown());
+    ToolHelper.allowTestProguardOptions(builder);
+    if (backend == Backend.DEX) {
+      builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    }
+    R8.run(builder.build());
+
+    ProcessResult result = runOnVMRaw(sink.build(), TestClass.class, backend);
+    assertEquals(result.toString(), 0, result.exitCode);
+    assertEquals(expected, result.stdout);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index a8708eb..66cd45e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -4,17 +4,19 @@
 
 package com.android.tools.r8.naming;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -22,6 +24,12 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
 
 @RunWith(Parameterized.class)
 public class EnumMinification extends TestBase {
@@ -37,29 +45,57 @@
     this.backend = backend;
   }
 
+  private AndroidApp buildApp(Class<?> mainClass, byte[] enumClassFile) throws Exception {
+    return ToolHelper.runR8(
+        R8Command.builder()
+            .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+            .addClassProgramData(enumClassFile, Origin.unknown())
+            .addProguardConfiguration(
+                ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+            .setProgramConsumer(emptyConsumer(backend))
+            .build());
+  }
+
+  public void runTest(
+      Class<?> mainClass, byte[] enumClass, String enumTypeName, boolean valueOfKept)
+      throws Exception {
+    AndroidApp output = buildApp(mainClass, enumClass);
+
+    CodeInspector inspector = new CodeInspector(output);
+    ClassSubject clazz = inspector.clazz(enumTypeName);
+    // The class and fields - including field $VALUES and method valueOf - can be renamed. Only
+    // the values() method needs to be
+    assertThat(clazz, isRenamed());
+    assertThat(clazz.field(enumTypeName, "VALUE1"), isRenamed());
+    assertThat(clazz.field(enumTypeName, "VALUE2"), isRenamed());
+    assertThat(clazz.field(enumTypeName + "[]", "$VALUES"), isRenamed());
+    assertThat(
+        clazz.method(enumTypeName, "valueOf", ImmutableList.of("java.lang.String")),
+        valueOfKept ? isRenamed() : not(isPresent()));
+    assertThat(clazz.method(enumTypeName + "[]", "values", ImmutableList.of()), not(isRenamed()));
+
+    assertEquals("VALUE1", runOnVM(output, mainClass, backend));
+  }
+
   @Test
   public void test() throws Exception {
-    AndroidApp output =
-        ToolHelper.runR8(
-            R8Command.builder()
-                .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
-                .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
-                .addProguardConfiguration(
-                    ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
-                .setProgramConsumer(emptyConsumer(backend))
-                .build());
+    runTest(Main.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), true);
+  }
 
-    // TODO(117299356): valueOf on enum fails for minified enums.
-    ProcessResult result = runOnVMRaw(output, Main.class, backend);
-    assertEquals(1, result.exitCode);
-    assertThat(
-        result.stderr,
-        containsString(
-            backend == Backend.DEX
-                ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
-                    ? "java.lang.NoSuchMethodException"
-                    : "java.lang.NullPointerException"
-                : "java.lang.IllegalArgumentException"));
+  @Test
+  public void testAsmDump() throws Exception {
+    runTest(Main.class, EnumDump.dump(true), "com.android.tools.r8.naming.Enum", true);
+  }
+
+  @Test
+  public void testWithoutValuesMethod() throws Exception {
+    // This should not fail even if the values method is not present.
+    buildApp(Main.class, EnumDump.dump(false));
+  }
+
+  @Test
+  public void testJavaLangEnumValueOf() throws Exception {
+    runTest(Main2.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), false);
   }
 }
 
@@ -67,7 +103,7 @@
 
   public static void main(String[] args) {
     Enum e = Enum.valueOf("VALUE1");
-    System.out.println(e);
+    System.out.print(e);
   }
 }
 
@@ -75,3 +111,219 @@
   VALUE1,
   VALUE2
 }
+
+class Main2 {
+  public static void main(String[] args) {
+    // Use java.lang.Enum.valueOf instead of com.android.tools.r8.naming.Enum.valueOf.
+    System.out.print(java.lang.Enum.valueOf(Enum.class, "VALUE1"));
+  }
+}
+/* Dump of javac generated code from the following enum class (the one just above):
+ *
+ *  package com.android.tools.r8.naming;
+ *
+ *  enum Enum {
+ *    VALUE1,
+ *    VALUE2
+ *  }
+ *
+ */
+class EnumDump implements Opcodes {
+
+  public static byte[] dump(boolean includeValuesMethod) {
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_FINAL | ACC_SUPER | ACC_ENUM,
+        "com/android/tools/r8/naming/Enum",
+        "Ljava/lang/Enum<Lcom/android/tools/r8/naming/Enum;>;",
+        "java/lang/Enum",
+        null);
+
+    classWriter.visitSource("EnumMinification.java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM,
+              "VALUE1",
+              "Lcom/android/tools/r8/naming/Enum;",
+              null,
+              null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM,
+              "VALUE2",
+              "Lcom/android/tools/r8/naming/Enum;",
+              null,
+              null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PRIVATE | ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC,
+              "$VALUES",
+              "[Lcom/android/tools/r8/naming/Enum;",
+              null,
+              null);
+      fieldVisitor.visitEnd();
+    }
+    if (includeValuesMethod) {
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC,
+                "values",
+                "()[Lcom/android/tools/r8/naming/Enum;",
+                null,
+                null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(72, label0);
+        methodVisitor.visitFieldInsn(
+            GETSTATIC,
+            "com/android/tools/r8/naming/Enum",
+            "$VALUES",
+            "[Lcom/android/tools/r8/naming/Enum;");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "[Lcom/android/tools/r8/naming/Enum;",
+            "clone",
+            "()Ljava/lang/Object;",
+            false);
+        methodVisitor.visitTypeInsn(CHECKCAST, "[Lcom/android/tools/r8/naming/Enum;");
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitMaxs(1, 0);
+        methodVisitor.visitEnd();
+      }
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "valueOf",
+              "(Ljava/lang/String;)Lcom/android/tools/r8/naming/Enum;",
+              null,
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(72, label0);
+      methodVisitor.visitLdcInsn(Type.getType("Lcom/android/tools/r8/naming/Enum;"));
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/lang/Enum",
+          "valueOf",
+          "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;",
+          false);
+      methodVisitor.visitTypeInsn(CHECKCAST, "com/android/tools/r8/naming/Enum");
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", "()V", null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(72, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable(
+          "this", "Lcom/android/tools/r8/naming/Enum;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(3, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(73, label0);
+      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitLdcInsn("VALUE1");
+      methodVisitor.visitInsn(ICONST_0);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL,
+          "com/android/tools/r8/naming/Enum",
+          "<init>",
+          "(Ljava/lang/String;I)V",
+          false);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC,
+          "com/android/tools/r8/naming/Enum",
+          "VALUE1",
+          "Lcom/android/tools/r8/naming/Enum;");
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(74, label1);
+      methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitLdcInsn("VALUE2");
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL,
+          "com/android/tools/r8/naming/Enum",
+          "<init>",
+          "(Ljava/lang/String;I)V",
+          false);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC,
+          "com/android/tools/r8/naming/Enum",
+          "VALUE2",
+          "Lcom/android/tools/r8/naming/Enum;");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(72, label2);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitTypeInsn(ANEWARRAY, "com/android/tools/r8/naming/Enum");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitInsn(ICONST_0);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC,
+          "com/android/tools/r8/naming/Enum",
+          "VALUE1",
+          "Lcom/android/tools/r8/naming/Enum;");
+      methodVisitor.visitInsn(AASTORE);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitFieldInsn(
+          GETSTATIC,
+          "com/android/tools/r8/naming/Enum",
+          "VALUE2",
+          "Lcom/android/tools/r8/naming/Enum;");
+      methodVisitor.visitInsn(AASTORE);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC,
+          "com/android/tools/r8/naming/Enum",
+          "$VALUES",
+          "[Lcom/android/tools/r8/naming/Enum;");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(4, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index e340c98..719fe80 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -70,8 +69,7 @@
       throws IOException, ProguardRuleParserException, ExecutionException {
     ProguardConfiguration configuration =
         ToolHelper.loadProguardConfiguration(dexItemFactory, configPaths);
-    InternalOptions options = new InternalOptions(configuration,
-        new Reporter(new DefaultDiagnosticsHandler()));
+    InternalOptions options = new InternalOptions(configuration, new Reporter());
 
     ExecutorService executor = ThreadUtils.getExecutorService(1);
 
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
index 9ccc94b..5127a59 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -112,6 +112,7 @@
                         + " <methods>;"
                         + "}",
                     "-printmapping",
+                    "-keepattributes LineNumberTable",
                     reflectionRules),
                 Origin.unknown())
             .setOutput(out, outputMode(backend));
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
rename to src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
index 7ce8a05..3b552c8 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
@@ -2,7 +2,7 @@
 // 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.naming;
+package com.android.tools.r8.naming.applymapping;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/CompositionalLenseTest.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
rename to src/test/java/com/android/tools/r8/naming/applymapping/CompositionalLenseTest.java
index 711ae60..446a481 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/CompositionalLenseTest.java
@@ -1,7 +1,7 @@
 // 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 com.android.tools.r8.memberrebinding;
+package com.android.tools.r8.naming.applymapping;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
@@ -44,7 +44,7 @@
   // Sub#foo ~> Base#foo by member rebinding analysis
 }
 
-class TestMain {
+class CompositionalLenseTestMain {
   public static void main(String[] args) {
     // Without regard to the order of member rebinding and apply mapping,
     // this call should be mapped to X.bar(), not Y.bar() nor Base.foo().
@@ -55,7 +55,7 @@
 @RunWith(Parameterized.class)
 public class CompositionalLenseTest extends TestBase {
   private final static List<Class> CLASSES =
-      ImmutableList.of(Base.class, Sub.class, TestMain.class);
+      ImmutableList.of(Base.class, Sub.class, CompositionalLenseTestMain.class);
 
   private Backend backend;
 
@@ -72,9 +72,9 @@
   public void test() throws Exception {
     Path mapPath = temp.newFile("test-mapping.txt").toPath();
     List<String> pgMap = ImmutableList.of(
-        "com.android.tools.r8.memberrebinding.Base -> X:",
+        "com.android.tools.r8.naming.applymapping.Base -> X:",
         "  void foo() -> bar",
-        "com.android.tools.r8.memberrebinding.Sub -> Y:",
+        "com.android.tools.r8.naming.applymapping.Sub -> Y:",
         "  void foo() -> bar"
     );
     FileUtils.writeTextFile(mapPath, pgMap);
@@ -84,7 +84,7 @@
     builder
         .addProguardConfiguration(
             ImmutableList.of(
-                keepMainProguardConfiguration(TestMain.class),
+                keepMainProguardConfiguration(CompositionalLenseTestMain.class),
                 "-applymapping " + mapPath,
                 "-dontobfuscate"), // to use the renamed names in test-mapping.txt
             Origin.unknown())
@@ -97,7 +97,7 @@
               options.enableVerticalClassMerging = false;
             });
     CodeInspector codeInspector = new CodeInspector(processedApp);
-    ClassSubject classSubject = codeInspector.clazz(TestMain.class);
+    ClassSubject classSubject = codeInspector.clazz(CompositionalLenseTestMain.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(CodeInspector.MAIN);
     assertThat(methodSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index dd1ee60..db3f356 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -100,22 +100,13 @@
     this.minify = minify;
   }
 
-  private String run(AndroidApp app, String main) throws IOException {
-    if (generatesDex(shrinker)) {
-      return runOnArt(app, main);
-    } else {
-      assert generatesCf(shrinker);
-      return runOnJava(app, main, Collections.emptyList());
-    }
-  }
-
   private static boolean vmVersionIgnored() {
     return !ToolHelper.getDexVm().getVersion().isAtLeast(Version.V7_0_0);
   }
 
   @Test
   public void test_keepAll() throws Exception {
-    Assume.assumeFalse(generatesDex(shrinker) && vmVersionIgnored());
+    Assume.assumeFalse(shrinker.generatesDex() && vmVersionIgnored());
     Class mainClass = TestMain.class;
     String keep = !minify ? "-keep" : "-keep,allowobfuscation";
     List<String> config = ImmutableList.of(
@@ -138,10 +129,10 @@
     AndroidApp app = runShrinker(shrinker, CLASSES, config);
     assertEquals(
         StringUtils.withNativeLineSeparator("123451234567\nABC\n"),
-        run(app, mainClass.getCanonicalName()));
+        runOnVM(app, mainClass.getCanonicalName(), shrinker.toBackend()));
 
     CodeInspector codeInspector =
-        isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+        shrinker.isR8() ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
     ClassSubject testClass = codeInspector.clazz(TestClass.class);
     assertThat(testClass, isPresent());
 
@@ -149,7 +140,7 @@
     MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
-    if (isR8(shrinker)) {
+    if (shrinker.isR8()) {
       assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic());
     } else {
       assertFalse(staticMethod.getMethod().accessFlags.isPublic());
@@ -159,14 +150,14 @@
     staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
-    boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+    boolean publicizeCondition = shrinker.isR8() ? allowAccessModification
         : minify && repackagePrefix != null && allowAccessModification;
     assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
   }
 
   @Test
   public void test_keepNonPublic() throws Exception {
-    Assume.assumeFalse(generatesDex(shrinker) && vmVersionIgnored());
+    Assume.assumeFalse(shrinker.generatesDex() && vmVersionIgnored());
     Class mainClass = TestMain.class;
     String keep = !minify ? "-keep" : "-keep,allowobfuscation";
     List<String> config = ImmutableList.of(
@@ -189,10 +180,10 @@
     AndroidApp app = runShrinker(shrinker, CLASSES, config);
     assertEquals(
         StringUtils.withNativeLineSeparator("123451234567\nABC\n"),
-        run(app, mainClass.getCanonicalName()));
+        runOnVM(app, mainClass.getCanonicalName(), shrinker.toBackend()));
 
     CodeInspector codeInspector =
-        isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+        shrinker.isR8() ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
     ClassSubject testClass = codeInspector.clazz(TestClass.class);
     assertThat(testClass, isPresent());
 
@@ -200,7 +191,7 @@
     MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
-    if (isR8(shrinker)) {
+    if (shrinker.isR8()) {
       assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic());
     } else {
       assertFalse(staticMethod.getMethod().accessFlags.isPublic());
@@ -210,14 +201,14 @@
     staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
-    boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+    boolean publicizeCondition = shrinker.isR8() ? allowAccessModification
         : minify && repackagePrefix != null && allowAccessModification;
     assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
   }
 
   @Test
   public void test_keepPublic() throws Exception {
-    Assume.assumeFalse(generatesDex(shrinker) && vmVersionIgnored());
+    Assume.assumeFalse(shrinker.generatesDex() && vmVersionIgnored());
     Class mainClass = TestMain.class;
     String keep = !minify ? "-keep" : "-keep,allowobfuscation";
     Iterable<String> config = ImmutableList.of(
@@ -236,7 +227,7 @@
         "}",
         "-dontwarn java.lang.invoke.*"
     );
-    if (isR8(shrinker)) {
+    if (shrinker.isR8()) {
       config = Iterables.concat(config, ImmutableList.of(
           "-neverinline class " + TestClass.class.getCanonicalName() + " {",
           "  * staticMethod();",
@@ -247,10 +238,10 @@
     AndroidApp app = runShrinker(shrinker, CLASSES, config);
     assertEquals(
         StringUtils.withNativeLineSeparator("123451234567\nABC\n"),
-        run(app, mainClass.getCanonicalName()));
+        runOnVM(app, mainClass.getCanonicalName(), shrinker.toBackend()));
 
     CodeInspector codeInspector =
-        isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+        shrinker.isR8() ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
     ClassSubject testClass = codeInspector.clazz(TestClass.class);
     assertThat(testClass, isPresent());
 
@@ -262,7 +253,7 @@
     staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
-    boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+    boolean publicizeCondition = shrinker.isR8() ? allowAccessModification
         : minify && repackagePrefix != null && allowAccessModification;
     assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
   }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
new file mode 100644
index 0000000..e1c61c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
@@ -0,0 +1,166 @@
+// 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 com.android.tools.r8.naming.retrace;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceTest extends TestBase {
+  private Backend backend;
+  private CompilationMode mode;
+
+  @Parameters(name = "Backend: {0}, mode: {1}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (Backend backend : Backend.values()) {
+      for (CompilationMode mode : CompilationMode.values()) {
+        parameters.add(new Object[] {backend, mode});
+      }
+    }
+    return parameters;
+  }
+
+  public RetraceTest(Backend backend, CompilationMode mode) {
+    this.backend = backend;
+    this.mode = mode;
+  }
+
+  private List<String> retrace(String map, List<String> stackTrace) throws IOException {
+    Path t = temp.newFolder().toPath();
+    Path mapFile = t.resolve("map");
+    Path stackTraceFile = t.resolve("stackTrace");
+    FileUtils.writeTextFile(mapFile, map);
+    FileUtils.writeTextFile(stackTraceFile, stackTrace);
+    return StringUtils.splitLines(ToolHelper.runRetrace(mapFile, stackTraceFile));
+  }
+
+  private boolean isDalvik() {
+    return backend == Backend.DEX && ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
+  }
+
+  private List<String> extractStackTrace(ProcessResult result) {
+    ImmutableList.Builder<String> builder = ImmutableList.builder();
+    List<String> stderr = StringUtils.splitLines(result.stderr);
+    Iterator<String> iterator = stderr.iterator();
+
+    // A Dalvik stacktrace looks like this:
+    // W(209693) threadid=1: thread exiting with uncaught exception (group=0xf616cb20)  (dalvikvm)
+    // java.lang.NullPointerException
+    // \tat com.android.tools.r8.naming.retrace.Main.a(:133)
+    // \tat com.android.tools.r8.naming.retrace.Main.a(:139)
+    // \tat com.android.tools.r8.naming.retrace.Main.main(:145)
+    // \tat dalvik.system.NativeStart.main(Native Method)
+    //
+    // An Art 5.1.1 and 6.0.1 stacktrace looks like this:
+    // java.lang.NullPointerException: throw with null exception
+    // \tat com.android.tools.r8.naming.retrace.Main.a(:154)
+    // \tat com.android.tools.r8.naming.retrace.Main.a(:160)
+    // \tat com.android.tools.r8.naming.retrace.Main.main(:166)
+    //
+    // An Art 7.0.0 and latest stacktrace looks like this:
+    // Exception in thread "main" java.lang.NullPointerException: throw with null exception
+    // \tat com.android.tools.r8.naming.retrace.Main.a(:150)
+    // \tat com.android.tools.r8.naming.retrace.Main.a(:156)
+    // \tat com.android.tools.r8.naming.retrace.Main.main(:162)
+    int last = stderr.size();
+    if (isDalvik()) {
+      // Skip the bottom frame "dalvik.system.NativeStart.main".
+      last--;
+    }
+    // Take all lines from the bottom starting with "\tat ".
+    int first = last;
+    while (first - 1 >= 0 && stderr.get(first - 1).startsWith("\tat ")) {
+      first--;
+    }
+    for (int i = first; i < last; i++) {
+      builder.add(stderr.get(i));
+    }
+    return builder.build();
+  }
+
+  public void runTest(Class<?> mainClass, BiConsumer<List<String>, List<String>> checker)
+      throws Exception {
+    StringBuilder proguardMapBuilder = new StringBuilder();
+    AndroidApp output =
+        ToolHelper.runR8(
+            R8Command.builder()
+                .setMode(mode)
+                .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+                .addProguardConfiguration(
+                    ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+                .setProgramConsumer(emptyConsumer(backend))
+                .setProguardMapConsumer((string, ignore) -> proguardMapBuilder.append(string))
+                .build());
+
+    ProcessResult result = runOnVMRaw(output, mainClass, backend);
+    List<String> stackTrace = extractStackTrace(result);
+    List<String> retracesStackTrace = retrace(proguardMapBuilder.toString(), stackTrace);
+    checker.accept(stackTrace, retracesStackTrace);
+  }
+
+  @Test
+  public void test() throws Exception {
+    runTest(
+        Main.class,
+        (List<String> stackTrace, List<String> retracesStackTrace) -> {
+          assertEquals(
+              mode == CompilationMode.RELEASE, stackTrace.size() != retracesStackTrace.size());
+          if (mode == CompilationMode.DEBUG) {
+            assertThat(stackTrace.get(0), not(containsString("method2")));
+            assertThat(stackTrace.get(1), not(containsString("method1")));
+            assertThat(stackTrace.get(2), containsString("main"));
+          }
+          assertEquals(3, retracesStackTrace.size());
+          assertThat(retracesStackTrace.get(0), containsString("method2"));
+          assertThat(retracesStackTrace.get(1), containsString("method1"));
+          assertThat(retracesStackTrace.get(2), containsString("main"));
+        });
+  }
+}
+
+class Main {
+  public static void method2(int i) {
+    System.out.println("In method2");
+    throw null;
+  }
+
+  public static void method1(String s) {
+    System.out.println("In method1");
+    for (int i = 0; i < 10; i++) {
+      method2(Integer.parseInt(s));
+    }
+  }
+
+  public static void main(String[] args) {
+    System.out.println("In main");
+    method1("1");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
new file mode 100644
index 0000000..353952b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
@@ -0,0 +1,18 @@
+// 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 com.android.tools.r8.release;
+
+public class ShareCommonCodeOnDistinctPositionsTest {
+
+  public static void main(String[] args) {
+    int x;
+    int len = args.length;
+    if (len > 42) {
+      x = (len - 2) + len * 2;
+    } else {
+      x = (len - 2) + len * 2;
+    }
+    System.out.println(x);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
new file mode 100644
index 0000000..6bfd3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
@@ -0,0 +1,72 @@
+// 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 com.android.tools.r8.release;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.LineNumberTable;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ShareCommonCodeOnDistinctPositionsTestRunner extends TestBase {
+
+  private static final Class CLASS = ShareCommonCodeOnDistinctPositionsTest.class;
+
+  @Parameters
+  public static Backend[] parameters() {
+    return Backend.values();
+  }
+
+  private final Backend backend;
+
+  public ShareCommonCodeOnDistinctPositionsTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws CompilationFailedException, IOException, ExecutionException {
+    AndroidAppConsumers sink = new AndroidAppConsumers();
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addLibraryFiles(runtimeJar(backend))
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(CLASS))
+            .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+            .setDisableMinification(true)
+            .setDisableTreeShaking(true)
+            .build(),
+        options -> options.lineNumberOptimization = LineNumberOptimization.OFF);
+    CodeInspector inspector = new CodeInspector(sink.build());
+    MethodSubject method = inspector.clazz(CLASS).mainMethod();
+    // Check that the two shared lines are not in the output (they have no throwing instructions).
+    LineNumberTable lineNumberTable = method.getLineNumberTable();
+    IntCollection lines = lineNumberTable.getLines();
+    assertFalse(lines.contains(12));
+    assertFalse(lines.contains(14));
+    // Check that the two lines have been shared, e.g., there may be only one multiplication left.
+    assertEquals(
+        "Expected only one multiplcation due to instruction sharing.",
+        // TODO(b/117539423): Implement support for sharing optimizations in the CF backend.
+        backend == Backend.DEX ? 1 : 2,
+        Streams.stream(method.iterateInstructions())
+            .filter(InstructionSubject::isMultiplication)
+            .count());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
new file mode 100644
index 0000000..96ce54a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
@@ -0,0 +1,133 @@
+// 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 com.android.tools.r8.shaking;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeepAttributesTest extends TestBase {
+
+  private static final Class CLASS = TestKeepAttributes.class;
+
+  @Parameters(name = "{0}")
+  public static Backend[] parameters() {
+    return new Backend[] { Backend.CF, Backend.DEX};
+  }
+
+  private final Backend backend;
+
+  public KeepAttributesTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void keepAllAttributesInDebugMode()
+      throws ExecutionException, CompilationFailedException, IOException {
+    List<String> keepRules = ImmutableList.of(
+        "-keep class ** { *; }"
+    );
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.DEBUG);
+    assertTrue(mainMethod.hasLineNumberTable());
+    assertTrue(mainMethod.hasLocalVariableTable());
+  }
+
+  @Test
+  public void discardAllAttributes()
+      throws CompilationFailedException, IOException, ExecutionException {
+    List<String> keepRules = ImmutableList.of(
+        "-keep class ** { *; }"
+    );
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    assertFalse(mainMethod.hasLineNumberTable());
+    assertFalse(mainMethod.hasLocalVariableTable());
+  }
+
+  @Test
+  public void keepLineNumberTable()
+      throws CompilationFailedException, IOException, ExecutionException {
+    List<String> keepRules = ImmutableList.of(
+        "-keep class ** { *; }",
+        "-keepattributes " + ProguardKeepAttributes.LINE_NUMBER_TABLE
+    );
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    assertTrue(mainMethod.hasLineNumberTable());
+    assertFalse(mainMethod.hasLocalVariableTable());
+  }
+
+  @Test
+  public void keepLineNumberTableAndLocalVariableTable()
+      throws CompilationFailedException, IOException, ExecutionException {
+    List<String> keepRules = ImmutableList.of(
+        "-keep class ** { *; }",
+        "-keepattributes "
+            + ProguardKeepAttributes.LINE_NUMBER_TABLE
+            + ", "
+            + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
+    );
+    MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    assertTrue(mainMethod.hasLineNumberTable());
+    // Locals are never included in release builds.
+    assertFalse(mainMethod.hasLocalVariableTable());
+  }
+
+  @Test
+  public void keepLocalVariableTable() throws IOException, ExecutionException {
+    List<String> keepRules = ImmutableList.of(
+        "-keep class ** { *; }",
+        "-keepattributes " + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
+    );
+    // Compiling with a keep rule for locals but no line results in an error in R8.
+    try {
+      compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+    } catch (CompilationFailedException e) {
+      assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LOCAL_VARIABLE_TABLE));
+      assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LINE_NUMBER_TABLE));
+      return;
+    }
+    fail("Expected error");
+  }
+
+  private MethodSubject compileRunAndGetMain(List<String> keepRules, CompilationMode mode)
+      throws CompilationFailedException, IOException, ExecutionException {
+    AndroidAppConsumers sink = new AndroidAppConsumers();
+    R8.run(
+        R8Command.builder()
+            .setMode(mode)
+            .addProgramFiles(
+                ToolHelper.getClassFilesForTestDirectory(
+                    ToolHelper.getClassFileForTestClass(CLASS).getParent()))
+            .addLibraryFiles(runtimeJar(backend))
+            .addProguardConfiguration(keepRules, Origin.unknown())
+            .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+            .build());
+    AndroidApp app = sink.build();
+    CodeInspector codeInspector = new CodeInspector(app);
+    runOnVM(app, CLASS.getTypeName(), backend);
+    return codeInspector.clazz(CLASS).mainMethod();
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index f8907d6..1ee19bc 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
@@ -208,8 +207,7 @@
   public void parseNonJavaIdentifiers() throws Exception {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(dexItemFactory,
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(dexItemFactory, new Reporter());
     String nonJavaIdentifiers =
         String.join("\n", ImmutableList.of(
             "-keep class -package-.-ClassNameWithDash-{",
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 7e4c4ef..9f9b797 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -33,7 +33,6 @@
 import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -162,8 +161,7 @@
 
     // Check the Proguard compatibility rules generated.
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(),
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility && hasDefaultConstructor) {
@@ -283,8 +281,7 @@
 
     // Check the Proguard compatibility rules generated.
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(),
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
@@ -383,8 +380,7 @@
 
     // Check the Proguard compatibility rules generated.
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(),
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
@@ -489,8 +485,7 @@
 
     // Check the Proguard compatibility rules generated.
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(),
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
@@ -605,8 +600,7 @@
 
     // Check the Proguard compatibility configuration generated.
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(),
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
     parser.parse(proguardCompatibilityRules);
     System.out.println(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
@@ -657,8 +651,7 @@
     inspection.accept(new CodeInspector(app));
     // Check the Proguard compatibility configuration generated.
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(),
-            new Reporter(new DefaultDiagnosticsHandler()));
+        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     compatInspection.accept(configuration);
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index 44527a2..4ee3011 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -40,44 +40,59 @@
     R8_COMPAT,
     R8_COMPAT_CF,
     R8,
-    R8_CF
-  }
+    R8_CF;
 
-  protected static boolean isR8(Shrinker shrinker) {
-    return shrinker == Shrinker.R8_COMPAT
-        || shrinker == Shrinker.R8_COMPAT_CF
-        || shrinker == Shrinker.R8
-        || shrinker == Shrinker.R8_CF;
-  }
-
-  protected static boolean generatesDex(Shrinker shrinker) {
-    return shrinker == Shrinker.PROGUARD6_THEN_D8
-        || shrinker == Shrinker.R8_COMPAT
-        || shrinker == Shrinker.R8;
-  }
-
-  protected static boolean generatesCf(Shrinker shrinker) {
-    return shrinker == Shrinker.PROGUARD5
-        || shrinker == Shrinker.PROGUARD6
-        || shrinker == Shrinker.R8_COMPAT_CF
-        || shrinker == Shrinker.R8_CF;
-  }
-
-  protected static Backend toBackend(Shrinker shrinker) {
-    if (generatesDex(shrinker)) {
-      return Backend.DEX;
+    public boolean isR8() {
+      return this == R8_COMPAT
+          || this == R8_COMPAT_CF
+          || this == R8
+          || this == R8_CF;
     }
-    assert generatesCf(shrinker);
-    return Backend.CF;
+
+    public boolean generatesDex() {
+      return this == PROGUARD6_THEN_D8
+          || this == R8_COMPAT
+          || this == R8;
+    }
+
+    public boolean generatesCf() {
+      return this == PROGUARD5
+          || this == PROGUARD6
+          || this == R8_COMPAT_CF
+          || this == R8_CF;
+    }
+
+    public Backend toBackend() {
+      if (generatesDex()) {
+        return Backend.DEX;
+      }
+      assert generatesCf();
+      return Backend.CF;
+    }
   }
 
   protected AndroidApp runShrinker(
       Shrinker mode, List<Class> programClasses, Iterable<String> proguardConfigs)
       throws Exception {
-    return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguardConfigs));
+    return runShrinker(
+        mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), null);
   }
 
-  protected AndroidApp runShrinker(Shrinker mode, List<Class> programClasses, String proguardConfig)
+  protected AndroidApp runShrinker(
+      Shrinker mode,
+      List<Class> programClasses,
+      Iterable<String> proguardConfigs,
+      Consumer<InternalOptions> configure)
+      throws Exception {
+    return runShrinker(
+        mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), configure);
+  }
+
+  protected AndroidApp runShrinker(
+      Shrinker mode,
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> configure)
       throws Exception {
     proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
     switch (mode) {
@@ -86,74 +101,104 @@
       case PROGUARD6:
         return runProguard6(programClasses, proguardConfig, proguardMap);
       case PROGUARD6_THEN_D8:
-        return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
+        return runProguard6AndD8(programClasses, proguardConfig, proguardMap, configure);
       case R8_COMPAT:
-        return runR8Compat(programClasses, proguardConfig, Backend.DEX);
+        return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
       case R8_COMPAT_CF:
-        return runR8Compat(programClasses, proguardConfig, Backend.CF);
+        return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
       case R8:
-        return runR8(programClasses, proguardConfig, Backend.DEX);
+        return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
       case R8_CF:
-        return runR8(programClasses, proguardConfig, Backend.CF);
+        return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
     }
     throw new IllegalArgumentException("Unknown shrinker: " + mode);
   }
 
   protected CodeInspector inspectAfterShrinking(
-      Shrinker mode, List<Class> programClasses, List<String> proguardConfigs) throws Exception {
-    return inspectAfterShrinking(
-        mode, programClasses, String.join(System.lineSeparator(), proguardConfigs));
+      Shrinker mode, List<Class> programClasses, List<String> proguardConfigs)
+      throws Exception {
+    return inspectAfterShrinking(mode, programClasses, proguardConfigs, null);
   }
 
   protected CodeInspector inspectAfterShrinking(
-      Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+      Shrinker mode,
+      List<Class> programClasses,
+      List<String> proguardConfigs,
+      Consumer<InternalOptions> configure)
+      throws Exception {
+    return inspectAfterShrinking(
+        mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), configure);
+  }
+
+  protected CodeInspector inspectAfterShrinking(
+      Shrinker mode,
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> configure)
+      throws Exception {
     switch (mode) {
       case PROGUARD5:
         return inspectProguard5Result(programClasses, proguardConfig);
       case PROGUARD6:
         return inspectProguard6Result(programClasses, proguardConfig);
       case PROGUARD6_THEN_D8:
-        return inspectProguard6AndD8Result(programClasses, proguardConfig);
+        return inspectProguard6AndD8Result(programClasses, proguardConfig, configure);
       case R8_COMPAT:
-        return inspectR8CompatResult(programClasses, proguardConfig, Backend.DEX);
+        return inspectR8CompatResult(programClasses, proguardConfig, configure, Backend.DEX);
       case R8_COMPAT_CF:
-        return inspectR8CompatResult(programClasses, proguardConfig, Backend.CF);
+        return inspectR8CompatResult(programClasses, proguardConfig, configure, Backend.CF);
       case R8:
-        return inspectR8Result(programClasses, proguardConfig, Backend.DEX);
+        return inspectR8Result(programClasses, proguardConfig, configure, Backend.DEX);
       case R8_CF:
-        return inspectR8Result(programClasses, proguardConfig, Backend.CF);
+        return inspectR8Result(programClasses, proguardConfig, configure, Backend.CF);
     }
     throw new IllegalArgumentException("Unknown shrinker: " + mode);
   }
 
-  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
-      throws Exception {
-    return runR8(programClasses, proguardConfig, null, backend);
-  }
-
   protected AndroidApp runR8(
       List<Class> programClasses,
       String proguardConfig,
+      Path proguardMap,
+      Backend backend)
+      throws Exception {
+    return runR8(programClasses, proguardConfig, proguardMap, null, backend);
+  }
+
+  protected AndroidApp runR8(
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
       Consumer<InternalOptions> configure,
       Backend backend)
       throws Exception {
     AndroidApp app = readClassesAndRuntimeJar(programClasses, backend);
     R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
     ToolHelper.allowTestProguardOptions(builder);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    builder.addProguardConfiguration(
+        ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
     return ToolHelper.runR8(builder.build(), configure);
   }
 
   protected CodeInspector inspectR8Result(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return new CodeInspector(runR8(programClasses, proguardConfig, backend));
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> configure,
+      Backend backend)
+      throws Exception {
+    return new CodeInspector(runR8(programClasses, proguardConfig, null, configure, backend));
   }
 
   protected AndroidApp runR8Compat(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
+      Consumer<InternalOptions> configure,
+      Backend backend)
+      throws Exception {
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
     ToolHelper.allowTestProguardOptions(builder);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    builder.addProguardConfiguration(
+        ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
     programClasses.forEach(
         clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
     if (backend == Backend.DEX) {
@@ -164,12 +209,15 @@
       builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
       builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
     }
-    return ToolHelper.runR8(builder.build());
+    return ToolHelper.runR8(builder.build(), configure);
   }
 
   protected CodeInspector inspectR8CompatResult(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return new CodeInspector(runR8Compat(programClasses, proguardConfig, backend));
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> configure,
+      Backend backend) throws Exception {
+    return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, configure, backend));
   }
 
   protected AndroidApp runProguard5(
@@ -267,7 +315,11 @@
   }
 
   protected AndroidApp runProguard6AndD8(
-      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
+      Consumer<InternalOptions> configure)
+      throws Exception {
     Path proguardedJar =
         File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
     Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -281,14 +333,15 @@
     if (result.exitCode != 0) {
       fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
     }
-    return ToolHelper.runD8(readJar(proguardedJar));
+    return ToolHelper.runD8(readJar(proguardedJar), configure);
   }
 
   protected CodeInspector inspectProguard6AndD8Result(
-      List<Class> programClasses, String proguardConfig) throws Exception {
+      List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
+      throws Exception {
     proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
     return new CodeInspector(
-        runProguard6AndD8(programClasses, proguardConfig, proguardMap), proguardMap);
+        runProguard6AndD8(programClasses, proguardConfig, proguardMap, configure), proguardMap);
   }
 
   protected void verifyClassesPresent(
@@ -306,4 +359,8 @@
       assertThat(c, not(isPresent()));
     }
   }
+
+  private String toPrintMappingRule(Path proguardMap) {
+    return proguardMap == null ? "" : "-printmapping " + proguardMap.toAbsolutePath();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index fe2a93f..67417e9 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -264,7 +264,9 @@
   @Parameterized.Parameters(name = "Shrinker: {0}")
   public static Collection<Object> data() {
     return ImmutableList.of(
-        Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+        Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6,
+        Shrinker.R8_COMPAT, Shrinker.R8_COMPAT_CF,
+        Shrinker.R8, Shrinker.R8_CF);
   }
 
   public ExternalizableTest(Shrinker shrinker) {
@@ -273,11 +275,6 @@
 
   @Test
   public void testExternalizable() throws Exception {
-    // TODO(b/116735204): R8 should keep default ctor() of classes that implement Externalizable
-    if (isR8(shrinker)) {
-      return;
-    }
-
     String javaOutput = runOnJava(ExternalizableTestMain.class);
 
     List<String> config = ImmutableList.of(
@@ -294,12 +291,18 @@
     AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_EXTERNALIZABLE, config);
 
     // TODO(b/117302947): Need to update ART binary.
-    if (generatesCf(shrinker)) {
+    if (shrinker.generatesCf()) {
       String output = runOnVM(
-          processedApp, ExternalizableTestMain.class.getCanonicalName(), toBackend(shrinker));
+          processedApp, ExternalizableTestMain.class.getCanonicalName(), shrinker.toBackend());
       assertEquals(javaOutput.trim(), output.trim());
     }
 
+    // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+    //   1.11 The Externalizable Interface
+    //   ...
+    //   The class of an Externalizable object must do the following:
+    //   ...
+    //     * Have a public no-arg constructor
     CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
     ClassSubject classSubject = codeInspector.clazz(ExternalizableDataClass.class);
     assertThat(classSubject, isPresent());
@@ -307,14 +310,7 @@
     assertThat(init, isPresent());
   }
 
-  @Test
-  public void testSerializable() throws Exception {
-    // TODO(b/116735204): R8 should keep default ctor() of first non-serializable superclass of
-    // serializable class.
-    if (isR8(shrinker)) {
-      return;
-    }
-
+  private void testSerializable(boolean enableVerticalClassMerging) throws Exception {
     String javaOutput = runOnJava(SerializableTestMain.class);
 
     List<String> config = ImmutableList.of(
@@ -328,18 +324,50 @@
         "  java.lang.Object readResolve();",
         "}");
 
-    AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config);
+    AndroidApp processedApp =
+        runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config,
+            o -> o.enableVerticalClassMerging = enableVerticalClassMerging);
     // TODO(b/117302947): Need to update ART binary.
-    if (generatesCf(shrinker)) {
+    if (shrinker.generatesCf()) {
       String output = runOnVM(
-          processedApp, SerializableTestMain.class.getCanonicalName(), toBackend(shrinker));
+          processedApp, SerializableTestMain.class.getCanonicalName(), shrinker.toBackend());
       assertEquals(javaOutput.trim(), output.trim());
     }
 
+    // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+    //   1.10 The Serializable Interface
+    //   ...
+    //   A Serializable class must do the following:
+    //   ...
+    //     * Have access to the no-arg constructor of its first non-serializable superclass
     CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
-    ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+    ClassSubject classSubject;
+    if (shrinker.isR8() && enableVerticalClassMerging) {
+      // Vertical class merging.
+      classSubject = codeInspector.clazz(SerializableDataClass.class);
+    } else {
+      classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+    }
     assertThat(classSubject, isPresent());
     MethodSubject init = classSubject.init(ImmutableList.of());
     assertThat(init, isPresent());
   }
+
+  @Test
+  public void testSerializable_withVerticalClassMerging() throws Exception {
+    if (!shrinker.isR8()) {
+      // Already covered by the other tests.
+      return;
+    }
+    // TODO(b/117514095): Vertical class merging should preserve non/serializable behavior.
+    if (shrinker.isR8()) {
+      return;
+    }
+    testSerializable(true);
+  }
+
+  @Test
+  public void testSerializable_withoutVerticalClassMerging() throws Exception {
+    testSerializable(false);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index 777561d..a537d40 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -147,13 +147,14 @@
       Class mainClass, List<Class> programClasses, String proguardConfiguration,
       TriConsumer<Class, List<Class>, CodeInspector> r8Checker,
       TriConsumer<Class, List<Class>, CodeInspector> proguardChecker) throws Exception {
-    CodeInspector inspector = inspectR8CompatResult(programClasses, proguardConfiguration, backend);
+    CodeInspector inspector =
+        inspectR8CompatResult(programClasses, proguardConfiguration, null, backend);
     r8Checker.accept(mainClass, programClasses, inspector);
 
     if (isRunProguard()) {
       inspector = inspectProguard6Result(programClasses, proguardConfiguration);
       proguardChecker.accept(mainClass, programClasses, inspector);
-      inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration);
+      inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration, null);
       proguardChecker.accept(mainClass, programClasses, inspector);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index e6de7cc..e420e1e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -12,10 +12,12 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
@@ -44,11 +46,9 @@
     return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
   }
 
-  @Override
-  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
-      throws Exception {
+  private void configure(InternalOptions options) {
     // Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
-    return runR8(programClasses, proguardConfig, o -> o.enableInlining = false, backend);
+    options.enableInlining = false;
   }
 
   @Test
@@ -65,7 +65,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -94,7 +94,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -104,8 +104,8 @@
     assertTrue(methodSubject.getMethod().accessFlags.isPublic());
 
     classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    if (isR8(shrinker)) {
-      // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
+    if (shrinker.isR8()) {
+      // TODO(b/117330692): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
       // at the 2nd tree shaking.
       assertThat(classSubject, not(isPresent()));
       return;
@@ -133,7 +133,7 @@
         "  !public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -143,8 +143,8 @@
     assertTrue(methodSubject.getMethod().accessFlags.isPublic());
 
     classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    if (isR8(shrinker)) {
-      // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
+    if (shrinker.isR8()) {
+      // TODO(b/117330692): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
       // at the 2nd tree shaking.
       assertThat(classSubject, not(isPresent()));
       return;
@@ -173,7 +173,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -205,7 +205,7 @@
         "  !public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -219,8 +219,8 @@
     assertThat(methodSubject, not(isPresent()));
     methodSubject = classSubject.method(nonPublicMethod);
     assertThat(methodSubject, isPresent());
-    if (isR8(shrinker)) {
-      // TODO(b/72109068): if kept in the 1st tree shaking, should not be publicized.
+    if (shrinker.isR8()) {
+      // TODO(b/117330692): if kept in the 1st tree shaking, should not be publicized.
       assertTrue(methodSubject.getMethod().accessFlags.isPublic());
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 5893782..3a95a6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -19,6 +20,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -62,8 +64,13 @@
 
   @Override
   protected CodeInspector inspectR8Result(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> configure,
+      Backend backend)
+      throws Exception {
+    return super.inspectR8Result(
+        programClasses, adaptConfiguration(proguardConfig), configure, backend);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 25acb23..3f9c857 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -4,11 +4,13 @@
 package com.android.tools.r8.shaking.ifrule;
 
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -43,8 +45,12 @@
 
   @Override
   protected CodeInspector inspectR8Result(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> config,
+      Backend backend) throws Exception {
+    return super.inspectR8Result(
+        programClasses, adaptConfiguration(proguardConfig), config, backend);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index b003b7b..9cada6f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -88,12 +88,6 @@
     options.enableVerticalClassMerging = enableClassMerging;
   }
 
-  @Override
-  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
-      throws Exception {
-    return super.runR8(programClasses, proguardConfig, this::configure, backend);
-  }
-
   private void check(AndroidApp app) throws Exception {
     CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazzA = inspector.clazz(A.class);
@@ -132,7 +126,7 @@
         "-dontobfuscate"
     );
 
-    check(runShrinker(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config, this::configure));
   }
 
   @Test
@@ -148,7 +142,7 @@
         "-dontobfuscate"
     );
 
-    check(runShrinker(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config, this::configure));
   }
 
   @Test
@@ -164,6 +158,6 @@
         "-dontobfuscate"
     );
 
-    check(runShrinker(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config, this::configure));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index bdd8e1a..a2ab372 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.graph.invokesuper.Consumer;
@@ -85,8 +84,7 @@
         .add("  public static void main(java.lang.String[]);")
         .add("}")
         .add(additionalKeepRules);
-    String config = String.join(System.lineSeparator(), builder.build());
-    CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, builder.build());
     inspection.accept(inspector);
   }
 
@@ -103,8 +101,7 @@
       ClassSubject cls = inspector.clazz(clazz);
       assertThat(cls, isPresent());
       assertEquals(1, cls.asFoundClassSubject().allFields().size());
-      // TODD(116079696): This is a hack!
-      cls.forAllFields(field -> assertNotEquals(1, field.getFinalName().length()));
+      cls.forAllFields(field -> assertThat(field, not(isRenamed())));
     }
   }
 
@@ -115,8 +112,7 @@
       assertThat(cls, isPresent());
       assertThat(cls, isRenamed());
       assertEquals(1, cls.asFoundClassSubject().allFields().size());
-      // TODD(116079696): This is a hack!
-      cls.forAllFields(field -> assertEquals(1, field.getFinalName().length()));
+      cls.forAllFields(field -> assertThat(field, isRenamed()));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 1396be9..15e8803 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -78,4 +78,14 @@
   public String getFinalSignatureAttribute() {
     return null;
   }
+
+  @Override
+  public LineNumberTable getLineNumberTable() {
+    return null;
+  }
+
+  @Override
+  public boolean hasLocalVariableTable() {
+    return false;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 3203f9c..067280a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -24,6 +25,8 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.Opcodes;
 
@@ -40,6 +43,41 @@
   }
 
   @Override
+  public boolean isInstancePut() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTFIELD;
+  }
+
+  @Override
+  public boolean isStaticPut() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTSTATIC;
+  }
+
+  @Override
+  public boolean isInstanceGet() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETFIELD;
+  }
+
+  @Override
+  public boolean isStaticGet() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETSTATIC;
+  }
+
+  @Override
+  public DexField getField() {
+    assert isFieldAccess();
+    return ((CfFieldInstruction) instruction).getField();
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return instruction instanceof CfInvoke || instruction instanceof CfInvokeDynamic;
+  }
+
+  @Override
   public boolean isInvokeVirtual() {
     return instruction instanceof CfInvoke
         && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKEVIRTUAL;
@@ -58,6 +96,12 @@
   }
 
   @Override
+  public DexMethod getMethod() {
+    assert isInvoke();
+    return ((CfInvoke) instruction).getMethod();
+  }
+
+  @Override
   public boolean isNop() {
     return instruction instanceof CfNop;
   }
@@ -105,40 +149,11 @@
   }
 
   @Override
-  public boolean isInvoke() {
-    return instruction instanceof CfInvoke || instruction instanceof CfInvokeDynamic;
-  }
-
-  @Override
   public boolean isNewInstance() {
     return instruction instanceof CfNew;
   }
 
   @Override
-  public boolean isInstancePut() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTFIELD;
-  }
-
-  @Override
-  public boolean isStaticPut() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTSTATIC;
-  }
-
-  @Override
-  public boolean isInstanceGet() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETFIELD;
-  }
-
-  @Override
-  public boolean isStaticGet() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETSTATIC;
-  }
-
-  @Override
   public boolean isCheckCast() {
     return instruction instanceof CfCheckCast;
   }
@@ -198,4 +213,13 @@
   public boolean isLoad() {
     return instruction instanceof CfLoad;
   }
+
+  @Override
+  public boolean isMultiplication() {
+    if (!(instruction instanceof CfArithmeticBinop)) {
+      return false;
+    }
+    int opcode = ((CfArithmeticBinop) instruction).getAsmOpcode();
+    return Opcodes.IMUL <= opcode && opcode <= Opcodes.DMUL;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index bc31615..28404f0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -39,6 +39,7 @@
 import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
@@ -136,6 +137,23 @@
     return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
   }
 
+  String mapType(Map<String, String> mapping, String typeName) {
+    final String ARRAY_POSTFIX = "[]";
+    int arrayCount = 0;
+    while (typeName.endsWith(ARRAY_POSTFIX)) {
+      arrayCount++;
+      typeName = typeName.substring(0, typeName.length() - 2);
+    }
+    String mappedType = mapping.get(typeName);
+    if (mappedType == null) {
+      return null;
+    }
+    for (int i = 0; i < arrayCount; i++) {
+      mappedType += ARRAY_POSTFIX;
+    }
+    return mappedType;
+  }
+
   static <S, T extends Subject> void forAll(
       S[] items,
       BiFunction<S, FoundClassSubject, ? extends T> constructor,
@@ -248,12 +266,11 @@
   }
 
   String getObfuscatedTypeName(String originalTypeName) {
-    String obfuscatedType = null;
+    String obfuscatedTypeName = null;
     if (mapping != null) {
-      obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
+      obfuscatedTypeName = mapType(originalToObfuscatedMapping, originalTypeName);
     }
-    obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
-    return obfuscatedType;
+    return obfuscatedTypeName != null ? obfuscatedTypeName : originalTypeName;
   }
 
   InstructionSubject createInstructionSubject(Instruction instruction) {
@@ -321,7 +338,7 @@
     public String parsedTypeName(String name) {
       String type = name;
       if (originalToObfuscatedMapping != null) {
-        String original = originalToObfuscatedMapping.inverse().get(name);
+        String original = mapType(originalToObfuscatedMapping.inverse(), name);
         type = original != null ? original : name;
       }
       signature.append(type);
@@ -330,14 +347,17 @@
 
     @Override
     public String parsedInnerTypeName(String enclosingType, String name) {
-      String type;
+      String type = null;
       if (originalToObfuscatedMapping != null) {
         // The enclosingType has already been mapped if a mapping is present.
         String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
-        type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
-        if (type != null) {
-          assert type.startsWith(enclosingType + "$");
-          name = type.substring(enclosingType.length() + 1);
+        if (minifiedEnclosing != null) {
+          assert !minifiedEnclosing.contains("[");
+          type = mapType(originalToObfuscatedMapping.inverse(), minifiedEnclosing + "$" + name);
+          if (type != null) {
+            assert type.startsWith(enclosingType + "$");
+            name = type.substring(enclosingType.length() + 1);
+          }
         }
       } else {
         type = enclosingType + "$" + name;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 4ea699a..ca9ce6a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -46,6 +46,16 @@
 import com.android.tools.r8.code.IputObject;
 import com.android.tools.r8.code.IputShort;
 import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.MulDouble;
+import com.android.tools.r8.code.MulDouble2Addr;
+import com.android.tools.r8.code.MulFloat;
+import com.android.tools.r8.code.MulFloat2Addr;
+import com.android.tools.r8.code.MulInt;
+import com.android.tools.r8.code.MulInt2Addr;
+import com.android.tools.r8.code.MulIntLit16;
+import com.android.tools.r8.code.MulIntLit8;
+import com.android.tools.r8.code.MulLong;
+import com.android.tools.r8.code.MulLong2Addr;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.PackedSwitch;
@@ -67,6 +77,8 @@
 import com.android.tools.r8.code.SputShort;
 import com.android.tools.r8.code.SputWide;
 import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
 
 public class DexInstructionSubject implements InstructionSubject {
   protected final Instruction instruction;
@@ -81,6 +93,65 @@
   }
 
   @Override
+  public boolean isInstanceGet() {
+    return instruction instanceof Iget
+        || instruction instanceof IgetBoolean
+        || instruction instanceof IgetByte
+        || instruction instanceof IgetShort
+        || instruction instanceof IgetChar
+        || instruction instanceof IgetWide
+        || instruction instanceof IgetObject;
+  }
+
+  @Override
+  public boolean isInstancePut() {
+    return instruction instanceof Iput
+        || instruction instanceof IputBoolean
+        || instruction instanceof IputByte
+        || instruction instanceof IputShort
+        || instruction instanceof IputChar
+        || instruction instanceof IputWide
+        || instruction instanceof IputObject;
+  }
+
+  @Override
+  public boolean isStaticGet() {
+    return instruction instanceof Sget
+        || instruction instanceof SgetBoolean
+        || instruction instanceof SgetByte
+        || instruction instanceof SgetShort
+        || instruction instanceof SgetChar
+        || instruction instanceof SgetWide
+        || instruction instanceof SgetObject;
+  }
+
+  @Override
+  public boolean isStaticPut() {
+    return instruction instanceof Sput
+        || instruction instanceof SputBoolean
+        || instruction instanceof SputByte
+        || instruction instanceof SputShort
+        || instruction instanceof SputChar
+        || instruction instanceof SputWide
+        || instruction instanceof SputObject;
+  }
+
+  @Override
+  public DexField getField() {
+    assert isFieldAccess();
+    return instruction.getField();
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return isInvokeVirtual()
+        || isInvokeInterface()
+        || isInvokeDirect()
+        || isInvokeSuper()
+        || isInvokeStatic();
+  }
+
+  @Override
   public boolean isInvokeVirtual() {
     return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
   }
@@ -95,6 +166,20 @@
     return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
   }
 
+  public boolean isInvokeSuper() {
+    return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
+  }
+
+  public boolean isInvokeDirect() {
+    return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
+  }
+
+  @Override
+  public DexMethod getMethod() {
+    assert isInvoke();
+    return instruction.getMethod();
+  }
+
   @Override
   public boolean isNop() {
     return instruction instanceof Nop;
@@ -147,15 +232,6 @@
   }
 
   @Override
-  public boolean isInvoke() {
-    return isInvokeVirtual()
-        || isInvokeInterface()
-        || isInvokeDirect()
-        || isInvokeSuper()
-        || isInvokeStatic();
-  }
-
-  @Override
   public boolean isNewInstance() {
     return instruction instanceof NewInstance;
   }
@@ -170,63 +246,11 @@
     return isCheckCast() && ((CheckCast) instruction).getType().toString().equals(type);
   }
 
-  public boolean isInvokeSuper() {
-    return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
-  }
-
-  public boolean isInvokeDirect() {
-    return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
-  }
-
   public boolean isConst4() {
     return instruction instanceof Const4;
   }
 
   @Override
-  public boolean isInstanceGet() {
-    return instruction instanceof Iget
-        || instruction instanceof IgetBoolean
-        || instruction instanceof IgetByte
-        || instruction instanceof IgetShort
-        || instruction instanceof IgetChar
-        || instruction instanceof IgetWide
-        || instruction instanceof IgetObject;
-  }
-
-  @Override
-  public boolean isInstancePut() {
-    return instruction instanceof Iput
-        || instruction instanceof IputBoolean
-        || instruction instanceof IputByte
-        || instruction instanceof IputShort
-        || instruction instanceof IputChar
-        || instruction instanceof IputWide
-        || instruction instanceof IputObject;
-  }
-
-  @Override
-  public boolean isStaticGet() {
-    return instruction instanceof Sget
-        || instruction instanceof SgetBoolean
-        || instruction instanceof SgetByte
-        || instruction instanceof SgetShort
-        || instruction instanceof SgetChar
-        || instruction instanceof SgetWide
-        || instruction instanceof SgetObject;
-  }
-
-  @Override
-  public boolean isStaticPut() {
-    return instruction instanceof Sput
-        || instruction instanceof SputBoolean
-        || instruction instanceof SputByte
-        || instruction instanceof SputShort
-        || instruction instanceof SputChar
-        || instruction instanceof SputWide
-        || instruction instanceof SputObject;
-  }
-
-  @Override
   public boolean isIf() {
     return instruction instanceof IfEq
         || instruction instanceof IfEqz
@@ -251,4 +275,18 @@
   public boolean isSparseSwitch() {
     return instruction instanceof SparseSwitch;
   }
+
+  @Override
+  public boolean isMultiplication() {
+    return instruction instanceof MulInt
+        || instruction instanceof MulIntLit8
+        || instruction instanceof MulIntLit16
+        || instruction instanceof MulInt2Addr
+        || instruction instanceof MulFloat
+        || instruction instanceof MulFloat2Addr
+        || instruction instanceof MulLong
+        || instruction instanceof MulLong2Addr
+        || instruction instanceof MulDouble
+        || instruction instanceof MulDouble2Addr;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index ae42b14..e65a8b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -67,9 +67,9 @@
     //
     // whereas the final signature is for X.a is "a a"
     String obfuscatedType = signature.type;
-    String originalType = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedType);
+    String originalType =
+        codeInspector.mapType(codeInspector.originalToObfuscatedMapping.inverse(), obfuscatedType);
     String fieldType = originalType != null ? originalType : obfuscatedType;
-
     FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
 
     MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 3fe8186..f4f1660 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -4,10 +4,26 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.function.Predicate;
 
@@ -134,6 +150,89 @@
   }
 
   @Override
+  public boolean hasLocalVariableTable() {
+    Code code = getMethod().getCode();
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      if (dexCode.getDebugInfo() != null) {
+        for (DexString parameter : dexCode.getDebugInfo().parameters) {
+          if (parameter != null) {
+            return true;
+          }
+        }
+        for (DexDebugEvent event : dexCode.getDebugInfo().events) {
+          if (event instanceof DexDebugEvent.StartLocal) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+    if (code.isCfCode()) {
+      return !code.asCfCode().getLocalVariables().isEmpty();
+    }
+    if (code.isJarCode()) {
+      return code.asJarCode().getNode().localVariables != null
+          && !code.asJarCode().getNode().localVariables.isEmpty();
+    }
+    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+  }
+
+  @Override
+  public LineNumberTable getLineNumberTable() {
+    Code code = getMethod().getCode();
+    if (code.isDexCode()) {
+      return getDexLineNumberTable(code.asDexCode());
+    }
+    if (code.isCfCode()) {
+      return getCfLineNumberTable(code.asCfCode());
+    }
+    if (code.isJarCode()) {
+      return getJarLineNumberTable(code.asJarCode());
+    }
+    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+  }
+
+  private LineNumberTable getJarLineNumberTable(JarCode code) {
+    throw new Unimplemented("No support for inspecting the line number table for JarCode");
+  }
+
+  private LineNumberTable getCfLineNumberTable(CfCode code) {
+    int currentLine = -1;
+    Reference2IntMap<InstructionSubject> lineNumberTable =
+        new Reference2IntOpenHashMap<>(code.getInstructions().size());
+    for (CfInstruction insn : code.getInstructions()) {
+      if (insn instanceof CfPosition) {
+        currentLine = ((CfPosition) insn).getPosition().line;
+      }
+      if (currentLine != -1) {
+        lineNumberTable.put(new CfInstructionSubject(insn), currentLine);
+      }
+    }
+    return currentLine == -1 ? null : new LineNumberTable(lineNumberTable);
+  }
+
+  private LineNumberTable getDexLineNumberTable(DexCode code) {
+    DexDebugInfo debugInfo = code.getDebugInfo();
+    if (debugInfo == null) {
+      return null;
+    }
+    Reference2IntMap<InstructionSubject> lineNumberTable =
+        new Reference2IntOpenHashMap<>(code.instructions.length);
+    DexDebugPositionState state =
+        new DexDebugPositionState(debugInfo.startLine, getMethod().method);
+    Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
+    for (Instruction insn : code.instructions) {
+      int offset = insn.getOffset();
+      while (state.getCurrentPc() < offset && iterator.hasNext()) {
+        iterator.next().accept(state);
+      }
+      lineNumberTable.put(new DexInstructionSubject(insn), state.getCurrentLine());
+    }
+    return new LineNumberTable(lineNumberTable);
+  }
+
+  @Override
   public String toString() {
     return dexMethod.toSourceString();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index e7eaa60..6890684 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+
 public interface InstructionSubject {
 
   enum JumboStringMode {
@@ -13,12 +16,26 @@
 
   boolean isFieldAccess();
 
+  boolean isInstancePut();
+
+  boolean isStaticPut();
+
+  boolean isInstanceGet();
+
+  boolean isStaticGet();
+
+  DexField getField();
+
+  boolean isInvoke();
+
   boolean isInvokeVirtual();
 
   boolean isInvokeInterface();
 
   boolean isInvokeStatic();
 
+  DexMethod getMethod();
+
   boolean isNop();
 
   boolean isConstString(JumboStringMode jumboStringMode);
@@ -37,18 +54,8 @@
 
   boolean isThrow();
 
-  boolean isInvoke();
-
   boolean isNewInstance();
 
-  boolean isInstancePut();
-
-  boolean isStaticPut();
-
-  boolean isInstanceGet();
-
-  boolean isStaticGet();
-
   boolean isCheckCast();
 
   boolean isCheckCast(String type);
@@ -58,4 +65,6 @@
   boolean isPackedSwitch();
 
   boolean isSparseSwitch();
+
+  boolean isMultiplication();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
new file mode 100644
index 0000000..7c9c30f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
@@ -0,0 +1,19 @@
+// 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 com.android.tools.r8.utils.codeinspector;
+
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+
+public class LineNumberTable {
+  private final Reference2IntMap<InstructionSubject> lineNumberTable;
+
+  public LineNumberTable(Reference2IntMap<InstructionSubject> lineNumberTable) {
+    this.lineNumberTable = lineNumberTable;
+  }
+
+  public IntCollection getLines() {
+    return lineNumberTable.values();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 6eaaf5f..1e21552 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -32,4 +32,12 @@
       Predicate<InstructionSubject> filter) {
     return null;
   }
+
+  public boolean hasLineNumberTable() {
+    return getLineNumberTable() != null;
+  }
+
+  public abstract LineNumberTable getLineNumberTable();
+
+  public abstract boolean hasLocalVariableTable();
 }
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
index 4b7473e..5af6d4d 100755
--- a/tools/build_r8lib.py
+++ b/tools/build_r8lib.py
@@ -9,6 +9,7 @@
 '''
 
 import argparse
+import gradle
 import os
 import subprocess
 import toolhelper
@@ -16,39 +17,70 @@
 
 parser = argparse.ArgumentParser(description=__doc__.strip(),
                                  formatter_class=argparse.RawTextHelpFormatter)
-
-SAMPLE_JAR = os.path.join(utils.REPO_ROOT, 'tests/d8_api_usage_sample.jar')
-R8LIB_JAR = os.path.join(utils.LIBS, 'r8lib.jar')
-R8LIB_MAP_FILE = os.path.join(utils.LIBS, 'r8lib-map.txt')
+parser.add_argument('-e', '--exclude_deps', action='store_true',
+                    help='Create lib jar without dependencies')
+parser.add_argument('-k', '--keep', default=utils.R8LIB_KEEP_RULES,
+                    help='Keep rules file for lib')
+parser.add_argument('-n', '--no_relocate', action='store_true',
+                    help='Create lib jar without relocating libraries')
+parser.add_argument('-o', '--out', default=None,
+                    help='Output for built library')
+parser.add_argument('-t', '--target', default='r8',
+                    help='Compile target for library')
 
 API_LEVEL = 26
-ANDROID_JAR = 'third_party/android_jar/lib-v%s/android.jar' % API_LEVEL
+DEPS_JAR = os.path.join(utils.LIBS, 'deps.jar')
+SAMPLE_JAR = os.path.join(utils.REPO_ROOT, 'tests', 'd8_api_usage_sample.jar')
 
-
-def build_r8lib(output_path=None, output_map=None, **kwargs):
+def build_r8lib(target, exclude_deps, no_relocate, keep_rules_path,
+    output_path, **kwargs):
+  # Clean the build directory to ensure no repackaging of any existing
+  # lib or deps.
+  gradle.RunGradle(['clean'])
+  lib_args = [target]
+  deps_args = ['repackageDeps']
+  if exclude_deps:
+    lib_args.append('-Pexclude_deps')
+  if no_relocate:
+    lib_args.append('-Plib_no_relocate')
+    deps_args.append('-Plib_no_relocate')
+  # Produce the r8lib target to be processed later.
+  gradle.RunGradle(lib_args)
+  target_lib = os.path.join(utils.LIBS, target + '.jar')
+  temp_lib = os.path.join(utils.LIBS, target + '_to_process.jar')
+  os.rename(target_lib, temp_lib)
+  # Produce the dependencies needed for running r8 on lib.jar.
+  gradle.RunGradle(deps_args)
+  temp_deps = os.path.join(utils.LIBS, target + 'lib_deps.jar')
+  os.rename(DEPS_JAR, temp_deps)
+  # Produce R8 for compiling lib
   if output_path is None:
-    output_path = R8LIB_JAR
-  if output_map is None:
-    output_map = R8LIB_MAP_FILE
+    output_path = target + 'lib.jar'
+  output_map_path = os.path.splitext(output_path)[0] + '.map'
   toolhelper.run(
       'r8',
       ('--release',
        '--classfile',
        '--lib', utils.RT_JAR,
-       utils.R8_JAR,
+       '--lib', temp_deps,
+       temp_lib,
        '--output', output_path,
-       '--pg-conf', utils.R8LIB_KEEP_RULES,
-       '--pg-map-output', output_map),
+       '--pg-conf', keep_rules_path,
+       '--pg-map-output', output_map_path),
       **kwargs)
+  if exclude_deps:
+    return [output_path, temp_deps]
+  else:
+    return [output_path]
 
 
-def test_d8sample():
+def test_d8sample(paths):
   with utils.TempDir() as path:
-    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
             'com.android.tools.apiusagesample.D8ApiUsageSample',
             '--output', path,
             '--min-api', str(API_LEVEL),
-            '--lib', ANDROID_JAR,
+            '--lib', utils.get_android_jar(API_LEVEL),
             '--classpath', utils.R8_JAR,
             '--main-dex-list', '/dev/null',
             os.path.join(utils.BUILD, 'test/examples/hello.jar')]
@@ -56,30 +88,30 @@
     subprocess.check_call(args)
 
 
-def test_r8command():
+def test_r8command(paths):
   with utils.TempDir() as path:
-    # SAMPLE_JAR and R8LIB_JAR should not have any classes in common, since e.g.
-    # R8CommandParser should have been minified in R8LIB_JAR.
-    # Just in case R8CommandParser is also present in R8LIB_JAR, we put
+    # SAMPLE_JAR and LIB_JAR should not have any classes in common, since e.g.
+    # R8CommandParser should have been minified in LIB_JAR.
+    # Just in case R8CommandParser is also present in LIB_JAR, we put
     # SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
-    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
             'com.android.tools.r8.R8CommandParser',
             '--output', path + "/output.zip",
             '--min-api', str(API_LEVEL),
-            '--lib', ANDROID_JAR,
+            '--lib', utils.get_android_jar(API_LEVEL),
             '--main-dex-list', '/dev/null',
             os.path.join(utils.BUILD, 'test/examples/hello.jar')]
     utils.PrintCmd(args)
     subprocess.check_call(args)
 
 
-def test_r8cfcommand():
+def test_r8cfcommand(paths):
   with utils.TempDir() as path:
-    # SAMPLE_JAR and R8LIB_JAR should not have any classes in common, since e.g.
-    # R8CommandParser should have been minified in R8LIB_JAR.
-    # Just in case R8CommandParser is also present in R8LIB_JAR, we put
+    # SAMPLE_JAR and LIB_JAR should not have any classes in common, since e.g.
+    # R8CommandParser should have been minified in LIB_JAR.
+    # Just in case R8CommandParser is also present in LIB_JAR, we put
     # SAMPLE_JAR first on the classpath to use its version of R8CommandParser.
-    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, R8LIB_JAR),
+    args = ['java', '-cp', '%s:%s' % (SAMPLE_JAR, ":".join(paths)),
             'com.android.tools.r8.R8CommandParser',
             '--classfile',
             '--output', path + "/output.jar",
@@ -91,12 +123,16 @@
 
 def main():
   # Handle --help
-  parser.parse_args()
-
-  build_r8lib()
-  test_d8sample()
-  test_r8command()
-  test_r8cfcommand()
+  args = parser.parse_args()
+  output_paths = build_r8lib(
+      args.target, args.exclude_deps, args.no_relocate, args.keep, args.out)
+  if args.target == 'r8':
+    gradle.RunGradle(['buildExampleJars'])
+    test_r8command(output_paths)
+    test_r8cfcommand(output_paths)
+  if args.target == 'd8':
+    gradle.RunGradle(['buildExampleJars'])
+    test_d8sample(output_paths)
 
 
 if __name__ == '__main__':
diff --git a/tools/test.py b/tools/test.py
index b57d00f..72e7e01 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -156,6 +156,8 @@
     gradle_args.append('-PHEAD_sha1=' + utils.get_HEAD_sha1())
   # Add Gradle tasks
   gradle_args.append('cleanTest')
+  # Build R8lib with dependencies for bootstrapping tests.
+  gradle_args.append('r8libWithDeps')
   gradle_args.append('test')
   # Test filtering. Must always follow the 'test' task.
   for testFilter in args: