xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/common/lifetime_tracking.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2024 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // This file implements helper classes to track c++ object lifetimes. They are
6 // useful to debug use-after-free issues in environments where the cost of ASAN
7 // is too high.
8 //
9 // Suppose you have an object of type "MyClass" and a raw pointer "ptr" pointing
10 // to it, and you suspect a dereference of "ptr" is unsafe because the object it
11 // points to is dead. You can do
12 //
13 // (1) Add a LifetimeTrackable member to "MyClass". Alternatively, you can also
14 //     change MyClass to inherit from LifetimeTrackable.
15 //
16 //     struct MyClass {
17 //       ...... existing members ......
18 //       LifetimeTrackable trackable;
19 //     }
20 //
21 // (2) Add a LifetimeTracker alongside the "ptr".
22 //
23 //     ptr = new MyClass().
24 //     tracker = ptr->trackable.NewTracker();
25 //
26 // (3) Before the potentially dangerous dereference, check whether *ptr is dead:
27 //
28 //     if (tracker.IsTrackedObjectDead()) {
29 //       // ptr->trackable has been destructed. Log its destruction stack below.
30 //       QUICHE_LOG(ERROR) << "*ptr has bee destructed: " << tracker;
31 //     }
32 //     ptr->MethodCall();  // Possibly a use-after-free
33 //
34 // All classes defined in this file are not thread safe.
35 
36 #ifndef QUICHE_COMMON_LIFETIME_TRACKING_H_
37 #define QUICHE_COMMON_LIFETIME_TRACKING_H_
38 
39 #include <memory>
40 #include <optional>
41 #include <utility>
42 #include <vector>
43 
44 #include "absl/strings/str_format.h"
45 #include "quiche/common/platform/api/quiche_export.h"
46 #include "quiche/common/platform/api/quiche_logging.h"
47 #include "quiche/common/platform/api/quiche_stack_trace.h"
48 
49 namespace quiche {
50 namespace test {
51 class LifetimeTrackingTest;
52 }  // namespace test
53 
54 // LifetimeInfo holds information about a LifetimeTrackable object.
55 struct QUICHE_EXPORT LifetimeInfo {
IsDeadLifetimeInfo56   bool IsDead() const { return destructor_stack.has_value(); }
57 
58   // If IsDead(), the stack when the LifetimeTrackable object is destructed.
59   std::optional<std::vector<void*>> destructor_stack;
60 };
61 
62 // LifetimeTracker tracks the lifetime of a LifetimeTrackable object, by holding
63 // a reference to its LifetimeInfo.
64 class QUICHE_EXPORT LifetimeTracker {
65  public:
66   // Copy constructor and assignment operator allow this tracker to track the
67   // same object as |other|.
LifetimeTracker(const LifetimeTracker & other)68   LifetimeTracker(const LifetimeTracker& other) { CopyFrom(other); }
69   LifetimeTracker& operator=(const LifetimeTracker& other) {
70     CopyFrom(other);
71     return *this;
72   }
73 
74   // Move constructor and assignment are implemented as copies, to prevent the
75   // moved-from object from tracking "nothing".
LifetimeTracker(LifetimeTracker && other)76   LifetimeTracker(LifetimeTracker&& other) { CopyFrom(other); }
77   LifetimeTracker& operator=(LifetimeTracker&& other) {
78     CopyFrom(other);
79     return *this;
80   }
81 
82   // Whether the tracked object is dead.
IsTrackedObjectDead()83   bool IsTrackedObjectDead() const { return info_->IsDead(); }
84 
85   template <typename Sink>
AbslStringify(Sink & sink,const LifetimeTracker & tracker)86   friend void AbslStringify(Sink& sink, const LifetimeTracker& tracker) {
87     if (tracker.info_->IsDead()) {
88       absl::Format(&sink, "Tracked object has died with %v",
89                    SymbolizeStackTrace(*tracker.info_->destructor_stack));
90     } else {
91       absl::Format(&sink, "Tracked object is alive.");
92     }
93   }
94 
95  private:
96   friend class LifetimeTrackable;
LifetimeTracker(std::shared_ptr<const LifetimeInfo> info)97   explicit LifetimeTracker(std::shared_ptr<const LifetimeInfo> info)
98       : info_(std::move(info)) {
99     QUICHE_CHECK(info_ != nullptr)
100         << "Passed a null info pointer into the lifetime tracker";
101   }
CopyFrom(const LifetimeTracker & other)102   void CopyFrom(const LifetimeTracker& other) { info_ = other.info_; }
103 
104   std::shared_ptr<const LifetimeInfo> info_;
105 };
106 
107 // LifetimeTrackable allows its lifetime to be tracked by any number of
108 // LifetimeTracker(s).
109 class QUICHE_EXPORT LifetimeTrackable {
110  public:
111   LifetimeTrackable() = default;
~LifetimeTrackable()112   virtual ~LifetimeTrackable() {
113     if (info_ != nullptr) {
114       info_->destructor_stack = CurrentStackTrace();
115     }
116   }
117 
118   // LifetimeTrackable only tracks the memory occupied by itself. All copy/move
119   // constructors and assignments are no-op.
LifetimeTrackable(const LifetimeTrackable &)120   LifetimeTrackable(const LifetimeTrackable&) : LifetimeTrackable() {}
121   LifetimeTrackable& operator=(const LifetimeTrackable&) { return *this; }
LifetimeTrackable(LifetimeTrackable &&)122   LifetimeTrackable(LifetimeTrackable&&) : LifetimeTrackable() {}
123   LifetimeTrackable& operator=(LifetimeTrackable&&) { return *this; }
124 
NewTracker()125   LifetimeTracker NewTracker() {
126     if (info_ == nullptr) {
127       info_ = std::make_shared<LifetimeInfo>();
128     }
129     return LifetimeTracker(info_);
130   }
131 
132  private:
133   friend class test::LifetimeTrackingTest;
134   // nullptr if this object is not tracked by any LifetimeTracker.
135   std::shared_ptr<LifetimeInfo> info_;
136 };
137 
138 }  // namespace quiche
139 
140 #endif  // QUICHE_COMMON_LIFETIME_TRACKING_H_
141