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