Add lifetime tracking debug utility to QUICHE.

PiperOrigin-RevId: 615244192
diff --git a/build/source_list.bzl b/build/source_list.bzl
index cce928f..9d9cf94 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -13,6 +13,7 @@
     "common/capsule.h",
     "common/http/http_header_block.h",
     "common/http/http_header_storage.h",
+    "common/lifetime_tracking.h",
     "common/masque/connect_ip_datagram_payload.h",
     "common/masque/connect_udp_datagram_payload.h",
     "common/platform/api/quiche_bug_tracker.h",
@@ -1057,6 +1058,7 @@
     "common/capsule_test.cc",
     "common/http/http_header_block_test.cc",
     "common/http/http_header_storage_test.cc",
+    "common/lifetime_tracking_test.cc",
     "common/masque/connect_ip_datagram_payload_test.cc",
     "common/masque/connect_udp_datagram_payload_test.cc",
     "common/platform/api/quiche_file_utils_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 36d7c2d..589f737 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -13,6 +13,7 @@
     "src/quiche/common/capsule.h",
     "src/quiche/common/http/http_header_block.h",
     "src/quiche/common/http/http_header_storage.h",
+    "src/quiche/common/lifetime_tracking.h",
     "src/quiche/common/masque/connect_ip_datagram_payload.h",
     "src/quiche/common/masque/connect_udp_datagram_payload.h",
     "src/quiche/common/platform/api/quiche_bug_tracker.h",
@@ -1058,6 +1059,7 @@
     "src/quiche/common/capsule_test.cc",
     "src/quiche/common/http/http_header_block_test.cc",
     "src/quiche/common/http/http_header_storage_test.cc",
+    "src/quiche/common/lifetime_tracking_test.cc",
     "src/quiche/common/masque/connect_ip_datagram_payload_test.cc",
     "src/quiche/common/masque/connect_udp_datagram_payload_test.cc",
     "src/quiche/common/platform/api/quiche_file_utils_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 5c154ef..274f204 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -12,6 +12,7 @@
     "quiche/common/capsule.h",
     "quiche/common/http/http_header_block.h",
     "quiche/common/http/http_header_storage.h",
+    "quiche/common/lifetime_tracking.h",
     "quiche/common/masque/connect_ip_datagram_payload.h",
     "quiche/common/masque/connect_udp_datagram_payload.h",
     "quiche/common/platform/api/quiche_bug_tracker.h",
@@ -1057,6 +1058,7 @@
     "quiche/common/capsule_test.cc",
     "quiche/common/http/http_header_block_test.cc",
     "quiche/common/http/http_header_storage_test.cc",
+    "quiche/common/lifetime_tracking_test.cc",
     "quiche/common/masque/connect_ip_datagram_payload_test.cc",
     "quiche/common/masque/connect_udp_datagram_payload_test.cc",
     "quiche/common/platform/api/quiche_file_utils_test.cc",
