Update PDFium PartitionAlloc hooking

Roll in [1-3] from upstream, which update PartitionAlloc's hooking
support to allow "override" hooks (GWP-ASan hooks that override
alloc/free) as well as "observer" hooks (pre-existing profiler hooks).

[1] https://crrev.com/c/1585325
[2] https://crrev.com/c/1588946
[3] https://crrev.com/c/1583179

Bug: 956824
Change-Id: Ibe5aa244293d64a39d5a9b82a7299cad32bc022e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/55132
Reviewed-by: Chris Palmer <palmer@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc.cc b/third_party/base/allocator/partition_allocator/partition_alloc.cc
index df5d3f1..e0bae85 100644
--- a/third_party/base/allocator/partition_allocator/partition_alloc.cc
+++ b/third_party/base/allocator/partition_allocator/partition_alloc.cc
@@ -66,9 +66,108 @@
 static bool g_initialized = false;
 
 void (*internal::PartitionRootBase::gOomHandlingFunction)() = nullptr;
-PartitionAllocHooks::AllocationHook* PartitionAllocHooks::allocation_hook_ =
-    nullptr;
-PartitionAllocHooks::FreeHook* PartitionAllocHooks::free_hook_ = nullptr;
+std::atomic<bool> PartitionAllocHooks::hooks_enabled_(false);
+subtle::SpinLock PartitionAllocHooks::set_hooks_lock_;
+std::atomic<PartitionAllocHooks::AllocationObserverHook*>
+    PartitionAllocHooks::allocation_observer_hook_(nullptr);
+std::atomic<PartitionAllocHooks::FreeObserverHook*>
+    PartitionAllocHooks::free_observer_hook_(nullptr);
+std::atomic<PartitionAllocHooks::AllocationOverrideHook*>
+    PartitionAllocHooks::allocation_override_hook_(nullptr);
+std::atomic<PartitionAllocHooks::FreeOverrideHook*>
+    PartitionAllocHooks::free_override_hook_(nullptr);
+std::atomic<PartitionAllocHooks::ReallocOverrideHook*>
+    PartitionAllocHooks::realloc_override_hook_(nullptr);
+
+void PartitionAllocHooks::SetObserverHooks(AllocationObserverHook* alloc_hook,
+                                           FreeObserverHook* free_hook) {
+  subtle::SpinLock::Guard guard(set_hooks_lock_);
+
+  // Chained hooks are not supported. Registering a non-null hook when a
+  // non-null hook is already registered indicates somebody is trying to
+  // overwrite a hook.
+  CHECK((!allocation_observer_hook_ && !free_observer_hook_) ||
+        (!alloc_hook && !free_hook));
+  allocation_observer_hook_ = alloc_hook;
+  free_observer_hook_ = free_hook;
+
+  hooks_enabled_ = allocation_observer_hook_ || allocation_override_hook_;
+}
+
+void PartitionAllocHooks::SetOverrideHooks(AllocationOverrideHook* alloc_hook,
+                                           FreeOverrideHook* free_hook,
+                                           ReallocOverrideHook realloc_hook) {
+  subtle::SpinLock::Guard guard(set_hooks_lock_);
+
+  CHECK((!allocation_override_hook_ && !free_override_hook_ &&
+         !realloc_override_hook_) ||
+        (!alloc_hook && !free_hook && !realloc_hook));
+  allocation_override_hook_ = alloc_hook;
+  free_override_hook_ = free_hook;
+  realloc_override_hook_ = realloc_hook;
+
+  hooks_enabled_ = allocation_observer_hook_ || allocation_override_hook_;
+}
+
+void PartitionAllocHooks::AllocationObserverHookIfEnabled(
+    void* address,
+    size_t size,
+    const char* type_name) {
+  if (AllocationObserverHook* hook =
+          allocation_observer_hook_.load(std::memory_order_relaxed)) {
+    hook(address, size, type_name);
+  }
+}
+
+bool PartitionAllocHooks::AllocationOverrideHookIfEnabled(
+    void** out,
+    int flags,
+    size_t size,
+    const char* type_name) {
+  if (AllocationOverrideHook* hook =
+          allocation_override_hook_.load(std::memory_order_relaxed)) {
+    return hook(out, flags, size, type_name);
+  }
+  return false;
+}
+
+void PartitionAllocHooks::FreeObserverHookIfEnabled(void* address) {
+  if (FreeObserverHook* hook =
+          free_observer_hook_.load(std::memory_order_relaxed)) {
+    hook(address);
+  }
+}
+
+bool PartitionAllocHooks::FreeOverrideHookIfEnabled(void* address) {
+  if (FreeOverrideHook* hook =
+          free_override_hook_.load(std::memory_order_relaxed)) {
+    return hook(address);
+  }
+  return false;
+}
+
+void PartitionAllocHooks::ReallocObserverHookIfEnabled(void* old_address,
+                                                       void* new_address,
+                                                       size_t size,
+                                                       const char* type_name) {
+  // Report a reallocation as a free followed by an allocation.
+  AllocationObserverHook* allocation_hook =
+      allocation_observer_hook_.load(std::memory_order_relaxed);
+  FreeObserverHook* free_hook =
+      free_observer_hook_.load(std::memory_order_relaxed);
+  if (allocation_hook && free_hook) {
+    free_hook(old_address);
+    allocation_hook(new_address, size, type_name);
+  }
+}
+bool PartitionAllocHooks::ReallocOverrideHookIfEnabled(size_t* out,
+                                                       void* address) {
+  if (ReallocOverrideHook* hook =
+          realloc_override_hook_.load(std::memory_order_relaxed)) {
+    return hook(out, address);
+  }
+  return false;
+}
 
 static void PartitionAllocBaseInit(internal::PartitionRootBase* root) {
   DCHECK(!root->initialized);
@@ -282,41 +381,53 @@
     internal::PartitionExcessiveAllocationSize();
   }
 
