Release the `DFATAL` pseudo-LogSeverity level

`DFATAL` is defined as `FATAL` in debug mode, and as `ERROR`
when `NDEBUG` is defined.

Closes #1279

PiperOrigin-RevId: 553904244
Change-Id: Iaa207ee65b2a39b4b7f5da241208c3d39cd5da0e
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt
index 76c4ff1..c3271a1 100644
--- a/absl/base/CMakeLists.txt
+++ b/absl/base/CMakeLists.txt
@@ -49,6 +49,7 @@
   SRCS
     "log_severity.cc"
   DEPS
+    absl::config
     absl::core_headers
   COPTS
     ${ABSL_DEFAULT_COPTS}
diff --git a/absl/base/log_severity.cc b/absl/base/log_severity.cc
index 60a8fc1..8e7bbbc 100644
--- a/absl/base/log_severity.cc
+++ b/absl/base/log_severity.cc
@@ -17,6 +17,7 @@
 #include <ostream>
 
 #include "absl/base/attributes.h"
+#include "absl/base/config.h"
 
 namespace absl {
 ABSL_NAMESPACE_BEGIN
diff --git a/absl/base/log_severity.h b/absl/base/log_severity.h
index 8bdca38..c8bcd2f 100644
--- a/absl/base/log_severity.h
+++ b/absl/base/log_severity.h
@@ -64,6 +64,8 @@
 //   --my_log_level=info
 //   --my_log_level=0
 //
+// `DFATAL` and `kLogDebugFatal` are similarly accepted.
+//
 // Unparsing a flag produces the same result as `absl::LogSeverityName()` for
 // the standard levels and a base-ten integer otherwise.
 enum class LogSeverity : int {
@@ -82,6 +84,16 @@
            absl::LogSeverity::kError, absl::LogSeverity::kFatal}};
 }
 
+// `absl::kLogDebugFatal` equals `absl::LogSeverity::kFatal` in debug builds
+// (i.e. when `NDEBUG` is not defined) and `absl::LogSeverity::kError`
+// otherwise.  Avoid ODR-using this variable as it has internal linkage and thus
+// distinct storage in different TUs.
+#ifdef NDEBUG
+static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kError;
+#else
+static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kFatal;
+#endif
+
 // LogSeverityName()
 //
 // Returns the all-caps string representation (e.g. "INFO") of the specified
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
index 9320ce5..2fc34d0 100644
--- a/absl/log/CMakeLists.txt
+++ b/absl/log/CMakeLists.txt
@@ -845,6 +845,7 @@
     absl::log_internal_test_helpers
     absl::log_severity
     absl::scoped_mock_log
+    GTest::gmock
     GTest::gtest_main
 )
 
diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc
index f7af47c..3d936cd 100644
--- a/absl/log/globals_test.cc
+++ b/absl/log/globals_test.cc
@@ -101,4 +101,33 @@
   EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag("test_tag_fail"), ".*");
 }
 
+TEST(TestExitOnDFatal, OffTest) {
+  // Turn off...
+  absl::log_internal::SetExitOnDFatal(false);
+  EXPECT_FALSE(absl::log_internal::ExitOnDFatal());
+
+  // We don't die.
+  {
+    absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+    // LOG(DFATAL) has severity FATAL if debugging, but is
+    // downgraded to ERROR if not debugging.
+    EXPECT_CALL(log, Log(absl::kLogDebugFatal, _, "This should not be fatal"));
+
+    log.StartCapturingLogs();
+    LOG(DFATAL) << "This should not be fatal";
+  }
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(TestDeathWhileExitOnDFatal, OnTest) {
+  absl::log_internal::SetExitOnDFatal(true);
+  EXPECT_TRUE(absl::log_internal::ExitOnDFatal());
+
+  // Death comes on little cats' feet.
+  EXPECT_DEBUG_DEATH({ LOG(DFATAL) << "This should be fatal in debug mode"; },
+                     "This should be fatal in debug mode");
+}
+#endif
+
 }  // namespace
diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h
index f576d65..41f6721 100644
--- a/absl/log/internal/conditions.h
+++ b/absl/log/internal/conditions.h
@@ -137,6 +137,15 @@
                   ? true                                                   \
                   : (::absl::log_internal::ExitQuietly(), false))          \
            : false))
+#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition)             \
+  ABSL_LOG_INTERNAL_##type##_CONDITION(                                 \
+      (ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \
+                   absl::kLogDebugFatal == absl::LogSeverity::kFatal),  \
+       (condition) &&                                                   \
+           (::absl::kLogDebugFatal >=                                   \
+                static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \
+            (::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal &&   \
+             (::absl::log_internal::AbortQuietly(), false)))))
 
 #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity)                    \
   for (int log_internal_severity_loop = 1; log_internal_severity_loop; \
@@ -163,6 +172,8 @@
   ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
 #define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \
   ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \
+  ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
 #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity)                    \
   for (int log_internal_severity_loop = 1; log_internal_severity_loop; \
        log_internal_severity_loop = 0)                                 \
diff --git a/absl/log/log.h b/absl/log/log.h
index 602b5ac..99e78ea 100644
--- a/absl/log/log.h
+++ b/absl/log/log.h
@@ -32,6 +32,8 @@
 // * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers
 //   quieter termination messages, e.g. without a full stack trace, and skips
 //   running registered error handlers.
+// * The `DFATAL` pseudo-severity level is defined as `FATAL` in debug mode and
+//   as `ERROR` otherwise.
 // Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has
 // the same meaning even if a local symbol or preprocessor macro named `INFO` is
 // defined.  To specify a severity level using an expression instead of a
diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h
index 2d41a07..4ed2435 100644
--- a/absl/log/log_streamer.h
+++ b/absl/log/log_streamer.h
@@ -165,6 +165,16 @@
   return absl::LogStreamer(absl::LogSeverity::kFatal, file, line);
 }
 