diff --git a/quiche/common/lifetime_tracking.h b/quiche/common/lifetime_tracking.h
new file mode 100644
index 0000000..34f8c6b
--- /dev/null
+++ b/quiche/common/lifetime_tracking.h
@@ -0,0 +1,142 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file implements helper classes to track c++ object lifetimes. They are
+// useful to debug use-after-free issues in environments where the cost of ASAN
+// is too high.
+//
+// Suppose you have an object of type "MyClass" and a raw pointer "ptr" pointing
+// to it, and you suspect a dereference of "ptr" is unsafe because the object it
+// points to is dead. You can do
+//
+// (1) Add a LifetimeTrackable member to "MyClass". Alternatively, you can also
+//     change MyClass to inherit from LifetimeTrackable.
+//
+//     struct MyClass {
+//       ...... existing members ......
+//       LifetimeTrackable trackable;
+//     }
+//
+// (2) Add a LifetimeTracker alongside the "ptr".
+//
+//     ptr = new MyClass().
+//     tracker = ptr->trackable.NewTracker();
+//
+// (3) Before the potentially dangerous dereference, check whether *ptr is dead:
+//
+//     if (tracker.IsTrackedObjectDead()) {
+//       // ptr->trackable has been destructed. Log its destruction stack below.
+//       QUICHE_LOG(ERROR) << "*ptr has bee destructed: " << tracker;
+//     }
+//     ptr->MethodCall();  // Possibly a use-after-free
+//
+// All classes defined in this file are not thread safe.
+
+#ifndef QUICHE_COMMON_LIFETIME_TRACKING_H_
+#define QUICHE_COMMON_LIFETIME_TRACKING_H_
+
+#include <array>
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/log/die_if_null.h"
+#include "absl/strings/str_format.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_stack_trace.h"
+
+namespace quiche {
+namespace test {
+class LifetimeTrackingTest;
+}  // namespace test
+
+// LifetimeInfo holds information about a LifetimeTrackable object.
+struct QUICHE_EXPORT LifetimeInfo {
+  bool IsDead() const { return destructor_stack.has_value(); }
+
+  // If IsDead(), the stack when the LifetimeTrackable object is destructed.
+  std::optional<std::vector<void*>> destructor_stack;
+};
+
+// LifetimeTracker tracks the lifetime of a LifetimeTrackable object, by holding
+// a reference to its LifetimeInfo.
+class QUICHE_EXPORT LifetimeTracker {
+ public:
+  // Copy constructor and assignment operator allow this tracker to track the
+  // same object as |other|.
+  LifetimeTracker(const LifetimeTracker& other) { CopyFrom(other); }
+  LifetimeTracker& operator=(const LifetimeTracker& other) {
+    CopyFrom(other);
+    return *this;
+  }
+
+  // Move constructor and assignment are implemented as copies, to prevent the
+  // moved-from object from tracking "nothing".
+  LifetimeTracker(LifetimeTracker&& other) { CopyFrom(other); }
+  LifetimeTracker& operator=(LifetimeTracker&& other) {
+    CopyFrom(other);
+    return *this;
+  }
+
+  // Whether the tracked object is dead.
+  bool IsTrackedObjectDead() const { return info_->IsDead(); }
+
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const LifetimeTracker& tracker) {
+    if (tracker.info_->IsDead()) {
+      absl::Format(&sink, "Tracked object has died with %v",
+                   SymbolizeStackTrace(*tracker.info_->destructor_stack));
+    } else {
+      absl::Format(&sink, "Tracked object is alive.");
+    }
+  }
+
+ private:
+  friend class LifetimeTrackable;
+  explicit LifetimeTracker(std::shared_ptr<const LifetimeInfo> info)
+      : info_(ABSL_DIE_IF_NULL(info)) {}
+  void CopyFrom(const LifetimeTracker& other) { info_ = other.info_; }
+
+  std::shared_ptr<const LifetimeInfo> info_;
+};
+
+// LifetimeTrackable allows its lifetime to be tracked by any number of
+// LifetimeTracker(s).
+class QUICHE_EXPORT LifetimeTrackable {
+ public:
+  LifetimeTrackable() = default;
+  virtual ~LifetimeTrackable() {
+    if (info_ != nullptr) {
+      info_->destructor_stack = CurrentStackTrace();
+    }
+  }
+
+  // LifetimeTrackable only tracks the memory occupied by itself. All copy/move
+  // constructors and assignments are no-op.
+  LifetimeTrackable(const LifetimeTrackable&) : LifetimeTrackable() {}
+  LifetimeTrackable& operator=(const LifetimeTrackable&) { return *this; }
+  LifetimeTrackable(LifetimeTrackable&&) : LifetimeTrackable() {}
+  LifetimeTrackable& operator=(LifetimeTrackable&&) { return *this; }
+
+  LifetimeTracker NewTracker() {
+    if (info_ == nullptr) {
+      info_ = std::make_shared<LifetimeInfo>();
+    }
+    return LifetimeTracker(info_);
+  }
+
+ private:
+  friend class test::LifetimeTrackingTest;
+  // nullptr if this object is not tracked by any LifetimeTracker.
+  std::shared_ptr<LifetimeInfo> info_;
+};
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_LIFETIME_TRACKING_H_
diff --git a/quiche/common/lifetime_tracking_test.cc b/quiche/common/lifetime_tracking_test.cc
new file mode 100644
index 0000000..94b0ba1
--- /dev/null
+++ b/quiche/common/lifetime_tracking_test.cc
@@ -0,0 +1,181 @@
+#include "quiche/common/lifetime_tracking.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace test {
+
+struct ComposedTrackable {
+  LifetimeTrackable trackable;
+};
+
+struct InheritedTrackable : LifetimeTrackable {};
+
+enum class TrackableType {
+  kComposed,
+  kInherited,
+};
+
+// Used by ::testing::PrintToStringParamName().
+std::string PrintToString(const TrackableType& type) {
+  switch (type) {
+    case TrackableType::kComposed:
+      return "Composed";
+    case TrackableType::kInherited:
+      return "Inherited";
+    default:
+      QUICHE_LOG(FATAL) << "Unknown TrackableType: " << static_cast<int>(type);
+  }
+}
+
+class LifetimeTrackingTest : public QuicheTestWithParam<TrackableType> {
+ protected:
+  LifetimeTrackingTest() {
+    if (GetParam() == TrackableType::kComposed) {
+      composed_trackable_ = std::make_unique<ComposedTrackable>();
+    } else {
+      inherited_trackable_ = std::make_unique<InheritedTrackable>();
+    }
+  }
+
+  // Returns the trackable object. Must be called before FreeTrackable.
+  LifetimeTrackable& GetTrackable() {
+    if (composed_trackable_ != nullptr) {
+      return composed_trackable_->trackable;
+    } else {
+      return *inherited_trackable_;
+    }
+  }
+
+  // Returns a trackable.info_.
+  const std::shared_ptr<LifetimeInfo>& GetLifetimeInfoFromTrackable(
+      LifetimeTrackable& trackable) {
+    return trackable.info_;
+  }
+
+  const std::shared_ptr<LifetimeInfo>& GetLifetimeInfoFromTrackable() {
+    return GetLifetimeInfoFromTrackable(GetTrackable());
+  }
+
+  void FreeTrackable() {
+    composed_trackable_ = nullptr;
+    inherited_trackable_ = nullptr;
+  }
+
+  std::unique_ptr<ComposedTrackable> composed_trackable_;
+  std::unique_ptr<InheritedTrackable> inherited_trackable_;
+};
+
+TEST_P(LifetimeTrackingTest, TrackableButNeverTracked) {
+  EXPECT_EQ(GetLifetimeInfoFromTrackable(), nullptr);
+}
+
+TEST_P(LifetimeTrackingTest, SingleTrackerQueryLiveness) {
+  LifetimeTracker tracker = GetTrackable().NewTracker();
+  EXPECT_FALSE(tracker.IsTrackedObjectDead());
+  EXPECT_THAT(absl::StrCat(tracker),
+              testing::HasSubstr("Tracked object is alive"));
+  FreeTrackable();
+  EXPECT_TRUE(tracker.IsTrackedObjectDead());
+  EXPECT_THAT(absl::StrCat(tracker),
+              testing::HasSubstr("Tracked object has died"));
+}
+
+TEST_P(LifetimeTrackingTest, MultiTrackersQueryLiveness) {
+  LifetimeTracker tracker1 = GetTrackable().NewTracker();
+  LifetimeTracker tracker2 = GetTrackable().NewTracker();
+  LifetimeTracker tracker3 = tracker2;
+  LifetimeTracker tracker4 = std::move(tracker3);
+  LifetimeTracker tracker5(std::move(tracker4));
+  LifetimeTrackable another_trackable;
+  LifetimeTracker tracker6 = another_trackable.NewTracker();
+  LifetimeTracker tracker7 = another_trackable.NewTracker();
+  tracker6 = tracker2;
+  tracker7 = std::move(tracker2);
+  EXPECT_FALSE(tracker1.IsTrackedObjectDead());
+  EXPECT_FALSE(
+      tracker2.IsTrackedObjectDead());  // NOLINT(bugprone-use-after-move)
+  EXPECT_FALSE(
+      tracker3.IsTrackedObjectDead());  // NOLINT(bugprone-use-after-move)
+  EXPECT_FALSE(
+      tracker4.IsTrackedObjectDead());  // NOLINT(bugprone-use-after-move)
+  EXPECT_FALSE(tracker5.IsTrackedObjectDead());
+  EXPECT_FALSE(tracker6.IsTrackedObjectDead());
+  EXPECT_FALSE(tracker7.IsTrackedObjectDead());
+  FreeTrackable();
+  EXPECT_TRUE(tracker1.IsTrackedObjectDead());
+  EXPECT_TRUE(
+      tracker2.IsTrackedObjectDead());  // NOLINT(bugprone-use-after-move)
+  EXPECT_TRUE(
+      tracker3.IsTrackedObjectDead());  // NOLINT(bugprone-use-after-move)
+  EXPECT_TRUE(
+      tracker4.IsTrackedObjectDead());  // NOLINT(bugprone-use-after-move)
+  EXPECT_TRUE(tracker5.IsTrackedObjectDead());
+  EXPECT_TRUE(tracker6.IsTrackedObjectDead());
+  EXPECT_TRUE(tracker7.IsTrackedObjectDead());
+}
+
+TEST_P(LifetimeTrackingTest, CopyTrackableIsNoop) {
+  LifetimeTracker tracker = GetTrackable().NewTracker();
+  const LifetimeInfo* info = GetLifetimeInfoFromTrackable().get();
+  EXPECT_NE(info, nullptr);
+  LifetimeTrackable trackable2(GetTrackable());
+  EXPECT_EQ(GetLifetimeInfoFromTrackable(trackable2), nullptr);
+
+  LifetimeTrackable trackable3;
+  trackable3 = GetTrackable();
+  EXPECT_EQ(GetLifetimeInfoFromTrackable(trackable3), nullptr);
+
+  EXPECT_EQ(GetLifetimeInfoFromTrackable().get(), info);
+}
+
+TEST_P(LifetimeTrackingTest, MoveTrackableIsNoop) {
+  LifetimeTracker tracker = GetTrackable().NewTracker();
+  const LifetimeInfo* info = GetLifetimeInfoFromTrackable().get();
+  EXPECT_NE(info, nullptr);
+  LifetimeTrackable trackable2(std::move(GetTrackable()));
+  EXPECT_EQ(GetLifetimeInfoFromTrackable(trackable2), nullptr);
+
+  LifetimeTrackable trackable3;
+  trackable3 = std::move(GetTrackable());
+  EXPECT_EQ(GetLifetimeInfoFromTrackable(trackable3), nullptr);
+
+  EXPECT_EQ(GetLifetimeInfoFromTrackable().get(), info);
+}
+
+TEST_P(LifetimeTrackingTest, ObjectDiedDueToVectorRealloc) {
+  if (GetParam() == TrackableType::kComposed) {
+    return;
+  }
+
+  std::vector<InheritedTrackable> trackables;
+
+  // Append 1 element to the vector and keep track of its life.
+  InheritedTrackable& trackable = trackables.emplace_back();
+  LifetimeTracker tracker = trackable.NewTracker();
+  EXPECT_FALSE(tracker.IsTrackedObjectDead());
+
+  // Append 1000 more elements to the vector, |trackable| should be destroyed by
+  // vector realloc.
+  for (int i = 0; i < 1000; ++i) {
+    trackables.emplace_back();
+  }
+
+  // Accessing |trackable| is a use-after-free.
+  EXPECT_TRUE(tracker.IsTrackedObjectDead());
+}
+
+INSTANTIATE_TEST_SUITE_P(Tests, LifetimeTrackingTest,
+                         testing::Values(TrackableType::kComposed,
+                                         TrackableType::kInherited),
+                         testing::PrintToStringParamName());
+
+}  // namespace test
+}  // namespace quiche
diff --git a/quiche/common/platform/api/quiche_stack_trace.h b/quiche/common/platform/api/quiche_stack_trace.h
index 4c07577..57fd654 100644
--- a/quiche/common/platform/api/quiche_stack_trace.h
+++ b/quiche/common/platform/api/quiche_stack_trace.h
@@ -6,11 +6,22 @@
 #define QUICHE_COMMON_PLATFORM_API_QUICHE_STACK_TRACE_H_
 
 #include <string>
