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/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp --- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp @@ -769,3 +769,56 @@ } #endif + +// 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 used even on +// other tests that don't care about them. This also has implications on +// platforms that strip symbols from binaries that are not shared libraries. +extern "C" { + +int AllocateHookCalled = 0; +int DeallocateHookCalled = 0; + +void __scudo_allocate_hook(void *Ptr, size_t Size) { ++AllocateHookCalled; } + +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(ScudoCombinedTest, AllocateHooks) { + auto *Allocator = this->Allocator.get(); + + { + AllocateHookCalled = 0; + auto Ptr = Allocator->allocate(16, Origin); + EXPECT_EQ(1, AllocateHookCalled); + + DeallocateHookCalled = 0; + Allocator->deallocate(Ptr, Origin); + EXPECT_EQ(1, DeallocateHookCalled); + } + { + auto Ptr = Allocator->allocate(16, Origin); + AllocateHookCalled = 0; + DeallocateHookCalled = 0; + auto NewPtr = + Allocator->reallocate(Ptr, 256); // Arbitrary size; the intent is for + // the reallocation to need another + // size class + EXPECT_EQ(1, AllocateHookCalled); + EXPECT_EQ(1, DeallocateHookCalled); + + Allocator->reallocate(NewPtr, 256); // Same size, so that the reallocation + // is a no-op. + EXPECT_EQ(1, AllocateHookCalled); + EXPECT_EQ(1, DeallocateHookCalled); + } +}