Merge "Ensure that golem does not have a daemon running"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 46f4876..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 =
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 22790b4..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,10 @@
     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);
   }
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/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/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/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/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index 5dcc7b8..ba7963b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -151,8 +151,8 @@
   MemberNaming lookupByOriginalItem(DexField field) {
     for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) {
       FieldSignature signature = entry.getKey();
-      if (signature.name.equals(field.name.toString())
-          && signature.type.equals(field.type.getName())) {
+      if (signature.name.equals(field.name.toSourceString())
+          && signature.type.equals(field.type.toSourceString())) {
         return entry.getValue();
       }
     }
@@ -162,11 +162,11 @@
   protected MemberNaming lookupByOriginalItem(DexMethod method) {
     for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) {
       MethodSignature signature = entry.getKey();
-      if (signature.name.equals(method.name.toString())
-          && signature.type.equals(method.proto.returnType.toString())
+      if (signature.name.equals(method.name.toSourceString())
+          && signature.type.equals(method.proto.returnType.toSourceString())
           && Arrays.equals(signature.parameters,
               Arrays.stream(method.proto.parameters.values)
-                  .map(DexType::toString).toArray(String[]::new))) {
+                  .map(DexType::toSourceString).toArray(String[]::new))) {
         return entry.getValue();
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 25df058..bbf8259 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -22,8 +22,8 @@
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -128,42 +128,80 @@
 
     private void applyMemberMapping(DexType from, ChainedClassNaming classNaming) {
       DexClass clazz = appInfo.definitionFor(from);
-      if (clazz == null) return;
+      if (clazz == null) {
+        return;
+      }
 
-      final Set<MemberNaming> appliedMemberNaming = Sets.newIdentityHashSet();
+      // We regard mappings as _complete_ if they cover literally everything, but that's too ideal.
+      // When visiting members with member mappings, obviously, there are two incomplete cases:
+      // no matched member or no matched mapping.
+      //
+      // 1. No matched member
+      // class A { // : X
+      //   void foo(); // : a
+      // }
+      //
+      // class B extends A { // : Y
+      //   @Override void foo(); // no mapping
+      // }
+      //
+      // For this case, we have chained class naming and move upward to search for super class's
+      // member mapping. One corner case we should be careful here is to resolve on the correct
+      // mapping, e.g.,
+      //
+      // class B extends A { // : Y
+      //   private void foo(); // no mapping, should be not renamed to a
+      // }
+
+      final Set<MemberNaming.Signature> appliedMemberSignature = new HashSet<>();
       clazz.forEachField(encodedField -> {
         MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
         if (memberNaming != null) {
-          appliedMemberNaming.add(memberNaming);
+          appliedMemberSignature.add(memberNaming.getOriginalSignature());
           applyFieldMapping(encodedField.field, memberNaming);
         }
       });
 
       clazz.forEachMethod(encodedMethod -> {
-        MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedMethod.method);
+        MemberNaming memberNaming =
+            classNaming.lookupByOriginalItem(encodedMethod.method, encodedMethod.isPrivateMethod());
         if (memberNaming != null) {
-          appliedMemberNaming.add(memberNaming);
+          appliedMemberSignature.add(memberNaming.getOriginalSignature());
           applyMethodMapping(encodedMethod.method, memberNaming);
         }
       });
 
+      // 2. No matched mapping
+      // class A { // : X
+      //   void foo(); // : a
+      // }
+      //
+      // class B extends A { // : Y
+      //   // no overriding, but has mapping: void foo() -> a
+      // }
+      //
       // We need to handle a class that extends another class where some members are not overridden,
       // resulting in absence of definitions. References to those members need to be redirected via
       // the lense as well.
+      // The caveat is, since such members don't exist, we pretend to see their definitions.
+      // We should ensure that they indeed don't exist. Otherwise, legitimately different members,
+      // e.g., private methods with same names, could be mapped to a wrong renamed name.
       classNaming.forAllFieldNaming(memberNaming -> {
-        if (!appliedMemberNaming.contains(memberNaming)) {
-          DexField pretendedOriginalField =
-              ((FieldSignature) memberNaming.getOriginalSignature())
-                  .toDexField(appInfo.dexItemFactory, from);
-          applyFieldMapping(pretendedOriginalField, memberNaming);
+        FieldSignature signature = (FieldSignature) memberNaming.getOriginalSignature();
+        if (!appliedMemberSignature.contains(signature)) {
+          DexField pretendedOriginalField = signature.toDexField(appInfo.dexItemFactory, from);
+          if (appInfo.definitionFor(pretendedOriginalField) == null) {
+            applyFieldMapping(pretendedOriginalField, memberNaming);
+          }
         }
       });
       classNaming.forAllMethodNaming(memberNaming -> {
-        if (!appliedMemberNaming.contains(memberNaming)) {
-          DexMethod pretendedOriginalMethod =
-              ((MethodSignature) memberNaming.getOriginalSignature())
-                  .toDexMethod(appInfo.dexItemFactory, from);
-          applyMethodMapping(pretendedOriginalMethod, memberNaming);
+        MethodSignature signature = (MethodSignature) memberNaming.getOriginalSignature();
+        if (!appliedMemberSignature.contains(signature)) {
+          DexMethod pretendedOriginalMethod = signature.toDexMethod(appInfo.dexItemFactory, from);
+          if (appInfo.definitionFor(pretendedOriginalMethod) == null) {
+            applyMethodMapping(pretendedOriginalMethod, memberNaming);
+          }
         }
       });
     }
@@ -229,6 +267,15 @@
       }
     }
 
+    protected MemberNaming lookupByOriginalItem(DexMethod method, boolean isPrivate) {
+      // If the current method is overridable, use chained mappings.
+      if (!isPrivate) {
+        return lookupByOriginalItem(method);
+      }
+      // Otherwise, just look up the current class's mappings only.
+      return super.lookupByOriginalItem(method);
+    }
+
     @Override
     protected MemberNaming lookupByOriginalItem(DexMethod method) {
       MemberNaming memberNaming = super.lookupByOriginalItem(method);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
index ce659e1..684c79b 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
@@ -19,20 +19,20 @@
 
   static ProguardMapError keptTypeWasRenamed(DexType type, String keptName, String rename) {
     return new ProguardMapError(
-        "Warning: " + type + createMessageForConflict(keptName, rename));
+        type + createMessageForConflict(keptName, rename));
   }
 
   static ProguardMapError keptMethodWasRenamed(DexMethod method, String keptName, String rename) {
     return new ProguardMapError(
-        "Warning: " + method.toSourceString() + createMessageForConflict(keptName, rename));
+        method.toSourceString() + createMessageForConflict(keptName, rename));
   }
 
   static ProguardMapError keptFieldWasRenamed(DexField field, String keptName, String rename) {
     return new ProguardMapError(
-        "Warning: " + field.toSourceString() + createMessageForConflict(keptName, rename));
+        field.toSourceString() + createMessageForConflict(keptName, rename));
   }
 
   private static String createMessageForConflict(String keptName, String rename) {
-    return " is not being kept as '" + keptName + "', but remapped to '" + rename + "'";
+    return " is not being kept as " + keptName + ", but remapped to " + rename;
   }
 }
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 0be9f91..e0cf415 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -291,6 +291,18 @@
     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(
       DexClass holder, Map<DexDefinition, ProguardKeepRule> dependentItems) {
     // Check if any dependent members are not static, and in that case enqueue the class as well.
@@ -697,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) {
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/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/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 5a1bd06..928058e 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -108,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/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index d7a5732..1eddbd3 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
@@ -29,7 +29,6 @@
 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,7 +76,6 @@
     assertThat(classSubject, isPresent());
   }
 