+#include <vector>
+
+#include "absl/types/span.h"
 
 #include "quiche_platform_impl/quiche_stack_trace_impl.h"
 
 namespace quiche {
 
+inline std::vector<void*> CurrentStackTrace() {
+  return CurrentStackTraceImpl();
+}
+
+inline std::string SymbolizeStackTrace(absl::Span<void* const> stacktrace) {
+  return SymbolizeStackTraceImpl(stacktrace);
+}
+
 // Returns a human-readable stack trace.  Mostly used in error logging and
 // related features.
 inline std::string QuicheStackTrace() { return QuicheStackTraceImpl(); }
diff --git a/quiche/common/platform/api/quiche_stack_trace_test.cc b/quiche/common/platform/api/quiche_stack_trace_test.cc
index 74a4ca9..f99b709 100644
--- a/quiche/common/platform/api/quiche_stack_trace_test.cc
+++ b/quiche/common/platform/api/quiche_stack_trace_test.cc
@@ -28,6 +28,13 @@
   return result;
 }
 
+ABSL_ATTRIBUTE_NOINLINE std::string
+QuicheDesignatedTwoStepStackTraceTestFunction() {
+  std::string result = SymbolizeStackTrace(CurrentStackTrace());
+  ABSL_BLOCK_TAIL_CALL_OPTIMIZATION();
+  return result;
+}
+
 TEST(QuicheStackTraceTest, GetStackTrace) {
   if (!ShouldRunTest()) {
     return;
@@ -38,6 +45,16 @@
               testing::HasSubstr("QuicheDesignatedStackTraceTestFunction"));
 }
 
