// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_ #define BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_ #include "base/allocator/buildflags.h" #include "base/allocator/dispatcher/configuration.h" #include "base/allocator/dispatcher/internal/dispatch_data.h" #include "base/allocator/dispatcher/internal/tools.h" #include "base/allocator/dispatcher/subsystem.h" #include "base/allocator/partition_allocator/partition_alloc_buildflags.h" #include "base/check.h" #include "base/compiler_specific.h" #include "build/build_config.h" #if BUILDFLAG(USE_PARTITION_ALLOC) #include "base/allocator/partition_allocator/partition_alloc.h" #endif #if BUILDFLAG(USE_ALLOCATOR_SHIM) #include "base/allocator/partition_allocator/shim/allocator_shim.h" #endif #include namespace base::allocator::dispatcher::internal { #if BUILDFLAG(USE_ALLOCATOR_SHIM) using allocator_shim::AllocatorDispatch; #endif template void inline PerformObserverCheck(const std::tuple& observers, std::index_sequence, CheckObserverPredicate check_observer) { ([](bool b) { DCHECK(b); }(check_observer(std::get(observers))), ...); } template ALWAYS_INLINE void PerformAllocationNotification( const std::tuple& observers, std::index_sequence, void* address, size_t size, AllocationSubsystem subSystem, const char* type_name) { ((std::get(observers)->OnAllocation(address, size, subSystem, type_name)), ...); } template ALWAYS_INLINE void PerformFreeNotification( const std::tuple& observers, std::index_sequence, void* address) { ((std::get(observers)->OnFree(address)), ...); } // DispatcherImpl provides hooks into the various memory subsystems. These hooks // are responsible for dispatching any notification to the observers. // In order to provide as many information on the exact type of the observer and // prevent any conditional jumps in the hot allocation path, observers are // stored in a std::tuple. DispatcherImpl performs a CHECK at initialization // time to ensure they are valid. template struct DispatcherImpl { using AllObservers = std::index_sequence_for; template = true> static DispatchData GetNotificationHooks( std::tuple observers) { s_observers = std::move(observers); PerformObserverCheck(s_observers, AllObservers{}, IsValidObserver{}); return CreateDispatchData(); } private: static DispatchData CreateDispatchData() { return DispatchData() #if BUILDFLAG(USE_PARTITION_ALLOC) .SetAllocationObserverHooks(&PartitionAllocatorAllocationHook, &PartitionAllocatorFreeHook) #endif #if BUILDFLAG(USE_ALLOCATOR_SHIM) .SetAllocatorDispatch(&allocator_dispatch_) #endif ; } #if BUILDFLAG(USE_PARTITION_ALLOC) static void PartitionAllocatorAllocationHook(void* address, size_t size, const char* type_name) { DoNotifyAllocation(address, size, AllocationSubsystem::kPartitionAllocator, type_name); } static void PartitionAllocatorFreeHook(void* address) { DoNotifyFree(address); } #endif #if BUILDFLAG(USE_ALLOCATOR_SHIM) static void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) { void* const address = self->next->alloc_function(self->next, size, context); DoNotifyAllocation(address, size, AllocationSubsystem::kAllocatorShim); return address; } static void* AllocUncheckedFn(const AllocatorDispatch* self, size_t size, void* context) { void* const address = self->next->alloc_unchecked_function(self->next, size, context); DoNotifyAllocation(address, size, AllocationSubsystem::kAllocatorShim); return address; } static void* AllocZeroInitializedFn(const AllocatorDispatch* self, size_t n, size_t size, void* context) { void* const address = self->next->alloc_zero_initialized_function( self->next, n, size, context); DoNotifyAllocation(address, n * size, AllocationSubsystem::kAllocatorShim); return address; } static void* AllocAlignedFn(const AllocatorDispatch* self, size_t alignment, size_t size, void* context) { void* const address = self->next->alloc_aligned_function( self->next, alignment, size, context); DoNotifyAllocation(address, size, AllocationSubsystem::kAllocatorShim); return address; } static void* ReallocFn(const AllocatorDispatch* self, void* address, size_t size, void* context) { // Note: size == 0 actually performs free. DoNotifyFree(address); void* const reallocated_address = self->next->realloc_function(self->next, address, size, context); DoNotifyAllocation(reallocated_address, size, AllocationSubsystem::kAllocatorShim); return reallocated_address; } static void FreeFn(const AllocatorDispatch* self, void* address, void* context) { // Note: The RecordFree should be called before free_function (here and in // other places). That is because observers need to handle the allocation // being freed before calling free_function, as once the latter is executed // the address becomes available and can be allocated by another thread. // That would be racy otherwise. DoNotifyFree(address); self->next->free_function(self->next, address, context); } static size_t GetSizeEstimateFn(const AllocatorDispatch* self, void* address, void* context) { return self->next->get_size_estimate_function(self->next, address, context); } static bool ClaimedAddressFn(const AllocatorDispatch* self, void* address, void* context) { return self->next->claimed_address_function(self->next, address, context); } static unsigned BatchMallocFn(const AllocatorDispatch* self, size_t size, void** results, unsigned num_requested, void* context) { unsigned const num_allocated = self->next->batch_malloc_function( self->next, size, results, num_requested, context); for (unsigned i = 0; i < num_allocated; ++i) { DoNotifyAllocation(results[i], size, AllocationSubsystem::kAllocatorShim); } return num_allocated; } static void BatchFreeFn(const AllocatorDispatch* self, void** to_be_freed, unsigned num_to_be_freed, void* context) { // Note: The code doesn't need to protect from recursions using // ReentryGuard, see ReallocFn for details. for (unsigned i = 0; i < num_to_be_freed; ++i) { DoNotifyFree(to_be_freed[i]); } self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed, context); } static void FreeDefiniteSizeFn(const AllocatorDispatch* self, void* address, size_t size, void* context) { DoNotifyFree(address); self->next->free_definite_size_function(self->next, address, size, context); } static void TryFreeDefaultFn(const AllocatorDispatch* self, void* address, void* context) { DoNotifyFree(address); self->next->try_free_default_function(self->next, address, context); } static void* AlignedMallocFn(const AllocatorDispatch* self, size_t size, size_t alignment, void* context) { void* const address = self->next->aligned_malloc_function( self->next, size, alignment, context); DoNotifyAllocation(address, size, AllocationSubsystem::kAllocatorShim); return address; } static void* AlignedReallocFn(const AllocatorDispatch* self, void* address, size_t size, size_t alignment, void* context) { // Note: size == 0 actually performs free. DoNotifyFree(address); address = self->next->aligned_realloc_function(self->next, address, size, alignment, context); DoNotifyAllocation(address, size, AllocationSubsystem::kAllocatorShim); return address; } static void AlignedFreeFn(const AllocatorDispatch* self, void* address, void* context) { DoNotifyFree(address); self->next->aligned_free_function(self->next, address, context); } static AllocatorDispatch allocator_dispatch_; #endif ALWAYS_INLINE static void DoNotifyAllocation( void* address, size_t size, AllocationSubsystem subSystem, const char* type_name = nullptr) { PerformAllocationNotification(s_observers, AllObservers{}, address, size, subSystem, type_name); } ALWAYS_INLINE static void DoNotifyFree(void* address) { PerformFreeNotification(s_observers, AllObservers{}, address); } static std::tuple s_observers; }; template std::tuple DispatcherImpl::s_observers; #if BUILDFLAG(USE_ALLOCATOR_SHIM) template AllocatorDispatch DispatcherImpl::allocator_dispatch_ = { &AllocFn, &AllocUncheckedFn, &AllocZeroInitializedFn, &AllocAlignedFn, &ReallocFn, &FreeFn, &GetSizeEstimateFn, &ClaimedAddressFn, &BatchMallocFn, &BatchFreeFn, &FreeDefiniteSizeFn, &TryFreeDefaultFn, &AlignedMallocFn, &AlignedReallocFn, &AlignedFreeFn, nullptr}; #endif // Specialization of DispatcherImpl in case we have no observers to notify. In // this special case we return a set of null pointers as the Dispatcher must not // install any hooks at all. template <> struct DispatcherImpl<> { static DispatchData GetNotificationHooks(std::tuple<> /*observers*/) { return DispatchData() #if BUILDFLAG(USE_PARTITION_ALLOC) .SetAllocationObserverHooks(nullptr, nullptr) #endif #if BUILDFLAG(USE_ALLOCATOR_SHIM) .SetAllocatorDispatch(nullptr) #endif ; } }; // A little utility function that helps using DispatcherImpl by providing // automated type deduction for templates. template inline DispatchData GetNotificationHooks( std::tuple observers) { return DispatcherImpl::GetNotificationHooks( std::move(observers)); } } // namespace base::allocator::dispatcher::internal #endif // BASE_ALLOCATOR_DISPATCHER_INTERNAL_DISPATCHER_INTERNAL_H_