Merge commit '073b18530351f39db03e7181dafcbba6558de885' into dev-release

Change-Id: If418b5d8e07f9b6ce9f0b0fbbe610bd094dce019
diff --git a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
index 1be40fe..167d305 100644
--- a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
@@ -50,7 +50,7 @@
     extends TreeFixerBase {
 
   private final ClassMergerSharedData classMergerSharedData;
-  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+  protected final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   protected final LB lensBuilder;
   protected final MC mergedClasses;
 
@@ -81,14 +81,17 @@
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     preprocess();
     Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
-    Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
+    Set<DexProgramClass> seen = Sets.newIdentityHashSet();
+    Iterables.filter(classes, DexProgramClass::isInterface)
+        .forEach(itf -> fixupInterfaceClass(itf, seen));
     classes.forEach(this::fixupAttributes);
     classes.forEach(this::fixupProgramClassSuperTypes);
 
     // TODO(b/170078037): parallelize this code segment.
     for (DexProgramClass root : getRoots()) {
-      traverseProgramClassesDepthFirst(root, new DexMethodSignatureBiMap<>());
+      traverseProgramClassesDepthFirst(root, seen, new DexMethodSignatureBiMap<>());
     }
+    assert seen.containsAll(appView.appInfo().classes());
     postprocess();
     GL lens = lensBuilder.build(appViewWithLiveness, mergedClasses);
     new AnnotationFixer(appView, lens).run(appView.appInfo().classes(), executorService);
@@ -99,35 +102,27 @@
   private List<DexProgramClass> getRoots() {
     List<DexProgramClass> roots = new ArrayList<>();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (!clazz.isInterface() && !mergedClasses.isMergeSource(clazz.getType())) {
-        DexProgramClass superClass =
-            asProgramClassOrNull(appView.definitionFor(clazz.getSuperType(), clazz));
-        if (superClass == null) {
-          roots.add(clazz);
-        }
+      if (isRoot(clazz)) {
+        roots.add(clazz);
       }
     }
     return roots;
   }
 
-  private void traverseProgramClassesDepthFirst(
-      DexProgramClass clazz, DexMethodSignatureBiMap<DexMethodSignature> state) {
-    DexMethodSignatureBiMap<DexMethodSignature> newState = fixupProgramClass(clazz, state);
-    for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
-      traverseProgramClassesDepthFirst(subclass, newState);
+  protected boolean isRoot(DexProgramClass clazz) {
+    if (clazz.isInterface()) {
+      return false;
     }
-    if (mergedClasses.isMergeTarget(clazz.getType())) {
-      for (DexType sourceType : mergedClasses.getSourcesFor(clazz.getType())) {
-        DexProgramClass sourceClass = appView.definitionFor(sourceType).asProgramClass();
-        for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(sourceClass)) {
-          if (subclass != clazz) {
-            traverseProgramClassesDepthFirst(subclass, newState);
-          }
-        }
-      }
-    }
+    DexProgramClass superClass =
+        asProgramClassOrNull(appView.definitionFor(clazz.getSuperType(), clazz));
+    return superClass == null;
   }
 
+  protected abstract void traverseProgramClassesDepthFirst(
+      DexProgramClass clazz,
+      Set<DexProgramClass> seen,
+      DexMethodSignatureBiMap<DexMethodSignature> state);
+
   public void preprocess() {
     // Intentionally empty.
   }
@@ -161,7 +156,7 @@
     clazz.setInterfaces(fixupInterfaces(clazz, clazz.getInterfaces()));
   }
 
