| //===-- 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<scudo::DefaultConfig> 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; |
| } |
| } |
| } |