Handle kept synthetics in synthetic finalization

Fixes: 187041481
Change-Id: I54b48b1149d8ec8813d0ac933bc5fbd620b7bfde
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 3c91d68..ba68e81 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -92,17 +92,15 @@
       D other,
       boolean includeContext,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
-    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap, syntheticItems) == 0;
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
+    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap) == 0;
   }
 
   int compareTo(
       D other,
       boolean includeContext,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
     DexType thisType = getHolder().getType();
     DexType otherType = other.getHolder().getType();
     if (getKind().isFixedSuffixSynthetic) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 79097e5..25f44af 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -24,10 +25,12 @@
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
@@ -36,6 +39,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.common.hash.HashCode;
 import java.util.ArrayList;
@@ -47,6 +51,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class SyntheticFinalization {
 
@@ -123,43 +129,6 @@
     }
   }
 
-  public static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> {
-    private final List<T> members;
-
-    public EquivalenceGroup(T representative, List<T> members) {
-      assert !members.isEmpty();
-      assert members.get(0) == representative;
-      this.members = members;
-    }
-
-    public T getRepresentative() {
-      return members.get(0);
-    }
-
-    public List<T> getMembers() {
-      return members;
-    }
-
-    public int compareToIncludingContext(
-        EquivalenceGroup<T> other,
-        GraphLens graphLens,
-        ClassToFeatureSplitMap classToFeatureSplitMap,
-        SyntheticItems syntheticItems) {
-      return getRepresentative()
-          .compareTo(
-              other.getRepresentative(), true, graphLens, classToFeatureSplitMap, syntheticItems);
-    }
-
-    @Override
-    public String toString() {
-      return "EquivalenceGroup{ members = "
-          + members.size()
-          + ", repr = "
-          + getRepresentative()
-          + " }";
-    }
-  }
-
   private final InternalOptions options;
   private final SyntheticItems synthetics;
   private final CommittedSyntheticsCollection committed;
@@ -393,18 +362,16 @@
           SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
           SynthesizingContext context = representative.getContext();
           context.registerPrefixRewriting(syntheticType, appView);
-          DexProgramClass representativeClass = representative.getHolder();
-          addSyntheticMarker(representative.getKind(), representativeClass, context, appView);
-          assert representativeClass.getMethodCollection().size() == 1;
-          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
-            if (member != representative) {
-              pruned.add(member.getHolder());
-              deduplicatedClasses.add(member.getHolder());
-            }
-            if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
-              derivedMainDexSynthetics.add(syntheticType);
-            }
+          addSyntheticMarker(
+              representative.getKind(), representative.getHolder(), context, appView);
+          if (syntheticGroup.isDerivedFromMainDexList(mainDexInfo)) {
+            derivedMainDexSynthetics.add(syntheticType);
           }
+          syntheticGroup.forEachNonRepresentativeMember(
+              member -> {
+                pruned.add(member.getHolder());
+                deduplicatedClasses.add(member.getHolder());
+              });
         });
 
     syntheticClassGroups.forEach(
@@ -414,16 +381,14 @@
           context.registerPrefixRewriting(syntheticType, appView);
           addSyntheticMarker(
               representative.getKind(), representative.getHolder(), context, appView);
-          for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
-            DexProgramClass memberClass = member.getHolder();
-            if (member != representative) {
-              pruned.add(memberClass);
-              deduplicatedClasses.add(memberClass);
-            }
-            if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
-              derivedMainDexSynthetics.add(syntheticType);
-            }
+          if (syntheticGroup.isDerivedFromMainDexList(mainDexInfo)) {
+            derivedMainDexSynthetics.add(syntheticType);
           }
+          syntheticGroup.forEachNonRepresentativeMember(
+              member -> {
+                pruned.add(member.getHolder());
+                deduplicatedClasses.add(member.getHolder());
+              });
         });
 
     // Only create a new application if anything changed.
@@ -457,6 +422,8 @@
     syntheticClassGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+          assert externalSyntheticClass != null
