Include method-to-reprocess set in determinism checks

Bug: 227138351
Change-Id: I4b467ae737caacb859d364073d255a660a0f5967
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 0292a0d..d6ca793 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -106,6 +106,9 @@
     constantFields.forEach(this::markFieldAsDead);
     readFields.keySet().forEach(this::markFieldAsDead);
     writtenFields.keySet().forEach(this::markWriteOnlyFieldAsDead);
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   private void markWriteOnlyFieldAsDead(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 82b58a8..acadf11 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -821,6 +821,9 @@
       inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
     }
     openClosedInterfacesAnalysis.onPrimaryOptimizationPassComplete();
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   public void addWaveDoneAction(com.android.tools.r8.utils.Action action) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index ecee91f..780f5d4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -20,11 +20,13 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DeterminismChecker;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
@@ -142,6 +144,10 @@
           new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing);
       return new PostMethodProcessor(appView, callGraph);
     }
+
+    public void dump(DeterminismChecker determinismChecker) throws IOException {
+      determinismChecker.accept(methodsToReprocessBuilder::dump);
+    }
   }
 
   private Deque<ProgramMethodSet> createWaves(CallGraph callGraph) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index d926360..f455175 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -706,6 +706,9 @@
             enumUnboxingLens,
             enumDataMap,
             utilityClasses);
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   private void updateOptimizationInfos(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index b7dd7ff..00be161 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -195,6 +195,9 @@
         .fixupApplication(affectedClasses, executorService, timing);
 
     timing.end();
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java b/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
index 4871186..2f95428 100644
--- a/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
+++ b/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
@@ -10,12 +10,13 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.Closeable;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -44,12 +45,14 @@
               return new LineCallbackChecker(Files.newBufferedReader(log, StandardCharsets.UTF_8));
             } else {
               System.out.println("Writing determinism log: " + log);
+              // Note that Files.newBufferedWriter will cause issues in presence of malformed input
+              // and unmappable character errors, since Files.newBufferedWriter uses the
+              // java.nio.charset.CharsetDecoder default action, which is to report such errors,
+              // instead of dealing with them in java.nio.charset.CharsetDecoder#onMalformedInput.
               BufferedWriter bufferedWriter =
-                  Files.newBufferedWriter(
-                      log,
-                      StandardCharsets.UTF_8,
-                      StandardOpenOption.CREATE,
-                      StandardOpenOption.TRUNCATE_EXISTING);
+                  new BufferedWriter(
+                      new OutputStreamWriter(
+                          new FileOutputStream(log.toFile()), StandardCharsets.UTF_8));
               return new LineCallbackWriter(bufferedWriter);
             }
           }
@@ -82,6 +85,13 @@
     return method.getReference().toSourceString();
   }
 
+  public <E extends Exception> void accept(ThrowingConsumer<LineCallback, E> consumer)
+      throws E, IOException {
+    try (LineCallback callback = callbackFactory.createCallback()) {
+      consumer.accept(callback);
+    }
+  }
+
   public void check(AppView<?> appView) {
     try (LineCallback callback = callbackFactory.createCallback()) {
       List<DexProgramClass> classes = new ArrayList<>(appView.appInfo().classes());
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 77a670b..133f721 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1696,24 +1696,37 @@
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
 
-    public void setDeterminismChecker(DeterminismChecker checker) {
-      determinismChecker = checker;
-    }
-
-    public void checkDeterminism(AppView<?> appView) {
+    private DeterminismChecker getDeterminismChecker() {
       // Lazily read the env-var so that it can be set after options init.
       if (determinismChecker == null && !hasReadCheckDeterminism) {
         hasReadCheckDeterminism = true;
         String dir = System.getProperty("com.android.tools.r8.checkdeterminism");
         if (dir != null) {
-          determinismChecker = DeterminismChecker.createWithFileBacking(Paths.get(dir));
+          setDeterminismChecker(DeterminismChecker.createWithFileBacking(Paths.get(dir)));
         }
       }
+      return determinismChecker;
+    }
+
+    public void setDeterminismChecker(DeterminismChecker checker) {
+      determinismChecker = checker;
+    }
+
+    public void checkDeterminism(AppView<?> appView) {
+      DeterminismChecker determinismChecker = getDeterminismChecker();
       if (determinismChecker != null) {
         determinismChecker.check(appView);
       }
     }
 
+    public <E extends Exception> void checkDeterminism(
+        ThrowingConsumer<DeterminismChecker, E> consumer) {
+      DeterminismChecker determinismChecker = getDeterminismChecker();
+      if (determinismChecker != null) {
+        consumer.acceptWithRuntimeException(determinismChecker);
+      }
+    }
+
     public static void allowExperimentClassFileVersion(InternalOptions options) {
       options.reportedExperimentClassFileVersion.set(true);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index ce4399b..735146a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -13,7 +13,11 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DeterminismChecker.LineCallback;
 import com.android.tools.r8.utils.SetUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import java.util.function.IntFunction;
 import java.util.function.Predicate;
@@ -184,4 +188,12 @@
             != null;
     return true;
   }
+
+  public void dump(LineCallback lineCallback) throws IOException {
+    List<DexMethod> sortedMethods = new ArrayList<>(methods);
+    sortedMethods.sort(DexMethod::compareTo);
+    for (DexMethod method : sortedMethods) {
+      lineCallback.onLine(method.toSourceString());
+    }
+  }
 }