Ensure stack usage remains low when unwinding the stack, to prevent stack overflows

PiperOrigin-RevId: 792704790
Change-Id: I4484219d570e84205cf7da95f71e3c8a00a590e8
diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc
index e9e9482..4f48a89 100644
--- a/absl/debugging/stacktrace_test.cc
+++ b/absl/debugging/stacktrace_test.cc
@@ -21,6 +21,7 @@
 #include <cerrno>
 #include <csignal>
 #include <cstring>
+#include <memory>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -32,6 +33,7 @@
 static int g_should_fixup_calls = 0;
 static int g_fixup_calls = 0;
 static bool g_enable_fixup = false;
+static uintptr_t g_last_fixup_frame_address = 0;
 
 #if ABSL_HAVE_ATTRIBUTE_WEAK
 bool absl::internal_stacktrace::ShouldFixUpStack() {
@@ -41,6 +43,11 @@
 
 void absl::internal_stacktrace::FixUpStack(void**, uintptr_t*, int*, size_t,
                                            size_t&) {
+  const void* frame_address = nullptr;
+#if ABSL_HAVE_BUILTIN(__builtin_frame_address)
+  frame_address = __builtin_frame_address(0);
+#endif
+  g_last_fixup_frame_address = reinterpret_cast<uintptr_t>(frame_address);
   ++g_fixup_calls;
 }
 #endif
@@ -223,6 +230,49 @@
 
 TEST(StackTrace, FixupNoFixupEquivalence) { FixupNoFixupEquivalenceNoInline(); }
 
+TEST(StackTrace, FixupLowStackUsage) {
+#if !ABSL_HAVE_ATTRIBUTE_WEAK
+  GTEST_SKIP() << "Skipping test on MSVC due to weak symbols";
+#endif
+#if defined(_WIN32)
+  // TODO(b/434184677): Add support for fixups on Windows if needed
+  GTEST_SKIP() << "Skipping test on Windows due to lack of support for fixups";
+#endif
+
+  const Cleanup restore_state([enable_fixup = g_enable_fixup,
+                               fixup_calls = g_fixup_calls,
+                               should_fixup_calls = g_should_fixup_calls]() {
+    g_enable_fixup = enable_fixup;
+    g_fixup_calls = fixup_calls;
+    g_should_fixup_calls = should_fixup_calls;
+  });
+
+  g_enable_fixup = true;
+
+  // Request a ton of stack frames, regardless of how many are actually used.
+  // It's fine to request more frames than we have, since functions preallocate
+  // memory before discovering how high the stack really is, and we're really
+  // just trying to make sure the preallocations don't overflow the stack.
+  //
+  // Note that we loop in order to cover all sides of any branches in the
+  // implementation that switch allocation behavior (e.g., from stack to heap)
+  // and to ensure that no sides allocate too much stack space.
+  constexpr size_t kPageSize = 4096;
+  for (size_t depth = 2; depth < (1 << 20); depth += depth / 2) {
+    const auto stack = std::make_unique<void*[]>(depth);
+    const auto frames = std::make_unique<int[]>(depth);
+
+    absl::GetStackFrames(stack.get(), frames.get(), static_cast<int>(depth), 0);
+    const void* frame_address = nullptr;
+#if ABSL_HAVE_BUILTIN(__builtin_frame_address)
+    frame_address = __builtin_frame_address(0);
+#endif
+    size_t stack_usage =
+        reinterpret_cast<uintptr_t>(frame_address) - g_last_fixup_frame_address;
+    EXPECT_LT(stack_usage, kPageSize);
+  }
+}
+
 TEST(StackTrace, CustomUnwinderPerformsFixup) {
 #if !ABSL_HAVE_ATTRIBUTE_WEAK
   GTEST_SKIP() << "Need weak symbol support";