Ensure missing definitions diagnostic message is deterministic

Change-Id: If5c7c376ce718f2fc0ed71d592c326892e757d68
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
new file mode 100644
index 0000000..2b0e375
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2021, 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.diagnostic.internal;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+import com.android.tools.r8.utils.FieldReferenceUtils;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import java.util.Comparator;
+
+public class MissingDefinitionInfoUtils {
+
+  private static final Comparator<MissingDefinitionInfo> COMPARATOR =
+      (info, other) -> {
+        IntBox result = new IntBox();
+        if (isMissingClassInfo(info)) {
+          ClassReference classReference = getClassReference(info);
+          other.getMissingDefinition(
+              otherClassReference ->
+                  result.set(ClassReferenceUtils.compare(classReference, otherClassReference)),
+              otherFieldReference ->
+                  result.set(
+                      ClassReferenceUtils.compare(
+                          classReference, otherFieldReference.getHolderClass())),
+              otherMethodReference ->
+                  result.set(
+                      ClassReferenceUtils.compare(
+                          classReference, otherMethodReference.getHolderClass())));
+        } else if (isMissingFieldInfo(info)) {
+          FieldReference fieldReference = getFieldReference(info);
+          other.getMissingDefinition(
+              otherClassReference ->
+                  result.set(
+                      ClassReferenceUtils.compare(
+                          fieldReference.getHolderClass(), otherClassReference)),
+              otherFieldReference ->
+                  result.set(FieldReferenceUtils.compare(fieldReference, otherFieldReference)),
+              otherMethodReference ->
+                  result.set(
+                      ClassReferenceUtils.compare(
+                          fieldReference.getHolderClass(), otherMethodReference.getHolderClass())));
+        } else {
+          MethodReference methodReference = getMethodReference(info);
+          other.getMissingDefinition(
+              otherClassReference ->
+                  result.set(
+                      ClassReferenceUtils.compare(
+                          methodReference.getHolderClass(), otherClassReference)),
+              otherFieldReference ->
+                  result.set(
+                      ClassReferenceUtils.compare(
+                          methodReference.getHolderClass(), otherFieldReference.getHolderClass())),
+              otherMethodReference ->
+                  result.set(MethodReferenceUtils.compare(methodReference, otherMethodReference)));
+        }
+        return result.get();
+      };
+
+  public static Comparator<MissingDefinitionInfo> getComparator() {
+    return COMPARATOR;
+  }
+
+  public static ClassReference getClassReference(MissingDefinitionInfo missingDefinitionInfo) {
+    Box<ClassReference> classReference = new Box<>();
+    missingDefinitionInfo.getMissingDefinition(
+        classReference::set, emptyConsumer(), emptyConsumer());
+    return classReference.get();
+  }
+
+  public static boolean isMissingClassInfo(MissingDefinitionInfo missingDefinitionInfo) {
+    return getClassReference(missingDefinitionInfo) != null;
+  }
+
+  public static FieldReference getFieldReference(MissingDefinitionInfo missingDefinitionInfo) {
+    Box<FieldReference> fieldReference = new Box<>();
+    missingDefinitionInfo.getMissingDefinition(
+        emptyConsumer(), fieldReference::set, emptyConsumer());
+    return fieldReference.get();
+  }
+
+  public static boolean isMissingFieldInfo(MissingDefinitionInfo missingDefinitionInfo) {
+    return getFieldReference(missingDefinitionInfo) != null;
+  }
+
+  public static MethodReference getMethodReference(MissingDefinitionInfo missingDefinitionInfo) {
+    Box<MethodReference> methodReference = new Box<>();
+    missingDefinitionInfo.getMissingDefinition(
+        emptyConsumer(), emptyConsumer(), methodReference::set);
+    return methodReference.get();
+  }
+
+  public static boolean isMissingMethodInfo(MissingDefinitionInfo missingDefinitionInfo) {
+    return getMethodReference(missingDefinitionInfo) != null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
index f9455a0..9c1f322 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
@@ -20,8 +20,10 @@
 import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
 public class MissingDefinitionsDiagnosticImpl implements MissingDefinitionsDiagnostic {
 
@@ -41,6 +43,13 @@
     return missingDefinitions;
   }
 
+  private Collection<MissingDefinitionInfo> getMissingDefinitionsWithDeterministicOrder() {
+    List<MissingDefinitionInfo> missingDefinitionsWithDeterministicOrder =
+        new ArrayList<>(getMissingDefinitions());
+    missingDefinitionsWithDeterministicOrder.sort(MissingDefinitionInfoUtils.getComparator());
+    return missingDefinitionsWithDeterministicOrder;
+  }
+
   /** A missing class(es) failure can generally not be attributed to a single origin. */
   @Override
   public Origin getOrigin() {
@@ -56,7 +65,8 @@
   @Override
   public String getDiagnosticMessage() {
     StringBuilder builder = new StringBuilder();
-    Iterator<MissingDefinitionInfo> missingDefinitionsIterator = missingDefinitions.iterator();
+    Iterator<MissingDefinitionInfo> missingDefinitionsIterator =
+        getMissingDefinitionsWithDeterministicOrder().iterator();
 
     // The diagnostic is always non-empty.
     assert missingDefinitionsIterator.hasNext();
diff --git a/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
index eb5659d..171344e 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
@@ -12,6 +12,10 @@
   private static final Comparator<ClassReference> COMPARATOR =
       Comparator.comparing(ClassReference::getDescriptor);
 
+  public static int compare(ClassReference classReference, ClassReference other) {
+    return getClassReferenceComparator().compare(classReference, other);
+  }
+
   public static Comparator<ClassReference> getClassReferenceComparator() {
     return COMPARATOR;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
index 062960e..ebf7821 100644
--- a/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
@@ -29,6 +29,10 @@
         return getTypeReferenceComparator().compare(field.getFieldType(), other.getFieldType());
       };
 
+  public static int compare(FieldReference fieldReference, FieldReference other) {
+    return getFieldReferenceComparator().compare(fieldReference, other);
+  }
+
   public static FieldReference fieldFromField(Class<?> clazz, String name) {
     try {
       return Reference.fieldFromField(clazz.getDeclaredField(name));
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 3719535..4d36fb9 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -52,6 +52,10 @@
         return method.getFormalTypes().size() - other.getFormalTypes().size();
       };
 
+  public static int compare(MethodReference methodReference, MethodReference other) {
+    return getMethodReferenceComparator().compare(methodReference, other);
+  }
+
   public static Comparator<MethodReference> getMethodReferenceComparator() {
     return COMPARATOR;
   }