//===-- scudo_hooks_test.cpp ------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "tests/scudo_unit_test.h" #include "allocator_config.h" #include "combined.h" namespace { void *LastAllocatedPtr = nullptr; size_t LastRequestSize = 0; void *LastDeallocatedPtr = nullptr; } // namespace // Scudo defines weak symbols that can be defined by a client binary // to register callbacks at key points in the allocation timeline. In // order to enforce those invariants, we provide definitions that // update some global state every time they are called, so that tests // can inspect their effects. An unfortunate side effect of this // setup is that because those symbols are part of the binary, they // can't be selectively enabled; that means that they will get called // on unrelated tests in the same compilation unit. To mitigate this // issue, we insulate those tests in a separate compilation unit. extern "C" { __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr, size_t Size) { LastAllocatedPtr = Ptr; LastRequestSize = Size; } __attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) { LastDeallocatedPtr = Ptr; } } // Simple check that allocation callbacks, when registered, are called: // 1) __scudo_allocate_hook is called when allocating. // 2) __scudo_deallocate_hook is called when deallocating. // 3) Both hooks are called when reallocating. // 4) Neither are called for a no-op reallocation. TEST(ScudoHooksTest, AllocateHooks) { scudo::Allocator Allocator; constexpr scudo::uptr DefaultSize = 16U; constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc; // Simple allocation and deallocation. { LastAllocatedPtr = nullptr; LastRequestSize = 0; void *Ptr = Allocator.allocate(DefaultSize, Origin); EXPECT_EQ(Ptr, LastAllocatedPtr); EXPECT_EQ(DefaultSize, LastRequestSize); LastDeallocatedPtr = nullptr; Allocator.deallocate(Ptr, Origin); EXPECT_EQ(Ptr, LastDeallocatedPtr); } // Simple no-op, same size reallocation. { void *Ptr = Allocator.allocate(DefaultSize, Origin); LastAllocatedPtr = nullptr; LastRequestSize = 0; LastDeallocatedPtr = nullptr; void *NewPtr = Allocator.reallocate(Ptr, DefaultSize); EXPECT_EQ(Ptr, NewPtr); EXPECT_EQ(nullptr, LastAllocatedPtr); EXPECT_EQ(0U, LastRequestSize); EXPECT_EQ(nullptr, LastDeallocatedPtr); } // Reallocation in increasing size classes. This ensures that at // least one of the reallocations will be meaningful. { void *Ptr = Allocator.allocate(0, Origin); for (scudo::uptr ClassId = 1U; ClassId <= scudo::DefaultConfig::Primary::SizeClassMap::LargestClassId; ++ClassId) { const scudo::uptr Size = scudo::DefaultConfig::Primary::SizeClassMap::getSizeByClassId( ClassId); LastAllocatedPtr = nullptr; LastRequestSize = 0; LastDeallocatedPtr = nullptr; void *NewPtr = Allocator.reallocate(Ptr, Size); if (NewPtr != Ptr) { EXPECT_EQ(NewPtr, LastAllocatedPtr); EXPECT_EQ(Size, LastRequestSize); EXPECT_EQ(Ptr, LastDeallocatedPtr); } else { EXPECT_EQ(nullptr, LastAllocatedPtr); EXPECT_EQ(0U, LastRequestSize); EXPECT_EQ(nullptr, LastDeallocatedPtr); } Ptr = NewPtr; } } }