-  @Ignore("todo: jsjeon")
   @Test
   public void test_differentLocals() throws Throwable {
     ClassSubject classSubject = inspector.clazz(MAIN);
@@ -131,7 +129,6 @@
     );
   }
 
-  @Ignore("todo: jsjeon")
   @Test
   public void test_sameLocal() throws Throwable {
     ClassSubject classSubject = inspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
index b280242..9552a61 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.memberrebinding;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8;
@@ -16,6 +15,7 @@
 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;
@@ -24,26 +24,115 @@
 @RunWith(Parameterized.class)
 public class IndirectSuperInterfaceTest extends TestBase {
 
-  public interface Interface {
+  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 void foo() {
-      System.out.print("Interface::foo ");
+    default String method() {
+      return "InterfaceA::method";
     }
   }
 
-  public static class A implements Interface {
+  static class InterfaceASub implements InterfaceA {
     // Intentionally empty.
   }
 
-  public static class B extends A {
+  static class A extends InterfaceASub {
     @Override
-    public void foo() {
-      System.out.print("B::foo ");
-      super.foo();
+    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) {
-      new B().foo();
+      System.out.println(new A().method());
+      System.out.println(new B().method());
+      System.out.println(new C().method());
+      System.out.print(new D().method());
     }
   }
 
@@ -51,7 +140,7 @@
 
   @Parameters(name = "{0}")
   public static Backend[] setup() {
-    return new Backend[] {Backend.CF, Backend.DEX};
+    return Backend.values();
   }
 
   public IndirectSuperInterfaceTest(Backend backend) {
@@ -60,36 +149,38 @@
 
   @Test
   public void test() throws Exception {
-    String expected = "B::foo Interface::foo ";
-    String reference = runOnJava(B.class);
-    assertEquals(expected, reference);
+    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()
-            .addClassProgramData(ToolHelper.getClassAsBytes(Interface.class), Origin.unknown())
-            .addClassProgramData(ToolHelper.getClassAsBytes(A.class), Origin.unknown())
-            .addClassProgramData(ToolHelper.getClassAsBytes(B.class), Origin.unknown())
-            .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
-            .addLibraryFiles(runtimeJar(backend))
-            .addProguardConfiguration(
-                ImmutableList.of(
-                    "-keep class " + Interface.class.getTypeName(),
-                    "-keep class " + A.class.getTypeName(),
-                    keepMainProguardConfigurationWithInliningAnnotation(B.class)),
-                Origin.unknown());
+    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(), B.class, backend);
-
-    // TODO(b/117407667): Assert the test does not fail once fixed.
-    assertTrue(result.toString(), result.exitCode == (backend == Backend.DEX ? 0 : 1));
-    if (result.exitCode == 0) {
-      assertEquals(reference, result.stdout);
-    }
+    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/applymapping/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
new file mode 100644
index 0000000..102a6a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
@@ -0,0 +1,230 @@
+// 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.applymapping;
+
+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.containsString;
+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.AsmTestBase;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+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.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.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MemberResolutionAsmTest extends AsmTestBase {
+  private final Backend backend;
+
+  @Parameterized.Parameters(name = "backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MemberResolutionAsmTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  //  class HasMapping { // : X
+  //    HasMapping() {
+  //      foo();
+  //    }
+  //
+  //    void foo() { // : a
+  //      System.out.println("HasMapping#foo");
+  //    }
+  //  }
+  //
+  //  class NoMapping extends HasMapping { // : Y
+  //    NoMapping() {
+  //      super();
+  //      foo();
+  //    }
+  //
+  //    private void foo() { // no mapping
+  //      System.out.println("NoMapping#foo");
+  //    }
+  //  }
+  //
+  //  class NoMappingMain {
+  //    public static void main(String[] args) {
+  //      new NoMapping();
+  //    }
+  //  }
+  @Test
+  public void test_noMapping() throws Exception {
+    String main = "NoMappingMain";
+    AndroidApp input = buildAndroidApp(
+        HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+
+    Path mapPath = temp.newFile("test-mapping.txt").toPath();
+    List<String> pgMap = ImmutableList.of(
+        "HasMapping -> X:",
+        "  void foo() -> a",
+        "NoMapping -> Y:"
+        // Intentionally missing a mapping for `private` foo().
+    );
+    FileUtils.writeTextFile(mapPath, pgMap);
+
+    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+    builder
+        .addProguardConfiguration(
+            ImmutableList.of(
+                keepMainProguardConfiguration(main),
+                // Do not turn on -allowaccessmodification
+                "-applymapping " + mapPath,
+                "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+            Origin.unknown())
+        .addLibraryFiles(runtimeJar(backend));
+    AndroidApp processedApp =
+        ToolHelper.runR8(
+            builder.build(),
+            options -> {
+              options.enableInlining = false;
+              options.enableVerticalClassMerging = false;
+            });
+
+    List<byte[]> classBytes = ImmutableList.of(
+        HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+    ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+    assertEquals(0, outputBefore.exitCode);
+    String outputAfter = runOnVM(processedApp, main, backend);
+    assertEquals(outputBefore.stdout.trim(), outputAfter.trim());
+
+    CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+    ClassSubject base = codeInspector.clazz("HasMapping");
+    assertThat(base, isPresent());
+    assertThat(base, isRenamed());
+    assertEquals("X", base.getFinalName());
+    MethodSubject x = base.method("void", "foo", ImmutableList.of());
+    assertThat(x, isPresent());
+    assertThat(x, isRenamed());
+    assertEquals("a", x.getFinalName());
+
+    ClassSubject sub = codeInspector.clazz("NoMapping");
+    assertThat(sub, isPresent());
+    assertThat(sub, isRenamed());
+    assertEquals("Y", sub.getFinalName());
+    MethodSubject y = sub.method("void", "foo", ImmutableList.of());
+    assertThat(y, isPresent());
+    assertThat(y, not(isRenamed()));
+    assertEquals("foo", y.getFinalName());
+  }
+
+  //  class A { // : X
+  //    A() {
+  //      x();
+  //      y();
+  //    }
+  //
+  //    private void x() { // : y
+  //      System.out.println("A#x");
+  //    }
+  //
+  //    public void y() { // : x
+  //      System.out.println("A#y");
+  //    }
+  //  }
+  //
+  //  class B extends A { // : Y
+  //  }
+  //
+  //  class Main {
+  //    public static void main(String[] args) {
+  //      new B().x(); // IllegalAccessError
+  //    }
+  //  }
+  @Test
+  public void test_swapping() throws Exception {
+    String main = "Main";
+    AndroidApp input = buildAndroidApp(
+        ADump.dump(), BDump.dump(), MainDump.dump());
+
+    Path mapPath = temp.newFile("test-mapping.txt").toPath();
+    List<String> pgMap = ImmutableList.of(
+        "A -> X:",
+        "  void x() -> y",
+        "  void y() -> x",
+        "B -> Y:"
+        // Intentionally missing mappings for non-overridden members
+    );
+    FileUtils.writeTextFile(mapPath, pgMap);
+
+    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+    builder
+        .addProguardConfiguration(
+            ImmutableList.of(
+                keepMainProguardConfiguration(main),
+                // Do not turn on -allowaccessmodification
+                "-applymapping " + mapPath,
+                "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+            Origin.unknown())
+        .addLibraryFiles(runtimeJar(backend));
+    AndroidApp processedApp =
+        ToolHelper.runR8(
+            builder.build(),
+            options -> {
+              options.enableInlining = false;
+              options.enableVerticalClassMerging = false;
+            });
+
+    List<byte[]> classBytes = ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
+    ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+    assertNotEquals(0, outputBefore.exitCode);
+    String expectedErrorMessage = "IllegalAccessError";
+    String expectedErrorSignature = "A.x()V";
+    assertThat(outputBefore.stderr, containsString(expectedErrorMessage));
+    assertThat(outputBefore.stderr, containsString(expectedErrorSignature));
+    ProcessResult outputAfter = runOnVMRaw(processedApp, main, backend);
+    assertNotEquals(0, outputAfter.exitCode);
+    expectedErrorSignature = "X.y()V";
+    if (backend == Backend.DEX) {
+      expectedErrorSignature = "void X.y()";
+      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
+        expectedErrorMessage ="IncompatibleClassChangeError";
+      }
+      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+        expectedErrorMessage ="illegal method access";
+        expectedErrorSignature = "LX;.y ()V";
+      }
+    }
+    assertThat(outputAfter.stderr, containsString(expectedErrorMessage));
+    assertThat(outputAfter.stderr, containsString(expectedErrorSignature));
+
+    CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+    ClassSubject base = codeInspector.clazz("A");
+    assertThat(base, isPresent());
+    assertThat(base, isRenamed());
+    assertEquals("X", base.getFinalName());
+    MethodSubject x = base.method("void", "x", ImmutableList.of());
+    assertThat(x, isPresent());
+    assertThat(x, isRenamed());
+    assertEquals("y", x.getFinalName());
+
+    ClassSubject sub = codeInspector.clazz("B");
+    assertThat(sub, isPresent());
+    assertThat(sub, isRenamed());
+    assertEquals("Y", sub.getFinalName());
+    MethodSubject subX = sub.method("void", "x", ImmutableList.of());
+    assertThat(subX, not(isPresent()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
new file mode 100644
index 0000000..61e51be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
@@ -0,0 +1,155 @@
+// 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.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+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.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// AbstractChecker -> X:
+abstract class AbstractChecker {
+  // String tag -> p
+  private String tag = "PrivateInitialTag_AbstractChecker";
+
+  // check() -> x
+  private void check() {
+    System.out.println("AbstractChecker#check:" + tag);
+  }
+
+  // foo() -> a
+  protected void foo() {
+    check();
+  }
+}
+
+// ConcreteChecker -> Y:
+class ConcreteChecker extends AbstractChecker {
+  // This should not be conflict with AbstractChecker#tag due to the access control.
+  // String tag -> q
+  private String tag = "PrivateInitialTag_ConcreteChecker";
+
+  ConcreteChecker(String tag){
+    this.tag = tag;
+  }
+
+  // This should not be conflict with AbstractChecker#check due to the access control.
+  // check() -> y
+  private void check() {
+    System.out.println("ConcreteChecker#check:" + tag);
+  }
+
+  // foo() -> a
+  @Override
+  protected void foo() {
+    super.foo();
+    check();
+  }
+}
+
+class MemberResolutionTestMain {
+  public static void main(String[] args) {
+    ConcreteChecker c = new ConcreteChecker("NewTag");
+    c.foo();
+  }
+}
+
+@RunWith(Parameterized.class)
+public class MemberResolutionTest extends TestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(
+      AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class);
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MemberResolutionTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void testPrivateMethodsWithSameName() throws Exception {
+    String pkg = this.getClass().getPackage().getName();
+    Path mapPath = temp.newFile("test-mapping.txt").toPath();
+    List<String> pgMap = ImmutableList.of(
+        pkg + ".AbstractChecker -> " + pkg + ".X:",
+        "  java.lang.String tag -> p",
+        "  void check() -> x",
+        "  void foo() -> a",
+        pkg + ".ConcreteChecker -> " + pkg + ".Y:",
+        "  java.lang.String tag -> q",
+        "  void check() -> y",
+        "  void foo() -> a"
+    );
+    FileUtils.writeTextFile(mapPath, pgMap);
+
+    AndroidApp app = readClasses(CLASSES);
+    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
+    builder
+        .addProguardConfiguration(
+            ImmutableList.of(
+                keepMainProguardConfiguration(MemberResolutionTestMain.class),
+                // Do not turn on -allowaccessmodification
+                "-applymapping " + mapPath,
+                "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+            Origin.unknown())
+        .addLibraryFiles(runtimeJar(backend));
+    AndroidApp processedApp =
+        ToolHelper.runR8(
+            builder.build(),
+            options -> {
+              options.enableInlining = false;
+              options.enableVerticalClassMerging = false;
+            });
+
+    String outputBefore = runOnJava(MemberResolutionTestMain.class);
+    String outputAfter = runOnVM(processedApp, MemberResolutionTestMain.class, backend);
+    assertEquals(outputBefore, outputAfter);
+
+    CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+    ClassSubject base = codeInspector.clazz(AbstractChecker.class);
+    assertThat(base, isPresent());
+    FieldSubject p = base.field("java.lang.String", "tag");
+    assertThat(p, isPresent());
+    assertThat(p, isRenamed());
+    assertEquals("p", p.getFinalName());
+    MethodSubject x = base.method("void", "check", ImmutableList.of());
+    assertThat(x, isPresent());
+    assertThat(x, isRenamed());
+    assertEquals("x", x.getFinalName());
+
+    ClassSubject sub = codeInspector.clazz(ConcreteChecker.class);
+    assertThat(sub, isPresent());
+    FieldSubject q = sub.field("java.lang.String", "tag");
+    assertThat(q, isPresent());
+    assertThat(q, isRenamed());
+    assertEquals("q", q.getFinalName());
+    MethodSubject y = sub.method("void", "check", ImmutableList.of());
+    assertThat(y, isPresent());
+    assertThat(y, isRenamed());
+    assertEquals("y", y.getFinalName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
new file mode 100644
index 0000000..ae46c2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
@@ -0,0 +1,197 @@
+// 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.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+//  class HasMapping {
+//    HasMapping() {
+//      foo();
+//    }
+//
+//    void foo() {
+//      System.out.println("HasMapping#foo");
+//    }
+//  }
+class HasMappingDump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "HasMapping", null, "java/lang/Object", null);
+
+    classWriter.visitSource("HasMapping.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(2, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(3, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "HasMapping", "foo", "()V", false);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(4, label2);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "foo", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("HasMapping#foo");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+//  class NoMapping extends HasMapping {
+//    NoMapping() {
+//      super();
+//      bar();
+//    }
+//
+//    private void bar() {
+//      System.out.println("NoMapping#foo");
+//    }
+//  }
+//
+// then renamed bar() to foo() to introduce name clash.
+class NoMappingDump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "NoMapping", null, "HasMapping", null);
+
+    classWriter.visitSource("NoMapping.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(12, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "HasMapping", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(13, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "foo", "()V", false);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(14, label2);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(9, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("NoMapping#foo");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(10, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+//  public class NoMappingMain {
+//    public static void main(String[] args) {
+//      new NoMapping();
+//    }
+//  }
+class NoMappingMainDump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8, ACC_PUBLIC | ACC_SUPER, "NoMappingMain", null, "java/lang/Object", null);
+
+    classWriter.visitSource("NoMappingMain.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(19, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(
+          ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(22, label0);
+      methodVisitor.visitTypeInsn(NEW, "NoMapping");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "<init>", "()V", false);
+      methodVisitor.visitInsn(POP);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(23, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
new file mode 100644
index 0000000..0dea699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
@@ -0,0 +1,191 @@
+// 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.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+//  class A {
+//    A() {
+//      x();
+//      y();
+//    }
+//
+//    private void x() {
+//      System.out.println("A#x");
+//    }
+//
+//    public void y() {
+//      System.out.println("A#y");
+//    }
+//  }
+class ADump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Test.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(2, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(3, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "x", "()V", false);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(4, label2);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "A", "y", "()V", false);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(5, label3);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "x", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(7, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("A#x");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(8, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "y", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(11, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitLdcInsn("A#y");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(12, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+//  class B extends A {
+//  }
+class BDump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "B", null, "A", null);
+
+    classWriter.visitSource("Test.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(15, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+//  public class Main {
+//    public static void main(String[] args) {
+//      new B().y();
+//    }
+//  }
+//
+// then replaced use of y() with x() to introduce IllegalAccessError.
+class MainDump implements Opcodes {
+
+  public static byte[] dump () throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Test.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(
+          ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(20, label0);
+      methodVisitor.visitTypeInsn(NEW, "B");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "x", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(21, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
+
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/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index cf14d8b..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
@@ -74,10 +74,25 @@
   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,42 +101,56 @@
       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, proguardMap, Backend.DEX);
+        return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
       case R8_COMPAT_CF:
-        return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.CF);
+        return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
       case R8:
-        return runR8(programClasses, proguardConfig, proguardMap, Backend.DEX);
+        return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
       case R8_CF:
-        return runR8(programClasses, proguardConfig, proguardMap, 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);
   }
@@ -151,14 +180,19 @@
   }
 
   protected CodeInspector inspectR8Result(
-      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
-    return new CodeInspector(runR8(programClasses, proguardConfig, null, 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,
       Path proguardMap,
+      Consumer<InternalOptions> configure,
       Backend backend)
       throws Exception {
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
@@ -175,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, null, 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(
@@ -278,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();
@@ -292,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(
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 e20b80a..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
@@ -310,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 (shrinker.isR8()) {
-      return;
-    }
-
+  private void testSerializable(boolean enableVerticalClassMerging) throws Exception {
     String javaOutput = runOnJava(SerializableTestMain.class);
 
     List<String> config = ImmutableList.of(
@@ -331,7 +324,9 @@
         "  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 (shrinker.generatesCf()) {
       String output = runOnVM(
@@ -346,9 +341,33 @@
     //   ...
     //     * 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 89d5079..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,6 +12,7 @@
 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;
@@ -45,13 +46,9 @@
     return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
   }
 
-  @Override
-  protected AndroidApp runR8(
-      List<Class> programClasses, String proguardConfig, Path proguardMap, 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, proguardMap, o -> o.enableInlining = false, backend);
+    options.enableInlining = false;
   }
 
   @Test
@@ -68,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);
@@ -97,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);
@@ -136,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);
@@ -176,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);
@@ -208,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);
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 165a68f..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,13 +88,6 @@
     options.enableVerticalClassMerging = enableClassMerging;
   }
 
-  @Override
-  protected AndroidApp runR8(
-      List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
-      throws Exception {
-    return super.runR8(programClasses, proguardConfig, proguardMap, this::configure, backend);
-  }
-
   private void check(AndroidApp app) throws Exception {
     CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazzA = inspector.clazz(A.class);
@@ -133,7 +126,7 @@
         "-dontobfuscate"
     );
 
-    check(runShrinker(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config, this::configure));
   }
 
   @Test
@@ -149,7 +142,7 @@
         "-dontobfuscate"
     );
 
-    check(runShrinker(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config, this::configure));
   }
 
   @Test
@@ -165,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 51c2c35..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
@@ -84,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);
   }
 
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 adee8cf..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
@@ -80,8 +80,8 @@
   }
 
   @Override
-  public boolean hasLineNumberTable() {
-    return false;
+  public LineNumberTable getLineNumberTable() {
+    return null;
   }
 
   @Override
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 dedb839..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;
@@ -212,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/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e1d74c1..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;
@@ -265,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/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index f205594..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
@@ -6,20 +6,26 @@
 
 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.ListIterator;
 import java.util.function.Predicate;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.LineNumberNode;
 
 public class FoundMethodSubject extends MethodSubject {
 
@@ -144,40 +150,6 @@
   }
 
   @Override
-  public boolean hasLineNumberTable() {
-    Code code = getMethod().getCode();
-    if (code.isDexCode()) {
-      DexCode dexCode = code.asDexCode();
-      if (dexCode.getDebugInfo() != null) {
-        for (DexDebugEvent event : dexCode.getDebugInfo().events) {
-          if (event instanceof DexDebugEvent.Default) {
-            return true;
-          }
-        }
-      }
-      return false;
-    }
-    if (code.isCfCode()) {
-      for (CfInstruction insn : code.asCfCode().getInstructions()) {
-        if (insn instanceof CfPosition) {
-          return true;
-        }
-      }
-      return false;
-    }
-    if (code.isJarCode()) {
-      ListIterator<AbstractInsnNode> it = code.asJarCode().getNode().instructions.iterator();
-      while (it.hasNext()) {
-        if (it.next() instanceof LineNumberNode) {
-          return true;
-        }
-      }
-      return false;
-    }
-    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
-  }
-
-  @Override
   public boolean hasLocalVariableTable() {
     Code code = getMethod().getCode();
     if (code.isDexCode()) {
@@ -207,6 +179,60 @@
   }
 
   @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 fb0e0d3..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
@@ -65,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 55030ca..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
@@ -33,7 +33,11 @@
     return null;
   }
 
-  public abstract boolean hasLineNumberTable();
+  public boolean hasLineNumberTable() {
+    return getLineNumberTable() != null;
+  }
+
+  public abstract LineNumberTable getLineNumberTable();
 
   public abstract boolean hasLocalVariableTable();
 }