+              : "Expected definition for " + syntheticType.getTypeName();
           SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
           addFinalSyntheticClass.accept(
               externalSyntheticClass,
@@ -496,10 +463,10 @@
       boolean verifyNonRepresentativesRemovedFromApplication(
           DexApplication application, Map<DexType, EquivalenceGroup<T>> syntheticGroups) {
     for (EquivalenceGroup<?> syntheticGroup : syntheticGroups.values()) {
-      for (SyntheticDefinition<?, ?, ?> member : syntheticGroup.getMembers()) {
-        assert member == syntheticGroup.getRepresentative()
-            || application.definitionFor(member.getHolder().getType()) == null;
-      }
+      syntheticGroup.forEachNonRepresentativeMember(
+          member -> {
+            assert application.definitionFor(member.getHolder().getType()) == null;
+          });
     }
     return true;
   }
@@ -532,34 +499,35 @@
           ClassToFeatureSplitMap classToFeatureSplitMap,
           Builder lensBuilder) {
     Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>();
+    Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
     potentialEquivalences.forEach(
         members -> {
-          List<List<T>> groups =
-              groupEquivalent(
-                  members, intermediate, appView.graphLens(), classToFeatureSplitMap, synthetics);
-          for (List<T> group : groups) {
-            T representative =
-                findDeterministicRepresentative(
-                    group, appView.graphLens(), classToFeatureSplitMap, synthetics);
-            // The representative is required to be the first element of the group.
-            group.remove(representative);
-            group.add(0, representative);
-            groupsPerPrefix
-                .computeIfAbsent(
-                    representative.getPrefixForExternalSyntheticType(), k -> new ArrayList<>())
-                .add(new EquivalenceGroup<>(representative, group));
+          List<EquivalenceGroup<T>> groups =
+              groupEquivalent(appView, members, intermediate, classToFeatureSplitMap);
+          for (EquivalenceGroup<T> group : groups) {
+            // If the group already has a representative, then this representative is pinned.
+            // Otherwise, we select a deterministic representative.
+            if (group.hasRepresentative()) {
+              EquivalenceGroup<T> previous =
+                  equivalences.put(group.getRepresentative().getHolder().getType(), group);
+              assert previous == null;
+            } else {
+              group.selectDeterministicRepresentative();
+              groupsPerPrefix
+                  .computeIfAbsent(
+                      group.getRepresentative().getPrefixForExternalSyntheticType(),
+                      k -> new ArrayList<>())
+                  .add(group);
+            }
           }
         });
-
-    Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
     groupsPerPrefix.forEach(
         (externalSyntheticTypePrefix, groups) -> {
           // Sort the equivalence groups that go into 'context' including the context type of the
           // representative which is equal to 'context' here (see assert below).
           groups.sort(
               (a, b) ->
-                  a.compareToIncludingContext(
-                      b, appView.graphLens(), classToFeatureSplitMap, synthetics));
+                  a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
             assert group
@@ -570,84 +538,113 @@
             // of the synthetic name will be non-deterministic between the two.
             assert i == 0
                 || checkGroupsAreDistinct(
-                    groups.get(i - 1),
-                    group,
-                    appView.graphLens(),
-                    classToFeatureSplitMap,
-                    synthetics);
-            SyntheticKind kind = group.members.get(0).getKind();
+                    groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
+            SyntheticKind kind = group.getRepresentative().getKind();
             DexType representativeType =
-                createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
+                createExternalType(
+                    kind,
+                    externalSyntheticTypePrefix,
+                    generators,
+                    appView,
+                    equivalences::containsKey);
             equivalences.put(representativeType, group);
-            for (T member : group.getMembers()) {
-              lensBuilder.move(member.getHolder().getType(), representativeType);
-            }
           }
         });
+    equivalences.forEach(
+        (representativeType, group) ->
+            group.forEach(
+                member -> lensBuilder.move(member.getHolder().getType(), representativeType)));
     return equivalences;
   }
 
