Implement QuicheWeakPtr that roughly models Chromium's base::WeakPtr API.
QUICHE has a lot of code in which two objects with roughly independent lifetimes need to interact with each other. Weak pointers are highly useful in that context and can make those interactions safer.
PiperOrigin-RevId: 699320409
diff --git a/build/source_list.bzl b/build/source_list.bzl
index bb27769..8c72689 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -56,6 +56,7 @@
"common/quiche_status_utils.h",
"common/quiche_stream.h",
"common/quiche_text_utils.h",
+ "common/quiche_weak_ptr.h",
"common/simple_buffer_allocator.h",
"common/structured_headers.h",
"common/vectorized_io_utils.h",
@@ -1098,6 +1099,7 @@
"common/quiche_random_test.cc",
"common/quiche_simple_arena_test.cc",
"common/quiche_text_utils_test.cc",
+ "common/quiche_weak_ptr_test.cc",
"common/simple_buffer_allocator_test.cc",
"common/structured_headers_generated_test.cc",
"common/structured_headers_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 988762f..9c50ad0 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -56,6 +56,7 @@
"src/quiche/common/quiche_status_utils.h",
"src/quiche/common/quiche_stream.h",
"src/quiche/common/quiche_text_utils.h",
+ "src/quiche/common/quiche_weak_ptr.h",
"src/quiche/common/simple_buffer_allocator.h",
"src/quiche/common/structured_headers.h",
"src/quiche/common/vectorized_io_utils.h",
@@ -1099,6 +1100,7 @@
"src/quiche/common/quiche_random_test.cc",
"src/quiche/common/quiche_simple_arena_test.cc",
"src/quiche/common/quiche_text_utils_test.cc",
+ "src/quiche/common/quiche_weak_ptr_test.cc",
"src/quiche/common/simple_buffer_allocator_test.cc",
"src/quiche/common/structured_headers_generated_test.cc",
"src/quiche/common/structured_headers_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 5aabda8..e4b767e 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -55,6 +55,7 @@
"quiche/common/quiche_status_utils.h",
"quiche/common/quiche_stream.h",
"quiche/common/quiche_text_utils.h",
+ "quiche/common/quiche_weak_ptr.h",
"quiche/common/simple_buffer_allocator.h",
"quiche/common/structured_headers.h",
"quiche/common/vectorized_io_utils.h",
@@ -1098,6 +1099,7 @@
"quiche/common/quiche_random_test.cc",
"quiche/common/quiche_simple_arena_test.cc",
"quiche/common/quiche_text_utils_test.cc",
+ "quiche/common/quiche_weak_ptr_test.cc",
"quiche/common/simple_buffer_allocator_test.cc",
"quiche/common/structured_headers_generated_test.cc",
"quiche/common/structured_headers_test.cc",
diff --git a/quiche/common/quiche_weak_ptr.h b/quiche/common/quiche_weak_ptr.h
new file mode 100644
index 0000000..c0d7b49
--- /dev/null
+++ b/quiche/common/quiche_weak_ptr.h
@@ -0,0 +1,128 @@
+// 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.
+
+// The API in this header provides ability for objects to create weak pointers
+// to themselves. Unlike a regular pointer, a weak pointer is aware as to
+// whether the object it points to is still alive or has been deleted. Unlike
+// std::weak_ptr, QuicheWeakPtr does not require the referred object to be owned
+// by an std::shared_ptr, and is not thread-safe. Conceptually, this is similar
+// to base::WeakPtrFactory in Chromium, though the API and the implementation
+// are dramatically simplified.
+//
+// Example usage:
+//
+// class MyClass {
+// public:
+// void PerformAsyncOperation() {
+// ScheduleOperation([weak_this = weak_factory_.Create()] {
+// MyClass* self = weak_this.GetIfAvaliable();
+// if (self == nullptr) {
+// return;
+// }
+// self->OnOperationComplete();
+// });
+// }
+//
+// private:
+// quiche::QuicheWeakPtrFactory<MyClass> weak_factory_; // Must be last
+// };
+
+#ifndef QUICHE_COMMON_QUICHE_WEAK_PTR_H_
+#define QUICHE_COMMON_QUICHE_WEAK_PTR_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "absl/base/nullability.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace quiche {
+
+template <typename T>
+class QuicheWeakPtrFactory;
+
+// QuicheWeakPtr contains a pointer to an object that may or may not be alive,
+// or nullptr.
+template <typename T>
+class QUICHE_NO_EXPORT QuicheWeakPtr final {
+ public:
+ // Initializes a null weak pointer.
+ QuicheWeakPtr() = default;
+
+ // Returns the pointer to the underlying object if it is alive, or nullptr
+ // otherwise.
+ absl::Nullable<T*> GetIfAvaliable() const {
+ return control_block_ != nullptr ? control_block_->Get() : nullptr;
+ }
+
+ // Returns true if the underlying object is alive.
+ bool IsValid() const {
+ return control_block_ != nullptr ? control_block_->IsValid() : false;
+ }
+
+ private:
+ friend class QuicheWeakPtrFactory<T>;
+
+ // An object shared by all of the weak pointers pointing to a given object.
+ // Initially it points to the object itself; when the object is destryoed, the
+ // contained pointer is set to nullptr.
+ class ControlBlock {
+ public:
+ explicit ControlBlock(absl::Nonnull<T*> object) : object_(object) {}
+
+ absl::Nullable<T*> Get() const { return object_; }
+ void Clear() { object_ = nullptr; }
+ bool IsValid() const { return object_ != nullptr; }
+
+ private:
+ absl::Nullable<T*> object_;
+ };
+
+ explicit QuicheWeakPtr(std::shared_ptr<ControlBlock> block)
+ : control_block_(std::move(block)) {}
+
+ absl::Nullable<std::shared_ptr<ControlBlock>> control_block_ = nullptr;
+};
+
+// QuicheWeakPtrFactory generates weak pointers to the parent object, and cleans
+// up the state when the parent object is destroyed. In order to do that
+// correctly, it MUST be the last field in the object holding it.
+template <typename T>
+class QUICHE_NO_EXPORT QuicheWeakPtrFactory final {
+ public:
+ explicit QuicheWeakPtrFactory(absl::Nonnull<T*> object)
+ : control_block_(std::make_shared<ControlBlock>(object)) {
+ // Chromium uses a Clang plugin to ensure that WeakPtrFactory objects are
+ // always last; QUICHE does not have infrastructure for that, but we can do
+ // some basic checks to prevent the API misuse.
+ const uintptr_t factory_address = reinterpret_cast<uintptr_t>(this);
+ const uintptr_t object_address_start = reinterpret_cast<uintptr_t>(object);
+ const uintptr_t object_address_end = object_address_start + sizeof(object);
+ QUICHE_DCHECK(factory_address >= object_address_start &&
+ factory_address <= object_address_end)
+ << "WeakPtrFactory<T> must be a member of T";
+ }
+ ~QuicheWeakPtrFactory() { control_block_->Clear(); }
+
+ QuicheWeakPtrFactory(const QuicheWeakPtrFactory&) = delete;
+ QuicheWeakPtrFactory& operator=(const QuicheWeakPtrFactory&) = delete;
+
+ // QuicheWeakPtrFactory is attached to the parent object; moving either the
+ // parent object or the factory would almost certainly be a mistake, since all
+ // of the existing WeakPtrs would point to the old object.
+ QuicheWeakPtrFactory(QuicheWeakPtrFactory&&) = delete;
+ QuicheWeakPtrFactory& operator=(QuicheWeakPtrFactory&&) = delete;
+
+ // Creates a weak pointer to the parent object.
+ QuicheWeakPtr<T> Create() const { return QuicheWeakPtr<T>(control_block_); }
+
+ private:
+ using ControlBlock = typename QuicheWeakPtr<T>::ControlBlock;
+ absl::Nonnull<std::shared_ptr<ControlBlock>> control_block_;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_QUICHE_WEAK_PTR_H_
diff --git a/quiche/common/quiche_weak_ptr_test.cc b/quiche/common/quiche_weak_ptr_test.cc
new file mode 100644
index 0000000..73e9c2d
--- /dev/null
+++ b/quiche/common/quiche_weak_ptr_test.cc
@@ -0,0 +1,74 @@
+// 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.
+
+#include "quiche/common/quiche_weak_ptr.h"
+
+#include <memory>
+#include <utility>
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace {
+
+class TestClass {
+ public:
+ QuicheWeakPtr<TestClass> CreateWeakPtr() { return weak_factory_.Create(); }
+
+ private:
+ QuicheWeakPtrFactory<TestClass> weak_factory_{this};
+};
+
+TEST(QuicheWeakPtrTest, Empty) {
+ QuicheWeakPtr<TestClass> ptr;
+ EXPECT_FALSE(ptr.IsValid());
+ EXPECT_EQ(ptr.GetIfAvaliable(), nullptr);
+}
+
+TEST(QuicheWeakPtrTest, Valid) {
+ TestClass object;
+ QuicheWeakPtr<TestClass> ptr = object.CreateWeakPtr();
+ EXPECT_TRUE(ptr.IsValid());
+ EXPECT_EQ(ptr.GetIfAvaliable(), &object);
+}
+
+TEST(QuicheWeakPtrTest, ValidCopy) {
+ TestClass object;
+ QuicheWeakPtr<TestClass> ptr = object.CreateWeakPtr();
+ QuicheWeakPtr<TestClass> ptr_copy = ptr;
+ EXPECT_TRUE(ptr.IsValid());
+ EXPECT_TRUE(ptr_copy.IsValid());
+ EXPECT_EQ(ptr.GetIfAvaliable(), &object);
+ EXPECT_EQ(ptr_copy.GetIfAvaliable(), &object);
+}
+
+TEST(QuicheWeakPtrTest, EmptyAfterMove) {
+ TestClass object;
+ QuicheWeakPtr<TestClass> ptr = object.CreateWeakPtr();
+ QuicheWeakPtr<TestClass> ptr_moved = std::move(ptr);
+ EXPECT_FALSE(ptr.IsValid()); // NOLINT(bugprone-use-after-move)
+ EXPECT_TRUE(ptr_moved.IsValid());
+ EXPECT_EQ(ptr.GetIfAvaliable(), nullptr); // NOLINT(bugprone-use-after-move)
+ EXPECT_EQ(ptr_moved.GetIfAvaliable(), &object);
+}
+
+TEST(QuicheWeakPtrTest, Expired) {
+ QuicheWeakPtr<TestClass> ptr;
+ {
+ TestClass object;
+ ptr = object.CreateWeakPtr();
+ EXPECT_TRUE(ptr.IsValid());
+ }
+ EXPECT_FALSE(ptr.IsValid());
+}
+
+TEST(QuicheWeakPtrTest, OutOfClassFactory) {
+ bool object = false;
+ EXPECT_QUICHE_DEBUG_DEATH(
+ std::make_unique<QuicheWeakPtrFactory<bool>>(&object),
+ "must be a member of T");
+}
+
+} // namespace
+} // namespace quiche