-  internal::PartitionPage* page = internal::PartitionPage::FromPointer(
-      internal::PartitionCookieFreePointerAdjust(ptr));
-  // TODO(palmer): See if we can afford to make this a CHECK.
-  DCHECK(root->IsValidPage(page));
+  const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
+  bool overridden = false;
+  size_t actual_old_size;
+  if (UNLIKELY(hooks_enabled)) {
+    overridden = PartitionAllocHooks::ReallocOverrideHookIfEnabled(
+        &actual_old_size, ptr);
+  }
+  if (LIKELY(!overridden)) {
+    internal::PartitionPage* page = internal::PartitionPage::FromPointer(
+        internal::PartitionCookieFreePointerAdjust(ptr));
+    // TODO(palmer): See if we can afford to make this a CHECK.
+    DCHECK(root->IsValidPage(page));
 
-  if (UNLIKELY(page->bucket->is_direct_mapped())) {
-    // We may be able to perform the realloc in place by changing the
-    // accessibility of memory pages and, if reducing the size, decommitting
-    // them.
-    if (PartitionReallocDirectMappedInPlace(root, page, new_size)) {
-      PartitionAllocHooks::ReallocHookIfEnabled(ptr, ptr, new_size, type_name);
+    if (UNLIKELY(page->bucket->is_direct_mapped())) {
+      // We may be able to perform the realloc in place by changing the
+      // accessibility of memory pages and, if reducing the size, decommitting
+      // them.
+      if (PartitionReallocDirectMappedInPlace(root, page, new_size)) {
+        if (UNLIKELY(hooks_enabled)) {
+          PartitionAllocHooks::ReallocObserverHookIfEnabled(ptr, ptr, new_size,
+                                                            type_name);
+        }
+        return ptr;
+      }
+    }
+
+    const size_t actual_new_size = root->ActualSize(new_size);
+    actual_old_size = PartitionAllocGetSize(ptr);
+
+    // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the
+    // new size is a significant percentage smaller. We could do the same if we
+    // determine it is a win.
+    if (actual_new_size == actual_old_size) {
+      // Trying to allocate a block of size |new_size| would give us a block of
+      // the same size as the one we've already got, so re-use the allocation
+      // after updating statistics (and cookies, if present).
+      page->set_raw_size(internal::PartitionCookieSizeAdjustAdd(new_size));
+#if DCHECK_IS_ON()
+      // Write a new trailing cookie when it is possible to keep track of
+      // |new_size| via the raw size pointer.
+      if (page->get_raw_size_ptr())
+        internal::PartitionCookieWriteValue(static_cast<char*>(ptr) + new_size);
+#endif
       return ptr;
     }
   }
 
-  size_t actual_new_size = root->ActualSize(new_size);
-  size_t actual_old_size = PartitionAllocGetSize(ptr);
-
-  // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the
-  // new size is a significant percentage smaller. We could do the same if we
-  // determine it is a win.
-  if (actual_new_size == actual_old_size) {
-    // Trying to allocate a block of size |new_size| would give us a block of
-    // the same size as the one we've already got, so re-use the allocation
-    // after updating statistics (and cookies, if present).
-    page->set_raw_size(internal::PartitionCookieSizeAdjustAdd(new_size));
-#if DCHECK_IS_ON()
-    // Write a new trailing cookie when it is possible to keep track of
-    // |new_size| via the raw size pointer.
-    if (page->get_raw_size_ptr())
-      internal::PartitionCookieWriteValue(static_cast<char*>(ptr) + new_size);
-#endif
-    return ptr;
-  }
-
   // This realloc cannot be resized in-place. Sadness.
   void* ret = PartitionAllocGenericFlags(root, flags, new_size, type_name);
   if (!ret) {
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc.h b/third_party/base/allocator/partition_allocator/partition_alloc.h
index c3491a4..88f37bc 100644
--- a/third_party/base/allocator/partition_allocator/partition_alloc.h
+++ b/third_party/base/allocator/partition_allocator/partition_alloc.h
@@ -212,56 +212,74 @@
 
 BASE_EXPORT void PartitionAllocGlobalInit(void (*oom_handling_function)());
 
+// PartitionAlloc supports setting hooks to observe allocations/frees as they
+// occur as well as 'override' hooks that allow overriding those operations.
 class BASE_EXPORT PartitionAllocHooks {
  public:
-  typedef void AllocationHook(void* address, size_t, const char* type_name);
-  typedef void FreeHook(void* address);
-
-  // To unhook, call Set*Hook with nullptr.
-  static void SetAllocationHook(AllocationHook* hook) {
-    // Chained allocation hooks are not supported. Registering a non-null
-    // hook when a non-null hook is already registered indicates somebody is
-    // trying to overwrite a hook.
-    CHECK(!hook || !allocation_hook_);
-    allocation_hook_ = hook;
-  }
-  static void SetFreeHook(FreeHook* hook) {
-    CHECK(!hook || !free_hook_);
-    free_hook_ = hook;
-  }
-
-  static void AllocationHookIfEnabled(void* address,
+  // Log allocation and free events.
+  typedef void AllocationObserverHook(void* address,
                                       size_t size,
-                                      const char* type_name) {
-    AllocationHook* hook = allocation_hook_;
-    if (UNLIKELY(hook != nullptr))
-      hook(address, size, type_name);
+                                      const char* type_name);
+  typedef void FreeObserverHook(void* address);
+
+  // If it returns true, the allocation has been overridden with the pointer in
+  // *out.
+  typedef bool AllocationOverrideHook(void** out,
+                                      int flags,
+                                      size_t size,
+                                      const char* type_name);
+  // If it returns true, then the allocation was overridden and has been freed.
+  typedef bool FreeOverrideHook(void* address);
+  // If it returns true, the underlying allocation is overridden and *out holds
+  // the size of the underlying allocation.
+  typedef bool ReallocOverrideHook(size_t* out, void* address);
+
+  // To unhook, call Set*Hooks with nullptrs.
+  static void SetObserverHooks(AllocationObserverHook* alloc_hook,
+                               FreeObserverHook* free_hook);
+  static void SetOverrideHooks(AllocationOverrideHook* alloc_hook,
+                               FreeOverrideHook* free_hook,
+                               ReallocOverrideHook realloc_hook);
+
+  // Helper method to check whether hooks are enabled. This is an optimization
+  // so that if a function needs to call observer and override hooks in two
+  // different places this value can be cached and only loaded once.
+  static bool AreHooksEnabled() {
+    return hooks_enabled_.load(std::memory_order_relaxed);
   }
 
-  static void FreeHookIfEnabled(void* address) {
-    FreeHook* hook = free_hook_;
-    if (UNLIKELY(hook != nullptr))
-      hook(address);
-  }
+  static void AllocationObserverHookIfEnabled(void* address,
+                                              size_t size,
+                                              const char* type_name);
+  static bool AllocationOverrideHookIfEnabled(void** out,
+                                              int flags,
+                                              size_t size,
+                                              const char* type_name);
 
-  static void ReallocHookIfEnabled(void* old_address,
-                                   void* new_address,
-                                   size_t size,
-                                   const char* type_name) {
-    // Report a reallocation as a free followed by an allocation.
-    AllocationHook* allocation_hook = allocation_hook_;
-    FreeHook* free_hook = free_hook_;
-    if (UNLIKELY(allocation_hook && free_hook)) {
-      free_hook(old_address);
-      allocation_hook(new_address, size, type_name);
-    }
-  }
+  static void FreeObserverHookIfEnabled(void* address);
+  static bool FreeOverrideHookIfEnabled(void* address);
+
+  static void ReallocObserverHookIfEnabled(void* old_address,
+                                           void* new_address,
+                                           size_t size,
+                                           const char* type_name);
+  static bool ReallocOverrideHookIfEnabled(size_t* out, void* address);
 
  private:
-  // Pointers to hook functions that PartitionAlloc will call on allocation and
-  // free if the pointers are non-null.
-  static AllocationHook* allocation_hook_;
-  static FreeHook* free_hook_;
+  // Single bool that is used to indicate whether observer or allocation hooks
+  // are set to reduce the numbers of loads required to check whether hooking is
+  // enabled.
+  static std::atomic<bool> hooks_enabled_;
+
+  // Lock used to synchronize Set*Hooks calls.
+  static subtle::SpinLock set_hooks_lock_;
+
+  static std::atomic<AllocationObserverHook*> allocation_observer_hook_;
+  static std::atomic<FreeObserverHook*> free_observer_hook_;
+
+  static std::atomic<AllocationOverrideHook*> allocation_override_hook_;
+  static std::atomic<FreeOverrideHook*> free_override_hook_;
+  static std::atomic<ReallocOverrideHook*> realloc_override_hook_;
 };
 
 ALWAYS_INLINE void* PartitionRoot::Alloc(size_t size, const char* type_name) {
@@ -280,6 +298,16 @@
   CHECK(result);
   return result;
 #else
+  void* result;
+  const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
+  if (UNLIKELY(hooks_enabled)) {
+    if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(&result, flags,
+                                                             size, type_name)) {
+      PartitionAllocHooks::AllocationObserverHookIfEnabled(result, size,
+                                                           type_name);
+      return result;
+    }
+  }
   size_t requested_size = size;
   size = internal::PartitionCookieSizeAdjustAdd(size);
   DCHECK(this->initialized);
@@ -287,9 +315,11 @@
   DCHECK(index < this->num_buckets);
   DCHECK(size == index << kBucketShift);
   internal::PartitionBucket* bucket = &this->buckets()[index];
-  void* result = AllocFromBucket(bucket, flags, size);
-  PartitionAllocHooks::AllocationHookIfEnabled(result, requested_size,
-                                               type_name);
+  result = AllocFromBucket(bucket, flags, size);
+  if (UNLIKELY(hooks_enabled)) {
+    PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size,
+                                                         type_name);
+  }
   return result;
 #endif  // defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
 }
@@ -318,10 +348,14 @@
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
   free(ptr);
 #else
-  void* original_ptr = ptr;
   // TODO(palmer): Check ptr alignment before continuing. Shall we do the check
   // inside PartitionCookieFreePointerAdjust?
-  PartitionAllocHooks::FreeHookIfEnabled(original_ptr);
+  if (PartitionAllocHooks::AreHooksEnabled()) {
+    PartitionAllocHooks::FreeObserverHookIfEnabled(ptr);
+    if (PartitionAllocHooks::FreeOverrideHookIfEnabled(ptr))
+      return;
+  }
+
   ptr = internal::PartitionCookieFreePointerAdjust(ptr);
   internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
   // TODO(palmer): See if we can afford to make this a CHECK.
@@ -365,17 +399,29 @@
   return result;
 #else
   DCHECK(root->initialized);
+  void* result;
+  const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
+  if (UNLIKELY(hooks_enabled)) {
+    if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(&result, flags,
+                                                             size, type_name)) {
+      PartitionAllocHooks::AllocationObserverHookIfEnabled(result, size,
+                                                           type_name);
+      return result;
+    }
+  }
   size_t requested_size = size;
   size = internal::PartitionCookieSizeAdjustAdd(size);
   internal::PartitionBucket* bucket = PartitionGenericSizeToBucket(root, size);
-  void* ret = nullptr;
   {
     subtle::SpinLock::Guard guard(root->lock);
-    ret = root->AllocFromBucket(bucket, flags, size);
+    result = root->AllocFromBucket(bucket, flags, size);
   }
-  PartitionAllocHooks::AllocationHookIfEnabled(ret, requested_size, type_name);
+  if (UNLIKELY(hooks_enabled)) {
+    PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size,
+                                                         type_name);
+  }
 
-  return ret;
+  return result;
 #endif
 }
 
@@ -399,7 +445,12 @@
   if (UNLIKELY(!ptr))
     return;
 
-  PartitionAllocHooks::FreeHookIfEnabled(ptr);
+  if (PartitionAllocHooks::AreHooksEnabled()) {
+    PartitionAllocHooks::FreeObserverHookIfEnabled(ptr);
+    if (PartitionAllocHooks::FreeOverrideHookIfEnabled(ptr))
+      return;
+  }
+
   ptr = internal::PartitionCookieFreePointerAdjust(ptr);
   internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
   // TODO(palmer): See if we can afford to make this a CHECK.