Fold BazelCrashUtils.java into BugReport.java. There's no need to have both of these.

This was very tedious and a lot of care had to be done in order to avoid build-time cycles -- this is mostly due to the combination of (a) the serialization codebase's usage of BugReport and (b) the proliferation of '@AutoCodec' annotations.

Also, while I'm here, slightly improve readability of BugReport.java.

RELNOTES: None
PiperOrigin-RevId: 231241644
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index f3b2f13..74f885f 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -116,10 +116,21 @@
 )
 
 # I/O utilities.
+
+OUT_ERR_SRCS = [
+    "util/io/OutErr.java",
+    "util/io/LinePrefixingOutputStream.java",
+    "util/io/LineFlushingOutputStream.java",
+]
+
 java_library(
     name = "io",
-    srcs = glob(["util/io/*.java"]),
+    srcs = glob(
+        ["util/io/*.java"],
+        exclude = OUT_ERR_SRCS,
+    ),
     deps = [
+        ":out-err",
         "//src/main/java/com/google/devtools/build/lib/clock",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/profiler",
@@ -129,6 +140,11 @@
     ],
 )
 
+java_library(
+    name = "out-err",
+    srcs = OUT_ERR_SRCS,
+)
+
 # General utilities.
 java_library(
     name = "os_util",
@@ -210,6 +226,7 @@
             "util/StringCanonicalizer.java",
             "util/StringTrie.java",
             "util/StringUtil.java",
+            "util/StringUtilities.java",
             "util/VarInt.java",
         ],
     ),
@@ -271,6 +288,7 @@
     srcs = [
         "util/StringCanonicalizer.java",
         "util/StringUtil.java",
+        "util/StringUtilities.java",
     ],
     deps = [
         "//src/main/java/com/google/devtools/build/lib/concurrent",
@@ -289,14 +307,10 @@
 )
 
 java_library(
-    name = "crash-utils",
-    srcs = [
-        "util/BazelCrashUtils.java",
-        "util/CustomExitCodePublisher.java",
-    ],
+    name = "custom-exit-code-publisher",
+    srcs = ["util/CustomExitCodePublisher.java"],
     deps = [
         ":exitcode-external",
-        "//third_party:guava",
         "//third_party:jsr305",
     ],
 )