-  private DexMethodSignatureBiMap<DexMethodSignature> fixupProgramClass(
+  protected DexMethodSignatureBiMap<DexMethodSignature> fixupProgramClass(
       DexProgramClass clazz, DexMethodSignatureBiMap<DexMethodSignature> remappedVirtualMethods) {
     assert !clazz.isInterface();
 
@@ -235,7 +230,8 @@
         : method;
   }
 
-  private void fixupInterfaceClass(DexProgramClass iface) {
+  private void fixupInterfaceClass(DexProgramClass iface, Set<DexProgramClass> seen) {
+    assert seen.add(iface);
     DexMethodSignatureBiMap<DexMethodSignature> remappedVirtualMethods =
         DexMethodSignatureBiMap.empty();
     MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures =
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 3f3aa1e..7288292 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -146,7 +146,7 @@
           if ((b & 0xC0) == 0x80) {
             return (char) (((a & 0x1F) << 6) | (b & 0x3F));
           }
-          throw new UTFDataFormatException("bad second byte");
+          throw badSecondByteException(a, b);
         }
         if ((a & 0xf0) == 0xe0) {
           int b = content[i++] & 0xff;
@@ -154,9 +154,9 @@
           if ((b & 0xC0) == 0x80 && (c & 0xC0) == 0x80) {
             return (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
           }
-          throw new UTFDataFormatException("bad second or third byte");
+          throw badSecondOrThirdByteException(a, b, c);
         }
-        throw new UTFDataFormatException("bad byte");
+        throw badByteException(a);
       }
 
       @Override
@@ -221,7 +221,7 @@
       } else if ((a & 0xe0) == 0xc0) {
         int b = content[p++] & 0xff;
         if ((b & 0xC0) != 0x80) {
-          throw new UTFDataFormatException("bad second byte");
+          throw badSecondByteException(a, b);
         }
         out[s] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
         if (++s == prefixLength) {
@@ -231,14 +231,14 @@
         int b = content[p++] & 0xff;
         int c = content[p++] & 0xff;
         if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
-          throw new UTFDataFormatException("bad second or third byte");
+          throw badSecondOrThirdByteException(a, b, c);
         }
         out[s] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
         if (++s == prefixLength) {
           return s;
         }
       } else {
-        throw new UTFDataFormatException("bad byte");
+        throw badByteException(a);
       }
     }
   }
@@ -260,18 +260,18 @@
       } else if ((a & 0xe0) == 0xc0) {
         int b = content[p++] & 0xff;
         if ((b & 0xC0) != 0x80) {
-          throw new UTFDataFormatException("bad second byte");
+          throw badSecondByteException(a, b);
         }
         h = 31 * h + (char) (((a & 0x1F) << 6) | (b & 0x3F));
       } else if ((a & 0xf0) == 0xe0) {
         int b = content[p++] & 0xff;
         int c = content[p++] & 0xff;
         if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
-          throw new UTFDataFormatException("bad second or third byte");
+          throw badSecondOrThirdByteException(a, b, c);
         }
         h = 31 * h + (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
       } else {
-        throw new UTFDataFormatException("bad byte");
+        throw badByteException(a);
       }
     }
 
@@ -279,6 +279,30 @@
     return h;
   }
 
+  private UTFDataFormatException badByteException(int a) {
+    return new UTFDataFormatException("bad byte: " + Integer.toHexString((char) (a & 0xff)) + ")");
+  }
+
+  private UTFDataFormatException badSecondByteException(int a, int b) {
+    return new UTFDataFormatException(
+        "bad second byte (first: "
+            + Integer.toHexString((char) (a & 0xff))
+            + ", second: "
+            + Integer.toHexString((char) (b & 0xff))
+            + ")");
+  }
+
+  private UTFDataFormatException badSecondOrThirdByteException(int a, int b, int c) {
+    return new UTFDataFormatException(
+        "bad second or third byte (first: "
+            + Integer.toHexString((char) (a & 0xff))
+            + ", second: "
+            + Integer.toHexString((char) (b & 0xff))
+            + ", third: "
+            + Integer.toHexString((char) (c & 0xff))
+            + ")");
+  }
+
   // Inspired from /dex/src/main/java/com/android/dex/Mutf8.java
   private static int countBytes(String string) {
     // We need an extra byte for the terminating '0'.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
index 24efc9a..6c85ffa 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -39,7 +39,7 @@
 public class ConstructorEntryPoint extends SyntheticSourceCode {
 
   private final DexField classIdField;
-  private final int extraNulls;
+  private final int numberOfUnusedArguments;
   private final ProgramMethod method;
   private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
 
@@ -47,11 +47,11 @@
       Int2ReferenceSortedMap<DexMethod> typeConstructors,
       ProgramMethod method,
       DexField classIdField,
-      int extraNulls,
+      int numberOfUnusedArguments,
       Position position) {
     super(method, position);
     this.classIdField = classIdField;
-    this.extraNulls = extraNulls;
+    this.numberOfUnusedArguments = numberOfUnusedArguments;
     this.method = method;
     this.typeConstructors = typeConstructors;
   }
@@ -67,7 +67,7 @@
               builder.hasArgumentValues()
                   ? (builder.getArgumentValues().size()
                       - BooleanUtils.intValue(typeConstructors.size() > 1)
-                      - extraNulls)
+                      - numberOfUnusedArguments)
                   : 0;
           int newNumberOfNonReceiverArguments = typeConstructor.getArity();
           List<Value> arguments = new ArrayList<>(newNumberOfNonReceiverArguments + 1);