+// LogDebugFatalStreamer()
+//
+// Returns a LogStreamer that writes at level LogSeverity::kLogDebugFatal.
+//
+// In debug mode, the program will be terminated when this `LogStreamer` is
+// destroyed, regardless of whether any data were streamed in.
+inline LogStreamer LogDebugFatalStreamer(absl::string_view file, int line) {
+  return absl::LogStreamer(absl::kLogDebugFatal, file, line);
+}
+
 ABSL_NAMESPACE_END
 }  // namespace absl
 
diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc
index 328d70d..40c7d48 100644
--- a/absl/log/log_streamer_test.cc
+++ b/absl/log/log_streamer_test.cc
@@ -151,6 +151,57 @@
 }
 #endif
 
+#ifdef NDEBUG
+TEST(LogStreamerTest, LogDebugFatalStreamer) {
+  absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+  EXPECT_CALL(
+      test_sink,
+      Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+                 Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)),
+                 TimestampInMatchWindow(),
+                 ThreadID(Eq(absl::base_internal::GetTID())),
+                 TextMessage(Eq("WriteToStream: foo")),
+                 ENCODED_MESSAGE(EqualsProto(R"pb(value {
+                                                    str: "WriteToStream: foo"
+                                                  })pb")),
+                 Stacktrace(IsEmpty()))));
+
+  test_sink.StartCapturingLogs();
+  WriteToStream("foo",
+                &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream());
+}
+#elif GTEST_HAS_DEATH_TEST
+TEST(LogStreamerDeathTest, LogDebugFatalStreamer) {
+  EXPECT_EXIT(
+      {
+        absl::ScopedMockLog test_sink;
+
+        EXPECT_CALL(test_sink, Send)
+            .Times(AnyNumber())
+            .WillRepeatedly(DeathTestUnexpectedLogging());
+
+        EXPECT_CALL(
+            test_sink,
+            Send(AllOf(
+                SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+                Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)),
+                TimestampInMatchWindow(),
+                ThreadID(Eq(absl::base_internal::GetTID())),
+                TextMessage(Eq("WriteToStream: foo")),
+                ENCODED_MESSAGE(EqualsProto(R"pb(value {
+                                                   str: "WriteToStream: foo"
+                                                 })pb")))))
+            .WillOnce(DeathTestExpectedLogging());
+
+        test_sink.StartCapturingLogs();
+        WriteToStream(
+            "foo", &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream());
+      },
+      DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
 TEST(LogStreamerTest, LogStreamer) {
   absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
 
diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc
index aff9149..3535703 100644
--- a/absl/log/stripping_test.cc
+++ b/absl/log/stripping_test.cc
@@ -322,6 +322,49 @@
   }
 }
 
+TEST_F(StrippingTest, DFatal) {
+  // We need to load a copy of the needle string into memory (so we can search
+  // for it) without leaving it lying around in plaintext in the executable file
+  // as would happen if we used a literal.  We might (or might not) leave it
+  // lying around later; that's what the tests are for!
+  const std::string needle = absl::Base64Escape("StrippingTest.DFatal");
+  // We don't care if the LOG statement is actually executed, we're just
+  // checking that it's stripped.
+  if (kReallyDie) LOG(DFATAL) << "U3RyaXBwaW5nVGVzdC5ERmF0YWw=";
+
+  std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+  ASSERT_THAT(exe, NotNull());
+  // `DFATAL` can be `ERROR` or `FATAL`, and a compile-time optimizer doesn't
+  // know which, because `absl::kLogDebugFatal` is declared `extern` and defined
+  // in another TU.  Link-time optimization might do better.  We have six cases:
+  // |         `AMLL` is-> | `<=ERROR` | `FATAL` | `>FATAL` |
+  // | ------------------- | --------- | ------- | -------- |
+  // | `DFATAL` is `ERROR` |   present |       ? | stripped |
+  // | `DFATAL` is `FATAL` |   present | present | stripped |
+
+  // These constexpr variables are used to suppress unreachable code warnings
+  // in the if-else statements below.
+
+  // "present" in the table above: `DFATAL` exceeds `ABSL_MIN_LOG_LEVEL`, so
+  // `DFATAL` statements should not be stripped (and they should be logged
+  // when executed, but that's a different testsuite).
+  constexpr bool kExpectPresent = absl::kLogDebugFatal >= kAbslMinLogLevel;
+
+  // "stripped" in the table above: even though the compiler may not know
+  // which value `DFATAL` has, it should be able to strip it since both
+  // possible values ought to be stripped.
+  constexpr bool kExpectStripped = kAbslMinLogLevel > absl::LogSeverity::kFatal;
+
+  if (kExpectPresent) {
+    EXPECT_THAT(exe.get(), FileHasSubstr(needle));
+  } else if (kExpectStripped) {
+    EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
+  } else {
+    // "?" in the table above; may or may not be stripped depending on whether
+    // any link-time optimization is done.  Either outcome is ok.
+  }
+}
+
 TEST_F(StrippingTest, Level) {
   const std::string needle = absl::Base64Escape("StrippingTest.Level");
   volatile auto severity = absl::LogSeverity::kWarning;