@@ -307,6 +321,7 @@
     srcs = glob(["events/*.java"]),
     deps = [
         ":io",
+        ":out-err",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
         "//src/main/java/com/google/devtools/build/lib/vfs",
@@ -609,6 +624,7 @@
         ],
         exclude = [
             "analysis/BuildInfo.java",
+            "analysis/BuildVersionInfo.java",
             "analysis/ProviderCollection.java",
             "analysis/TransitiveInfoProvider.java",
             "analysis/config/BuildConfigurationOptionDetails.java",
@@ -629,6 +645,7 @@
         ":io",
         ":keep-going-option",
         ":os_util",
+        ":out-err",
         ":packages-internal",
         ":process_util",
         ":provider-collection",
@@ -903,10 +920,12 @@
         ":events",
         ":io",
         ":maven-connector",
+        ":out-err",
         ":packages-internal",
         ":runtime",
         ":skylark_semantics",
         ":skylarkinterface",
+        ":string_util",
         ":util",
         "//src/java_tools/singlejar/java/com/google/devtools/build/zip",
         "//src/main/java/com/google/devtools/build/lib/actions",
@@ -1174,7 +1193,9 @@
         ":exitcode-external",
         ":io",
         ":os_util",
+        ":out-err",
         ":runtime",
+        ":string_util",
         ":unix",
         ":util",
         "//src/main/java/com/google/devtools/build/lib/clock",
@@ -1194,6 +1215,29 @@
 )
 
 java_library(
+    name = "bug-report",
+    srcs = ["bugreport/BugReport.java"],
+    deps = [
+        ":blaze-version-info",
+        ":custom-exit-code-publisher",
+        ":exitcode-external",
+        ":logging-util",
+        ":out-err",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
+
+java_library(
+    name = "logging-util",
+    srcs = ["util/LoggingUtil.java"],
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib/concurrent",
+        "//third_party:guava",
+    ],
+)
+
+java_library(
     name = "runtime",
     srcs = glob(
         [
@@ -1213,14 +1257,17 @@
         "server/signal/InterruptSignalHandler.java",
     ],
     deps = [
+        ":bug-report",
         ":build-base",
         ":build-request-options",
         ":command-utils",
+        ":custom-exit-code-publisher",
         ":events",
         ":exitcode-external",
         ":io",
         ":keep-going-option",
         ":loading-phase-threads-option",
+        ":out-err",
         ":packages-internal",
         ":process_util",
         ":shared-base-rules",
@@ -1272,6 +1319,17 @@
     ],
 )
 
+java_library(
+    name = "blaze-version-info",
+    srcs = [
+        "analysis/BlazeVersionInfo.java",
+    ],
+    deps = [
+        ":string_util",
+        "//third_party:guava",
+    ],
+)
+
 merge_licenses(
     name = "merge_licenses",
     srcs = [
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD
index 3c2a22c..74c5adf 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD
@@ -21,6 +21,7 @@
     deps = [
         "//src/main/java/com/google/devtools/build/lib:packages",
         "//src/main/java/com/google/devtools/build/lib:skylarkinterface",
+        "//src/main/java/com/google/devtools/build/lib:string_util",
         "//src/main/java/com/google/devtools/build/lib:transitive-info-provider",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BugReport.java b/src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java
similarity index 62%
rename from src/main/java/com/google/devtools/build/lib/runtime/BugReport.java
rename to src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java
index eb6c0a6..44fba2b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BugReport.java
+++ b/src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java
@@ -11,7 +11,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package com.google.devtools.build.lib.runtime;
+package com.google.devtools.build.lib.bugreport;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
@@ -25,15 +25,16 @@
 import java.io.PrintStream;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import javax.annotation.Nullable;
 
 /**
- * Utility methods for sending bug reports.
+ * Utility methods for handling crashes: we log the crash, optionally send a bug report, and then
+ * terminate the jvm.
  *
- * <p> Note, code in this class must be extremely robust.  There's nothing
- * worse than a crash-handler that itself crashes!
+ * <p> Note, code in this class must be extremely robust. There's nothing worse than a crash-handler
+ * that itself crashes!
  */
 public abstract class BugReport {
 
@@ -43,15 +44,27 @@
 
   private static BlazeVersionInfo versionInfo = BlazeVersionInfo.instance();
 
-  private static BlazeRuntime runtime = null;
+  private static BlazeRuntimeInterface runtime = null;
 
-  private static AtomicBoolean alreadyHandlingCrash = new AtomicBoolean(false);
+  @Nullable private static volatile Throwable unprocessedThrowableInTest = null;
+  private static final Object LOCK = new Object();
 
-  private static final boolean IN_TEST =
-      System.getenv("TEST_TMPDIR") != null
-          && System.getenv("ENABLE_BUG_REPORT_LOGGING_IN_TEST") == null;
+  private static final boolean IN_TEST = System.getenv("TEST_TMPDIR") != null;
 
-  public static void setRuntime(BlazeRuntime newRuntime) {
+  private static final boolean SHOULD_NOT_SEND_BUG_REPORT_BECAUSE_IN_TEST =
+      System.getenv("ENABLE_BUG_REPORT_LOGGING_IN_TEST") == null;
+
+  /**
+   * This is a narrow interface for {@link BugReport}'s usage of BlazeRuntime. It lives in this
+   * file, for the sake of avoiding a build-time cycle.
+   */
+  public interface BlazeRuntimeInterface {
+    String getProductName();
+    void notifyCommandComplete(int exitCode);
+    void shutdownOnCrash();
+  }
+
+  public static void setRuntime(BlazeRuntimeInterface newRuntime) {
     Preconditions.checkNotNull(newRuntime);
     Preconditions.checkState(runtime == null, "runtime already set: %s, %s", runtime, newRuntime);
     runtime = newRuntime;
@@ -62,6 +75,24 @@
   }
 
   /**
+   * In tests, Runtime#halt is disabled. Thus, the main thread should call this method whenever it
+   * is about to block on thread completion that might hang because of a failed halt below.
+   */
+  public static void maybePropagateUnprocessedThrowableIfInTest() {
+    if (IN_TEST) {
+      // Instead of the jvm having been halted, we might have a saved Throwable.
+      synchronized (LOCK) {
+        Throwable lastUnprocessedThrowableInTest = unprocessedThrowableInTest;
+        unprocessedThrowableInTest = null;
+        if (lastUnprocessedThrowableInTest != null) {
+          Throwables.throwIfUnchecked(lastUnprocessedThrowableInTest);
+          throw new RuntimeException(lastUnprocessedThrowableInTest);
+        }
+      }
+    }
+  }
+
+  /**
    * Logs the unhandled exception with a special prefix signifying that this was a crash.
    *
    * @param exception the unhandled exception to display.
@@ -69,7 +100,7 @@
    * @param values Additional string values to clarify the exception.
    */
   public static void sendBugReport(Throwable exception, List<String> args, String... values) {
-    if (IN_TEST) {
+    if (SHOULD_NOT_SEND_BUG_REPORT_BECAUSE_IN_TEST) {
       Throwables.throwIfUnchecked(exception);
       throw new IllegalStateException(
           "Bug reports in tests should crash: " + args + ", " + Arrays.toString(values), exception);
@@ -82,48 +113,80 @@
     logException(exception, filterClientEnv(args), values);
   }
 
-  private static void logCrash(Throwable throwable, String... args) {
+  private static void logCrash(Throwable throwable, boolean sendBugReport, String... args) {
     logger.severe("Crash: " + Throwables.getStackTraceAsString(throwable));
-    BugReport.sendBugReport(throwable, Arrays.asList(args));
+    if (sendBugReport) {
+      BugReport.sendBugReport(throwable, Arrays.asList(args));
+    }
     BugReport.printBug(OutErr.SYSTEM_OUT_ERR, throwable);
     System.err.println("ERROR: " + getProductName() + " crash in async thread:");
     throwable.printStackTrace();
   }
 
   /**
-   * Print and send a bug report, and exit with the proper Blaze code. Does not exit if called a
-   * second time. This method tries hard to catch any throwables thrown during its execution and
-   * halts the runtime in that case.
+   * Print, log, send a bug report, and then cause the current Blaze command to fail with the
+   * specified exit code, and then cause the jvm to terminate.
+   *
+   * <p>Has no effect if another crash has already been handled by {@link BugReport}.
+   */
+  public static void handleCrashWithoutSendingBugReport(
+      Throwable throwable, int exitCode, String... args) {
+    handleCrash(throwable, /*sendBugReport=*/ false, Integer.valueOf(exitCode), args);
+  }
+
+  /**
+   * Print, log, send a bug report, and then cause the current Blaze command to fail with the
+   * specified exit code, and then cause the jvm to terminate.
+   *
+   * <p>Has no effect if another crash has already been handled by {@link BugReport}.
+   */
+  public static void handleCrash(Throwable throwable, int exitCode, String... args) {
+    handleCrash(throwable, /*sendBugReport=*/ true, Integer.valueOf(exitCode), args);
+  }
+
+  /**
+   * Print, log, and send a bug report, and then cause the current Blaze command to fail with an
+   * exit code inferred from the given {@link Throwable}, and then cause the jvm to terminate.
+   *
+   * <p>Has no effect if another crash has already been handled by {@link BugReport}.
    */
   public static void handleCrash(Throwable throwable, String... args) {
-    int exitCode = getExitCodeForThrowable(throwable).getNumericExitCode();
+    handleCrash(throwable, /*sendBugReport=*/ true, /*exitCode=*/ null, args);
+  }
+
+  private static void handleCrash(
+      Throwable throwable, boolean sendBugReport, @Nullable Integer exitCode, String... args) {
+    int exitCodeToUse = exitCode == null
+        ? getExitCodeForThrowable(throwable).getNumericExitCode()
+        : exitCode.intValue();
     try {
-      if (alreadyHandlingCrash.compareAndSet(false, true)) {
+      synchronized (LOCK) {
+        if (IN_TEST) {
+          unprocessedThrowableInTest = throwable;
+        }
+        logCrash(throwable, sendBugReport, args);
         try {
-          logCrash(throwable, args);
           if (runtime != null) {
-            runtime.notifyCommandComplete(exitCode);
+            runtime.notifyCommandComplete(exitCodeToUse);
             // We don't call runtime#shutDown() here because all it does is shut down the modules,
             // and who knows if they can be trusted. Instead, we call runtime#shutdownOnCrash()
             // which attempts to cleanly shutdown those modules that might have something pending
             // to do as a best-effort operation.
             runtime.shutdownOnCrash();
           }
-          CustomExitCodePublisher.maybeWriteExitStatusFile(exitCode);
+          CustomExitCodePublisher.maybeWriteExitStatusFile(exitCodeToUse);
         } finally {
           // Avoid shutdown deadlock issues: If an application shutdown hook crashes, it will
           // trigger our Blaze crash handler (this method). Calling System#exit() here, would
           // therefore induce a deadlock. This call would block on the shutdown sequence completing,
           // but the shutdown sequence would in turn be blocked on this thread finishing. Instead,
           // exit fast via halt().
-          Runtime.getRuntime().halt(exitCode);
+          Runtime.getRuntime().halt(exitCodeToUse);
         }
-      } else {
-        logCrash(throwable, args);
       }
     } catch (Throwable t) {
       System.err.println(
-          "ERROR: An crash occurred while "
+          "ERROR: A crash occurred while "
               + getProductName()
               + " was trying to handle a crash! Please file a bug against "
               + getProductName()
@@ -135,7 +198,7 @@
       System.err.println("Exception encountered during BugReport#handleCrash:");
       t.printStackTrace(System.err);
 
-      Runtime.getRuntime().halt(exitCode);
+      Runtime.getRuntime().halt(exitCodeToUse);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD b/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD
index 5b00d29..523daf3 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD
@@ -27,6 +27,7 @@
         ":buildeventservice-options",
         "//src/main/java/com/google/devtools/build/lib:events",
         "//src/main/java/com/google/devtools/build/lib:io",
+        "//src/main/java/com/google/devtools/build/lib:out-err",
         "//src/main/java/com/google/devtools/build/lib:runtime",
         "//src/main/java/com/google/devtools/build/lib:util",
         "//src/main/java/com/google/devtools/build/lib/authandtls",
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD b/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD
index a3d7752..d15b0bd 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD
@@ -19,7 +19,7 @@
         "Order.java",
     ],
     deps = [
-        "//src/main/java/com/google/devtools/build/lib:crash-utils",
+        "//src/main/java/com/google/devtools/build/lib:bug-report",
         "//src/main/java/com/google/devtools/build/lib:exitcode-external",
         "//src/main/java/com/google/devtools/build/lib/collect/compacthashset",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
index bb4abdd..e0516fe 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
@@ -22,10 +22,10 @@
 import com.google.common.flogger.GoogleLogger;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet;
 import com.google.devtools.build.lib.concurrent.MoreFutures;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
-import com.google.devtools.build.lib.util.BazelCrashUtils;
 import com.google.devtools.build.lib.util.ExitCode;
 import com.google.protobuf.ByteString;
 import java.util.AbstractCollection;
@@ -201,7 +201,7 @@
         System.err.println(
             "An interrupted exception occurred during nested set deserialization, "
                 + "exiting abruptly.");
-        BazelCrashUtils.halt(e, ExitCode.INTERRUPTED.getNumericExitCode());
+        BugReport.handleCrash(e, ExitCode.INTERRUPTED.getNumericExitCode());
         throw new IllegalStateException("Server should have shut down.", e);
       }
     } else {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index fb5ebf2..52b8c17 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.Flushables;
 import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.buildtool.buildevent.ProfilerStartedEvent;
 import com.google.devtools.build.lib.clock.BlazeClock;
 import com.google.devtools.build.lib.events.Event;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index 84af24a..5990bac 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
 import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.clock.BlazeClock;
 import com.google.devtools.build.lib.clock.Clock;
 import com.google.devtools.build.lib.events.Event;
@@ -125,7 +126,7 @@
  *
  * <p>The parts specific to the current command are stored in {@link CommandEnvironment}.
  */
-public final class BlazeRuntime {
+public final class BlazeRuntime implements BugReport.BlazeRuntimeInterface {
   private static final Pattern suppressFromLog =
       Pattern.compile("--client_env=([^=]*(?:auth|pass|cookie)[^=]*)=", Pattern.CASE_INSENSITIVE);
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java
index 0ae63e4..e9d33a3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.actions.ActionStatusMessage;
 import com.google.devtools.build.lib.analysis.AnalysisPhaseCompleteEvent;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.buildeventstream.AnnounceBuildEventTransportsEvent;
 import com.google.devtools.build.lib.buildeventstream.BuildEventTransport;
 import com.google.devtools.build.lib.buildeventstream.BuildEventTransportClosedEvent;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java b/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java
index 14490d7..4f288d3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java
@@ -15,6 +15,7 @@
 package com.google.devtools.build.lib.runtime;
 
 import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.common.options.OptionsParsingException;
 import com.sun.management.GarbageCollectionNotificationInfo;
 import java.lang.management.GarbageCollectorMXBean;
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD
index bdbfc9a..935a8de 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD
@@ -20,6 +20,7 @@
         "//src/main/java/com/google/devtools/build/lib:command-utils",
         "//src/main/java/com/google/devtools/build/lib:events",
         "//src/main/java/com/google/devtools/build/lib:io",
+        "//src/main/java/com/google/devtools/build/lib:out-err",
         "//src/main/java/com/google/devtools/build/lib:process_util",
         "//src/main/java/com/google/devtools/build/lib:runtime",
         "//src/main/java/com/google/devtools/build/lib:util",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
index 1a01b9b..c327a73 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
@@ -15,7 +15,7 @@
         exclude = ["SerializationConstants.java"],
     ),
     deps = [
-        "//src/main/java/com/google/devtools/build/lib:crash-utils",
+        "//src/main/java/com/google/devtools/build/lib:bug-report",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:registered-singleton",
         "//src/main/java/com/google/devtools/build/lib/unsafe:string",
         "//src/main/java/com/google/devtools/build/lib/unsafe:unsafe-provider",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java
index 9a89d6f..0ea80cd 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java
@@ -22,9 +22,9 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.skyframe.serialization.Memoizer.Serializer;
 import com.google.devtools.build.lib.skyframe.serialization.SerializationException.NoCodecException;
-import com.google.devtools.build.lib.util.BazelCrashUtils;
 import com.google.protobuf.CodedOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -164,7 +164,7 @@
 
         @Override
         public void onFailure(Throwable t) {
-          throw BazelCrashUtils.halt(t);
+          BugReport.handleCrash(t);
         }
       };
 
diff --git a/src/main/java/com/google/devtools/build/lib/util/BazelCrashUtils.java b/src/main/java/com/google/devtools/build/lib/util/BazelCrashUtils.java
deleted file mode 100644
index 6a7bfa9..0000000
--- a/src/main/java/com/google/devtools/build/lib/util/BazelCrashUtils.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2018 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.util;
-
-import com.google.common.base.Throwables;
-import java.util.logging.Logger;
-import javax.annotation.Nullable;
-
-/** Utilities for crashing the jvm. */
-public class BazelCrashUtils {
-  private static final Logger logger = Logger.getLogger(BazelCrashUtils.class.getName());
-
-  private static final Object HALT_LOCK = new Object();
-
-  @Nullable private static volatile Throwable unprocessedThrowableInTest = null;
-
-  private BazelCrashUtils() {}
-
-  /**
-   * In tests, System.exit is disabled. Thus, the main thread should call this method whenever it is
-   * about to block on thread completion that might hang because of a failed halt below.
-   */
-  public static void maybePropagateUnprocessedThrowableIfInTest() {
-    synchronized (HALT_LOCK) {
-      Throwable lastUnprocessedThrowableInTest = unprocessedThrowableInTest;
-      unprocessedThrowableInTest = null;
-      if (lastUnprocessedThrowableInTest != null) {
-        Throwables.throwIfUnchecked(lastUnprocessedThrowableInTest);
-        throw new RuntimeException(lastUnprocessedThrowableInTest);
-      }
-    }
-  }
-
-  /** Terminates the jvm with the exit code {@link ExitCode#BLAZE_INTERNAL_ERROR}. */
-  public static RuntimeException halt(Throwable cause) {
-    return halt(cause, ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode());
-  }
-
-  /**
-   * Terminates the jvm with the given {@code exitCode}. Note that, in server mode, the jvm's exit
-   * code has nothing to do with the exit code picked by the blaze client, except as communicated by
-   * the 'custom_exit_code_on_abrupt_exit' behavior. If this fails, the exit code picked by the
-   * client will default to INTERNAL_ERROR.
-   */
-  public static RuntimeException halt(Throwable crash, int exitCode) {
-    synchronized (HALT_LOCK) {
-      try {
-        logCrashDetails(crash);
-        unprocessedThrowableInTest = crash;
-        CustomExitCodePublisher.maybeWriteExitStatusFile(exitCode);
-      } catch (OutOfMemoryError oom) {
-        // In case we tried to halt with a non-OOM, but OOMed during the lead up to the halt.
-        Runtime.getRuntime().halt(exitCode);
-      } finally {
-        doDie(crash, exitCode);
-      }
-      throw new IllegalStateException("impossible");
-    }
-  }
-
-  private static void logCrashDetails(Throwable crash) {
-    logger.severe(
-        "About to call System#exit. Halt requested due to crash. See stderr for details ("
-            + crash.getMessage()
-            + ")");
-    System.err.println("About to call System#exit. Halt requested due to crash");
-    crash.printStackTrace();
-    System.err.println("... called from");
-    new Exception().printStackTrace();
-  }
-
-  private static void doDie(Throwable crash, int exitCode) {
-    try {
-      // Bypass shutdown hooks if we encountered an OOM, they're likely to take way too long to be
-      // sensible to run in such a hostile environment.
-      Throwable cause = crash;
-      while (cause != null) {
-        if (cause instanceof OutOfMemoryError) {
-          Runtime.getRuntime().halt(exitCode);
-        }
-        cause = cause.getCause();
-      }
-    } finally {
-      System.exit(exitCode);
-    }
-  }
-}