@@ -106,7 +106,7 @@
   protected void prepareMultiConstructorInstructions() {
     int typeConstructorCount = typeConstructors.size();
     // The class id register is always the first synthetic argument.
-    int classIdRegister = getParamRegister(method.getArity() - 1 - extraNulls);
+    int classIdRegister = getParamRegister(method.getArity() - 1 - numberOfUnusedArguments);
     if (hasClassIdField()) {
       addRegisterClassIdAssignment(classIdRegister);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
index 794ca05..e089509 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
@@ -7,8 +7,13 @@
 import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.classmerging.ClassMergerTreeFixer;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -17,7 +22,7 @@
  * have been remapped to new classes by the class merger. While doing so, all updated changes are
  * tracked in {@link HorizontalClassMergerTreeFixer#lensBuilder}.
  */
-class HorizontalClassMergerTreeFixer
+public class HorizontalClassMergerTreeFixer
     extends ClassMergerTreeFixer<
         HorizontalClassMergerGraphLens.Builder,
         HorizontalClassMergerGraphLens,
@@ -90,4 +95,26 @@
       throws ExecutionException {
     return super.run(executorService, timing);
   }
+
+  @Override
+  protected void traverseProgramClassesDepthFirst(
+      DexProgramClass clazz,
+      Set<DexProgramClass> seen,
+      DexMethodSignatureBiMap<DexMethodSignature> state) {
+    assert seen.add(clazz);
+    if (mergedClasses.isMergeSource(clazz.getType())) {
+      assert !clazz.hasMethodsOrFields();
+      return;
+    }
+    DexMethodSignatureBiMap<DexMethodSignature> newState = fixupProgramClass(clazz, state);
+    for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
+      traverseProgramClassesDepthFirst(subclass, seen, newState);
+    }
+    for (DexType sourceType : mergedClasses.getSourcesFor(clazz.getType())) {
+      DexProgramClass sourceClass = appView.definitionFor(sourceType).asProgramClass();
+      for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(sourceClass)) {
+        traverseProgramClassesDepthFirst(subclass, seen, newState);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
index d727424..c006a41 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
@@ -41,7 +41,7 @@
 public class IncompleteMergedInstanceInitializerCode extends IncompleteHorizontalClassMergerCode {
 
   private final DexField classIdField;
-  private int extraNulls;
+  private int numberOfUnusedArguments;
 
   private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre;
   private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost;
@@ -51,13 +51,13 @@
 
   IncompleteMergedInstanceInitializerCode(
       DexField classIdField,
-      int extraNulls,
+      int numberOfUnusedArguments,
       Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre,
       Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost,
       DexMethod parentConstructor,
       List<InstanceFieldInitializationInfo> parentConstructorArguments) {
     this.classIdField = classIdField;
-    this.extraNulls = extraNulls;
+    this.numberOfUnusedArguments = numberOfUnusedArguments;
     this.instanceFieldAssignmentsPre = instanceFieldAssignmentsPre;
     this.instanceFieldAssignmentsPost = instanceFieldAssignmentsPost;
     this.parentConstructor = parentConstructor;
@@ -66,7 +66,7 @@
 
   @Override
   public void addExtraUnusedArguments(int numberOfUnusedArguments) {
-    this.extraNulls += numberOfUnusedArguments;
+    this.numberOfUnusedArguments += numberOfUnusedArguments;
   }
 
   @Override
@@ -107,7 +107,7 @@
 
     // Assign class id.
     if (classIdField != null) {
-      Value classIdValue = argumentValues.get(argumentValues.size() - 1 - extraNulls);
+      Value classIdValue = argumentValues.get(argumentValues.size() - 1 - numberOfUnusedArguments);
       lirBuilder.addInstancePut(
           lens.getNextFieldSignature(classIdField), receiverValue, classIdValue);
       instructionIndex++;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index aa4bce4..f40e17e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -67,12 +67,10 @@
   }
 
   public IncompleteMergedInstanceInitializerCode createCode(
-      HorizontalMergeGroup group,
-      boolean hasClassId,
-      int extraNulls) {
+      HorizontalMergeGroup group, boolean hasClassId, int numberOfUnusedArguments) {
     return new IncompleteMergedInstanceInitializerCode(
         hasClassId ? group.getClassIdField() : null,
-        extraNulls,
+        numberOfUnusedArguments,
         instanceFieldAssignmentsPre,
         instanceFieldAssignmentsPost,
         parentConstructor,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 559f14f..256971c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -251,17 +251,16 @@
         Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
   }
 
-  private Code getNewCode(
-      boolean needsClassId,
-      int extraNulls) {
+  private Code getNewCode(boolean needsClassId, int numberOfUnusedArguments) {
     if (hasInstanceInitializerDescription()) {
-      return instanceInitializerDescription.createCode(group, needsClassId, extraNulls);
+      return instanceInitializerDescription.createCode(
+          group, needsClassId, numberOfUnusedArguments);
     }
     assert useSyntheticMethod();
     return new ConstructorEntryPointSynthesizedCode(
         createClassIdToInstanceInitializerMap(),
         group.hasClassIdField() ? group.getClassIdField() : null,
-        extraNulls);
+        numberOfUnusedArguments);
   }
 
   private boolean isSingleton() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
index b0cfa19..7d8a826 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
@@ -37,21 +37,21 @@
 public class ConstructorEntryPointSynthesizedCode extends IncompleteHorizontalClassMergerCode {
 
   private final DexField classIdField;
-  private int extraNulls;
+  private int numberOfUnusedArguments;
   private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
 
   public ConstructorEntryPointSynthesizedCode(
       Int2ReferenceSortedMap<DexMethod> typeConstructors,
       DexField classIdField,
-      int extraNulls) {
+      int numberOfUnusedArguments) {
     this.typeConstructors = typeConstructors;
     this.classIdField = classIdField;
-    this.extraNulls = extraNulls;
+    this.numberOfUnusedArguments = numberOfUnusedArguments;
   }
 
   @Override
   public void addExtraUnusedArguments(int numberOfUnusedArguments) {
-    this.extraNulls += numberOfUnusedArguments;
+    this.numberOfUnusedArguments += numberOfUnusedArguments;
   }
 
   private void registerReachableDefinitions(UseRegistry<?> registry) {
@@ -120,7 +120,8 @@
             .setIsD8R8Synthesized(true)
             .build();
     SourceCode sourceCode =
-        new ConstructorEntryPoint(typeConstructors, method, classIdField, extraNulls, position);
+        new ConstructorEntryPoint(
+            typeConstructors, method, classIdField, numberOfUnusedArguments, position);
     return IRBuilder.create(method, appView, sourceCode).build(method, conversionOptions);
   }
 
@@ -135,7 +136,7 @@
       RewrittenPrototypeDescription protoChanges) {
     SourceCode sourceCode =
         new ConstructorEntryPoint(
-            typeConstructors, method, classIdField, extraNulls, callerPosition);
+            typeConstructors, method, classIdField, numberOfUnusedArguments, callerPosition);
     return IRBuilder.createForInlining(
             method, appView, codeLens, sourceCode, valueNumberGenerator, protoChanges)
         .build(context, MethodConversionOptions.nonConverting());
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
index b601485..e7627a3 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
@@ -3,13 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.verticalclassmerging;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.classmerging.ClassMergerTreeFixer;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
+import com.google.common.collect.Iterables;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -62,4 +69,33 @@
   public void postprocess() {
     lensBuilder.fixupContextualVirtualToDirectMethodMaps();
   }
+
+  @Override
+  protected boolean isRoot(DexProgramClass clazz) {
+    if (!super.isRoot(clazz)) {
+      return false;
+    }
+    return !Iterables.any(
+        mergedClasses.getSourcesFor(clazz.getType()),
+        source -> isRoot(asProgramClassOrNull(appView.definitionFor(source))));
+  }
+
+  @Override
+  protected void traverseProgramClassesDepthFirst(
+      DexProgramClass clazz,
+      Set<DexProgramClass> seen,
+      DexMethodSignatureBiMap<DexMethodSignature> state) {
+    assert seen.add(clazz) : clazz.getTypeName();
+    if (mergedClasses.isMergeSource(clazz.getType())) {
+      assert !clazz.hasMethodsOrFields();
+      DexProgramClass target =
+          Iterables.getOnlyElement(immediateSubtypingInfo.getSubclasses(clazz));
+      traverseProgramClassesDepthFirst(target, seen, state);
+    } else {
+      DexMethodSignatureBiMap<DexMethodSignature> newState = fixupProgramClass(clazz, state);
+      for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
+        traverseProgramClassesDepthFirst(subclass, seen, newState);
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/horizontalclassmerging/ReserveInterfaceMethodSignaturesInFixupTest.java b/src/test/java/com/android/tools/r8/horizontalclassmerging/ReserveInterfaceMethodSignaturesInFixupTest.java
new file mode 100644
index 0000000..7a29a69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/horizontalclassmerging/ReserveInterfaceMethodSignaturesInFixupTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2024, 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.horizontalclassmerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoParameterTypeStrengthening;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/328899963. */
+@RunWith(Parameterized.class)
+public class ReserveInterfaceMethodSignaturesInFixupTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(B.class, C.class).assertNoOtherClassesMerged())
+        .addVerticallyMergedClassesInspector(
+            VerticallyMergedClassesInspector::assertNoClassesMerged)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("BSub", "CSub");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I b = System.currentTimeMillis() > 0 ? new BSub() : new CSub();
+      b.foo(new CSub());
+      I c = System.currentTimeMillis() > 0 ? new CSub() : new BSub();
+      c.foo(new CSub());
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {
+
+    void foo(C c);
+  }
+
+  @NoVerticalClassMerging
+  abstract static class A implements I {}
+
+  @NoVerticalClassMerging
+  abstract static class B extends A {}
+
+  @NoVerticalClassMerging
+  abstract static class C extends A {}
+
+  @NoHorizontalClassMerging
+  static class BSub extends B {
+
+    @NeverInline
+    @NoParameterTypeStrengthening
+    @Override
+    public void foo(C c) {
+      System.out.println("BSub");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class CSub extends C {
+
+    @NeverInline
+    @NoParameterTypeStrengthening
+    @Override
+    public void foo(C c) {
+      System.out.println(c);
+    }
+
+    @Override
+    public String toString() {
+      return "CSub";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index a152459..e60c8b6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -94,6 +94,15 @@
 
   private void inspect(
       HorizontallyMergedClassesInspector inspector, KotlinLambdasInInput lambdasInInput) {
+    if (hasKotlinCGeneratedLambdaClasses()
+        && kotlinParameters.getCompilerVersion().isLessThan(KOTLINC_1_5_0)) {
+      // Don't check exactly how J-style Kotlin lambdas are merged for kotlinc before 1.5.0.
+      assertEquals(
+          parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods() ? 2 : 10,
+          inspector.getMergeGroups().size());
+      return;
+    }
+
     if (!allowAccessModification && hasKotlinCGeneratedLambdaClasses()) {
       // Only a subset of all J-style Kotlin lambdas are merged without -allowaccessmodification.
       Set<ClassReference> unmergedLambdas =
@@ -214,7 +223,13 @@
         lambdasInOutput.add(classReference);
       }
     }
-    assertEquals(0, lambdasInOutput.size());
+    assertEquals(
+        kotlinParameters.getCompilerVersion().isGreaterThanOrEqualTo(KOTLINC_1_5_0)
+            ? 0
+            : (parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods()
+                ? 2
+                : 8),
+        lambdasInOutput.size());
   }
 
   private boolean hasKotlinCGeneratedLambdaClasses() {
diff --git a/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java b/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
index 9290be4..1464cff 100644
--- a/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/MappingFileAfterRepackagingTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
@@ -38,8 +39,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
         .applyIf(repackage, testBuilder -> testBuilder.addKeepRules("-repackageclasses"))
         .enableInliningAnnotations()
         .setMinApi(parameters)
@@ -61,10 +65,16 @@
                   StringUtils.splitLines(runResult.proguardMap()).stream()
                       .filter(line -> line.contains("java.lang.String toString()"))
                       .count();
-              assertEquals(repackage ? 1 : 3, unqualifiedMatches);
+              assertEquals(
+                  (repackage ? 1 : 3) + BooleanUtils.intValue(isPc2pc()), unqualifiedMatches);
             });
   }
 
+  private boolean isPc2pc() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+  }
+
   static class Main {
 
     public static void main(String[] args) {