+TEST(QuicheStackTraceTest, GetStackTraceInTwoSteps) {
+  if (!ShouldRunTest()) {
+    return;
+  }
+
+  std::string stacktrace = QuicheDesignatedTwoStepStackTraceTestFunction();
+  EXPECT_THAT(stacktrace, testing::HasSubstr(
+                              "QuicheDesignatedTwoStepStackTraceTestFunction"));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quiche
diff --git a/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.cc b/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.cc
index 9b0c969..b94da0c 100644
--- a/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.cc
+++ b/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.cc
@@ -4,6 +4,7 @@
 
 #include "quiche_platform_impl/quiche_stack_trace_impl.h"
 
+#include <string>
 #include <vector>
 
 #include "absl/base/macros.h"
@@ -11,6 +12,7 @@
 #include "absl/debugging/symbolize.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 
 namespace quiche {
 
@@ -20,15 +22,18 @@
 constexpr absl::string_view kUnknownSymbol = "(unknown)";
 }  // namespace
 
-std::string QuicheStackTraceImpl() {
+std::vector<void*> CurrentStackTraceImpl() {
   std::vector<void*> stacktrace(kMaxStackSize, nullptr);
   int num_frames = absl::GetStackTrace(stacktrace.data(), stacktrace.size(),
                                        /*skip_count=*/0);
   if (num_frames <= 0) {
-    return "";
+    return {};
   }
   stacktrace.resize(num_frames);
+  return stacktrace;
+}
 
+std::string SymbolizeStackTraceImpl(absl::Span<void* const> stacktrace) {
   std::string formatted_trace = "Stack trace:\n";
   for (void* function : stacktrace) {
     char symbol_name[kMaxSymbolSize];
@@ -40,6 +45,10 @@
   return formatted_trace;
 }
 
+std::string QuicheStackTraceImpl() {
+  return SymbolizeStackTraceImpl(CurrentStackTraceImpl());
+}
+
 bool QuicheShouldRunStackTraceTestImpl() {
   void* unused[4];  // An arbitrary small number of stack frames to trace.
   int stack_traces_found =
diff --git a/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.h b/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.h
index 5228e41..6fd2a38 100644
--- a/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.h
+++ b/quiche/common/platform/default/quiche_platform_impl/quiche_stack_trace_impl.h
@@ -6,9 +6,14 @@
 #define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_STACK_TRACE_IMPL_H_
 
 #include <string>
+#include <vector>
+
+#include "absl/types/span.h"
 
 namespace quiche {
 
+std::vector<void*> CurrentStackTraceImpl();
+std::string SymbolizeStackTraceImpl(absl::Span<void* const> stacktrace);
 std::string QuicheStackTraceImpl();
 bool QuicheShouldRunStackTraceTestImpl();