-  private static <T extends SyntheticDefinition<?, T, ?>> List<List<T>> groupEquivalent(
+  private static <T extends SyntheticDefinition<?, T, ?>> List<EquivalenceGroup<T>> groupEquivalent(
+      AppView<?> appView,
       List<T> potentialEquivalence,
       boolean intermediate,
-      GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
-    List<List<T>> groups = new ArrayList<>();
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
+    List<EquivalenceGroup<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
     for (T synthetic : potentialEquivalence) {
-      boolean requireNewGroup = true;
-      for (List<T> group : groups) {
+      boolean mustBeRepresentative = isPinned(appView, synthetic);
+      EquivalenceGroup<T> equivalenceGroup = null;
+      for (EquivalenceGroup<T> group : groups) {
         if (synthetic.isEquivalentTo(
-            group.get(0), intermediate, graphLens, classToFeatureSplitMap, syntheticItems)) {
-          requireNewGroup = false;
-          group.add(synthetic);
+            group.hasRepresentative()
+                ? group.getRepresentative()
+                : group.getFirstNonRepresentativeMember(),
+            intermediate,
+            appView.graphLens(),
+            classToFeatureSplitMap)) {
+          if (mustBeRepresentative && group.hasRepresentative()) {
+            // Check if the current synthetic is smaller than the group's representative. If so,
+            // then replace the representative, to ensure deterministic groups, and create a new
+            // singleton group containing the old representative. Otherwise, just add a singleton
+            // group containing the new synthetic.
+            T representative = group.getRepresentative();
+            if (representative
+                    .toReference()
+                    .getReference()
+                    .compareTo(synthetic.toReference().getReference())
+                > 0) {
+              group.replaceAndRemoveRepresentative(synthetic);
+              synthetic = representative;
+            }
+          }
+          equivalenceGroup = group;
           break;
         }
       }
-      if (requireNewGroup) {
-        List<T> newGroup = new ArrayList<>();
-        newGroup.add(synthetic);
-        groups.add(newGroup);
+      if (equivalenceGroup != null) {
+        equivalenceGroup.add(synthetic, mustBeRepresentative);
+      } else {
+        groups.add(new EquivalenceGroup<>(synthetic, mustBeRepresentative));
       }
     }
     return groups;
   }
 
+  /**
+   * In R8, keep rules may apply to synthetics from the input, if the input has been compiled using
+   * intermediate mode.
+   */
+  private static <D extends SyntheticDefinition<?, D, ?>> boolean isPinned(
+      AppView<?> appView, D definition) {
+    if (!appView.enableWholeProgramOptimizations()) {
+      return false;
+    }
+    if (!definition.getHolder().isProgramClass()) {
+      return true;
+    }
+    DexProgramClass holder = definition.getHolder().asProgramClass();
+    KeepInfoCollection keepInfo = appView.getKeepInfo();
+    if (keepInfo.getClassInfo(holder).isPinned()) {
+      return true;
+    }
+    for (DexEncodedMember<?, ?> member : holder.members()) {
+      if (keepInfo.getMemberInfo(member, holder).isPinned()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private static <T extends SyntheticDefinition<?, T, ?>> boolean checkGroupsAreDistinct(
       EquivalenceGroup<T> g1,
       EquivalenceGroup<T> g2,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
-    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap, syntheticItems);
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
+    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap);
     assert order != 0;
-    assert order
-        != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap, syntheticItems);
+    assert order != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap);
     return true;
   }
 
-  private static <T extends SyntheticDefinition<?, T, ?>> T findDeterministicRepresentative(
-      List<T> members,
-      GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap,
-      SyntheticItems syntheticItems) {
-    // Pick a deterministic member as representative.
-    T smallest = members.get(0);
-    for (int i = 1; i < members.size(); i++) {
-      T next = members.get(i);
-      if (next.toReference().getReference().compareTo(smallest.toReference().getReference()) < 0) {
-        smallest = next;
-      }
-    }
-    return smallest;
-  }
-
   private DexType createExternalType(
       SyntheticKind kind,
       String externalSyntheticTypePrefix,
       Map<String, NumberGenerator> generators,
-      AppView<?> appView) {
+      AppView<?> appView,
+      Predicate<DexType> reserved) {
     DexItemFactory factory = appView.dexItemFactory();
     if (kind.isFixedSuffixSynthetic) {
       return SyntheticNaming.createExternalType(kind, externalSyntheticTypePrefix, "", factory);
@@ -659,6 +656,12 @@
       externalType =
           SyntheticNaming.createExternalType(
               kind, externalSyntheticTypePrefix, Integer.toString(generator.next()), factory);
+      // If the generated external type matches an external synthetic from the input, which is kept,
+      // then continue.
+      if (reserved.test(externalType)) {
+        externalType = null;
+        continue;
+      }
       DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
       if (clazz != null && isNotSyntheticType(clazz.type)) {
         assert options.testing.allowConflictingSyntheticTypes
@@ -727,4 +730,101 @@
     }
     return definitions;
   }
+
+  private static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> {
+
+    // The members of the equivalence group, *excluding* the representative.
+    private List<T> members = new ArrayList<>();
+    private T representative;
+
+    EquivalenceGroup(T member, boolean isRepresentative) {
+      add(member, isRepresentative);
+    }
+
+    void add(T member, boolean isRepresentative) {
+      if (isRepresentative) {
+        assert !hasRepresentative();
+        representative = member;
+      } else {
+        members.add(member);
+      }
+    }
+
+    int compareToIncludingContext(
+        EquivalenceGroup<T> other,
+        GraphLens graphLens,
+        ClassToFeatureSplitMap classToFeatureSplitMap) {
+      return getRepresentative()
+          .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap);
+    }
+
+    public void forEach(Consumer<T> consumer) {
+      consumer.accept(getRepresentative());
+      members.forEach(consumer);
+    }
+
+    public void forEachNonRepresentativeMember(Consumer<T> consumer) {
+      members.forEach(consumer);
+    }
+
+    T getFirstNonRepresentativeMember() {
+      assert !members.isEmpty();
+      return members.get(0);
+    }
+
+    T getRepresentative() {
+      assert hasRepresentative();
+      return representative;
+    }
+
+    boolean hasRepresentative() {
+      return representative != null;
+    }
+
+    boolean isDerivedFromMainDexList(MainDexInfo mainDexInfo) {
+      return getRepresentative().getContext().isDerivedFromMainDexList(mainDexInfo)
+          || Iterables.any(
+              members, member -> member.getContext().isDerivedFromMainDexList(mainDexInfo));
+    }
+
+    void replaceAndRemoveRepresentative(T representative) {
+      assert hasRepresentative();
+      this.representative = representative;
+    }
+
+    void selectDeterministicRepresentative() {
+      // Pick a deterministic member as representative.
+      assert !hasRepresentative();
+      int representativeIndex = 0;
+      for (int i = 1; i < members.size(); i++) {
+        T next = members.get(i);
+        T representative = members.get(representativeIndex);
+        if (next.toReference().getReference().compareTo(representative.toReference().getReference())
+            < 0) {
+          representativeIndex = i;
+        }
+      }
+      T representative = members.get(representativeIndex);
+      members.set(representativeIndex, ListUtils.last(members));
+      ListUtils.removeLast(members);
+      setRepresentative(representative);
+    }
+
+    void setRepresentative(T representative) {
+      assert !hasRepresentative();
+      this.representative = representative;
+    }
+
+    @Override
+    public String toString() {
+      if (hasRepresentative()) {
+        return "EquivalenceGroup{ size = "
+            + (members.size() + 1)
+            + ", repr = "
+            + getRepresentative()
+            + " }";
+      }
+      return "EquivalenceGroup{ size = " + members.size() + " }";
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index d0164d7..1575685 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -249,7 +249,7 @@
 
   public boolean isSubjectToKeepRules(DexProgramClass clazz) {
     assert isSyntheticClass(clazz);
-    return committed.containsSyntheticInput(clazz.getType());
+    return isSyntheticInput(clazz);
   }
 
   public boolean isSyntheticClass(DexType type) {
@@ -260,6 +260,10 @@
     return isSyntheticClass(clazz.type);
   }
 
+  boolean isSyntheticInput(DexProgramClass clazz) {
+    return committed.containsSyntheticInput(clazz.getType());
+  }
+
   public FeatureSplit getContextualFeatureSplit(DexType type) {
     if (pending.legacyClasses.containsKey(type)) {
       LegacySyntheticDefinition definition = pending.legacyClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index e7c8e46..7b4976c 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -135,6 +135,12 @@
     return mapOrElse(list, fn, list);
   }
 
+  public static <T> ArrayList<T> newArrayList(T element) {
+    ArrayList<T> list = new ArrayList<>();
+    list.add(element);
+    return list;
+  }
+
   public static <T> ArrayList<T> newArrayList(ForEachable<T> forEachable) {
     ArrayList<T> list = new ArrayList<>();
     forEachable.forEach(list::add);
@@ -155,6 +161,10 @@
     return Optional.empty();
   }
 
+  public static <T> T removeLast(List<T> list) {
+    return list.remove(list.size() - 1);
+  }
+
   public static <T extends Comparable<T>> boolean verifyListIsOrdered(List<T> list) {
     for (int i = list.size() - 1; i > 0; i--) {
       if (list.get(i).compareTo(list.get(i - 1)) < 0) {