Fix missing NUL-termination in Demangle() on parse failure and make ParseAbiTags() restore pre-parse state correctly

PiperOrigin-RevId: 947849322
Change-Id: I899ab7a5fa16f478fdfa7765a36418224208f1d9
diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc
index a8d7511..5b2d623 100644
--- a/absl/debugging/internal/demangle.cc
+++ b/absl/debugging/internal/demangle.cc
@@ -17,6 +17,7 @@
 
 #include "absl/debugging/internal/demangle.h"
 
+#include <algorithm>
 #include <cstddef>
 #include <cstdint>
 #include <cstdio>
@@ -456,22 +457,35 @@
 }
 
 // Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
-// set to out_end_idx+1.  The output string is ensured to
-// always terminate with '\0' as long as there is no overflow.
+// set to out_end_idx+1.  The output buffer is always terminated with '\0' if it
+// has nonzero length.
 static void Append(State *state, const char *const str, const size_t length) {
-  for (size_t i = 0; i < length; ++i) {
-    if (state->parse_state.out_cur_idx + 1 <
-        state->out_end_idx) {  // +1 for '\0'
-      state->out[state->parse_state.out_cur_idx++] = str[i];
-    } else {
-      // signal overflow
-      state->parse_state.out_cur_idx = state->out_end_idx + 1;
-      break;
-    }
+  if (length == 0) {
+    return;
   }
-  if (state->parse_state.out_cur_idx < state->out_end_idx) {
-    state->out[state->parse_state.out_cur_idx] =
-        '\0';  // Terminate it with '\0'
+
+  // Figure out how much space is remaining in the output buffer to copy into.
+  const int cap = state->out_end_idx - state->parse_state.out_cur_idx;
+
+  // If overflow was already signaled (negative value, set further below) or
+  // there is zero space to write into, we cannot do anything.
+  if (cap <= 0) {
+    return;
+  }
+
+  // Copy the number of characters requested, capped by the amount of space
+  // remaining.
+  std::char_traits<char>::copy(state->out + state->parse_state.out_cur_idx, str,
+                               (std::min)(length, static_cast<size_t>(cap)));
+
+  // Did we copy everything we needed to, with enough room to NUL-terminate?
+  if (length < static_cast<size_t>(cap)) {
+    state->parse_state.out_cur_idx += static_cast<int>(length);
+    state->out[state->parse_state.out_cur_idx] = '\0';
+  } else {
+    // No, we ran out of space. Signal overflow, and NUL-terminate for safety.
+    state->parse_state.out_cur_idx = state->out_end_idx + 1;
+    state->out[state->out_end_idx - 1] = '\0';
   }
 }
 
@@ -893,8 +907,11 @@
   ComplexityGuard guard(state);
   if (guard.IsTooComplex()) return false;
 
-  while (ParseOneCharToken(state, 'B')) {
-    ParseState copy = state->parse_state;
+  for (;;) {
+    const ParseState copy = state->parse_state;
+    if (!ParseOneCharToken(state, 'B')) {
+      break;
+    }
     MaybeAppend(state, "[abi:");
 
     if (!ParseSourceName(state)) {
diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc
index 9af2583..7238fd0 100644
--- a/absl/debugging/internal/demangle_test.cc
+++ b/absl/debugging/internal/demangle_test.cc
@@ -14,6 +14,7 @@
 
 #include "absl/debugging/internal/demangle.h"
 
+#include <array>
 #include <cstdlib>
 #include <memory>
 #include <string>
@@ -30,6 +31,7 @@
 namespace debugging_internal {
 namespace {
 
+using ::testing::Contains;
 using ::testing::ContainsRegex;
 
 TEST(Demangle, FunctionTemplate) {
@@ -1908,6 +1910,13 @@
   EXPECT_STREQ("my_crate::my_func", tmp);
 }
 
+TEST(Demangle, DemanglingNulTerminatesOnParsingFailure) {
+  std::array buf = {'\xAA', '\xAA', '\xAA', '\xAA'};
+  EXPECT_FALSE(Demangle("_ZN1xBE", std::data(buf), std::size(buf)));
+  // Ensure string is properly NUL-terminated despite parsing failure.
+  EXPECT_THAT(buf, Contains('\0'));
+}
+
 // Tests that verify that Demangle footprint is within some limit.
 // They are not to be run under sanitizers as the sanitizers increase
 // stack consumption by about 4x.