diff --git a/compiler-rt/lib/scudo/standalone/allocator_config.h b/compiler-rt/lib/scudo/standalone/allocator_config.h --- a/compiler-rt/lib/scudo/standalone/allocator_config.h +++ b/compiler-rt/lib/scudo/standalone/allocator_config.h @@ -26,7 +26,7 @@ // allocator. // // struct ExampleConfig { -// // SizeClasMmap to use with the Primary. +// // SizeClassMap to use with the Primary. // using SizeClassMap = DefaultSizeClassMap; // // Indicates possible support for Memory Tagging. // static const bool MaySupportMemoryTagging = false; diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -683,6 +683,8 @@ void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment); if (LIKELY(NewPtr)) { memcpy(NewPtr, OldTaggedPtr, Min(NewSize, OldSize)); + if (UNLIKELY(&__scudo_deallocate_hook)) + __scudo_deallocate_hook(OldTaggedPtr); quarantineOrDeallocateChunk(Options, OldTaggedPtr, &OldHeader, OldSize); } return NewPtr; diff --git a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt --- a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt +++ b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt @@ -127,3 +127,11 @@ add_scudo_unittest(ScudoCxxUnitTest SOURCES ${SCUDO_CXX_UNIT_TEST_SOURCES} ADDITIONAL_RTOBJECTS RTScudoStandaloneCWrappers RTScudoStandaloneCxxWrappers) + +set(SCUDO_HOOKS_UNIT_TEST_SOURCES + scudo_hooks_test.cpp + scudo_unit_test_main.cpp +) + +add_scudo_unittest(ScudoHooksUnitTest + SOURCES ${SCUDO_HOOKS_UNIT_TEST_SOURCES}) diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_hooks_test.cpp b/compiler-rt/lib/scudo/standalone/tests/scudo_hooks_test.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/scudo/standalone/tests/scudo_hooks_test.cpp @@ -0,0 +1,145 @@ +//===-- 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" + +static constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc; + +template struct TestAllocator : scudo::Allocator { + TestAllocator() { + this->initThreadMaybe(); + if (scudo::archSupportsMemoryTagging() && + !scudo::systemDetectsMemoryTagFaultsTestOnly()) + this->disableMemoryTagging(); + } + ~TestAllocator() { this->unmapTestOnly(); } + + void *operator new(size_t size) { + void *p = nullptr; + EXPECT_EQ(0, posix_memalign(&p, alignof(TestAllocator), size)); + return p; + } + + void operator delete(void *ptr) { free(ptr); } +}; + +template struct ScudoHooksTest : public Test { + ScudoHooksTest() { Allocator = std::make_unique(); } + ~ScudoHooksTest() { Allocator->releaseToOS(); } + + void RunTest(); + + void BasicTest(scudo::uptr SizeLog); + + using AllocatorT = TestAllocator; + std::unique_ptr Allocator; +}; + +#if SCUDO_FUCHSIA +#define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ + SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidSvelteConfig) \ + SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, FuchsiaConfig) +#else +#define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ + SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidSvelteConfig) \ + SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, DefaultConfig) \ + SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidConfig) +#endif + +#define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \ + using FIXTURE##NAME##_##TYPE = FIXTURE##NAME; \ + TEST_F(FIXTURE##NAME##_##TYPE, NAME) { FIXTURE##NAME::Run(); } + +#define SCUDO_TYPED_TEST(FIXTURE, NAME) \ + template \ + struct FIXTURE##NAME : public FIXTURE { \ + void Run(); \ + }; \ + SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ + template void FIXTURE##NAME::Run() + +// 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 +// increment a counter every time they are called, so that tests can +// check whether they have been called. An unfortunate side effect is +// that because those symbols are part of the binary, they can't be +// selectively enabled; that means that they will get called on +// unreleated tests in the same compile unit. To mitigate this issue, +// we insulate those tests in a separate compile unit. This also has +// implications on platforms that strip symbols from binaries that are +// not shared libraries. +extern "C" { + +int AllocateHookCalled = 0; +int DeallocateHookCalled = 0; + +__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr, + size_t Size) { + ++AllocateHookCalled; +} +__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) { + ++DeallocateHookCalled; +} +} + +// 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. +SCUDO_TYPED_TEST(ScudoHooksTest, AllocateHooks) { + auto *Allocator = this->Allocator.get(); + constexpr scudo::uptr DefaultSize = 16U; + // Simple allocation and deallocation. + { + AllocateHookCalled = 0; + auto Ptr = Allocator->allocate(DefaultSize, Origin); + EXPECT_EQ(1, AllocateHookCalled); + + DeallocateHookCalled = 0; + Allocator->deallocate(Ptr, Origin); + EXPECT_EQ(1, DeallocateHookCalled); + } + + // Simple no-op, same size reallocation. + { + auto *Ptr = Allocator->allocate(DefaultSize, Origin); + AllocateHookCalled = 0; + DeallocateHookCalled = 0; + Allocator->reallocate(Ptr, DefaultSize); + EXPECT_EQ(0, AllocateHookCalled); + EXPECT_EQ(0, DeallocateHookCalled); + } + + // Reallocation in increasing size classes. This ensures that at + // least one of the reallocations will be meaningful. + { + auto *Ptr = Allocator->allocate(0, Origin); + for (scudo::uptr ClassId = 1U; + ClassId <= TypeParam::Primary::SizeClassMap::LargestClassId; + ++ClassId) { + const scudo::uptr Size = + TypeParam::Primary::SizeClassMap::getSizeByClassId(ClassId); + AllocateHookCalled = 0; + DeallocateHookCalled = 0; + auto *NewPtr = Allocator->reallocate(Ptr, Size); + if (NewPtr != Ptr) { + EXPECT_EQ(1, AllocateHookCalled); + EXPECT_EQ(1, DeallocateHookCalled); + } else { + EXPECT_EQ(0, AllocateHookCalled); + EXPECT_EQ(0, DeallocateHookCalled); + } + Ptr = NewPtr; + } + } +}