From 31e0dac25b22e6d58b70890edbb74be2efdeae5d Mon Sep 17 00:00:00 2001 From: Architector #4 Date: Sun, 25 Jan 2026 09:07:31 +0300 Subject: [PATCH] Disable allocation pooling when AddressSanitizer is enabled Allocation pooling obscures whether or not memory is freed or not from AddressSanitizer, making it less useful. With this patch, the game works same as it ever was, but AddressSanitizer now properly finds some errors that previously would only trigger spurious really cryptic UndefinedBehaviorSanitizer errors. An alternative would be to, instead of doing this, use ASan's API to poison/unpoison memory. However, the functions to do that require an allocation size to be provided, and the memory pooling functions don't get those. --- Source/System/Entity.cpp | 20 ++++++++++++++++++++ Source/System/Entity.h | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/Source/System/Entity.cpp b/Source/System/Entity.cpp index e3fb49bb67..3aa2579c63 100644 --- a/Source/System/Entity.cpp +++ b/Source/System/Entity.cpp @@ -237,6 +237,11 @@ namespace RTE { } void Entity::ClassInfo::FillPool(int fillAmount) { +#ifdef __SANITIZE_ADDRESS__ + // If we have ASan, make this a no-op. + (void)(fillAmount); // Silence warning about unused variable. +#else + // Default to the set block allocation size if fillAmount is 0 if (fillAmount <= 0) { fillAmount = m_PoolAllocBlockCount; @@ -248,6 +253,7 @@ namespace RTE { m_AllocatedPool.push_back(m_Allocate()); } } +#endif } bool Entity::ClassInfo::IsClassOrChildClassOf(const ClassInfo* classInfoToCheck) const { @@ -260,6 +266,13 @@ namespace RTE { } void* Entity::ClassInfo::GetPoolMemory() { +#ifdef __SANITIZE_ADDRESS__ + // If compiled with ASan, sidestep pooling and just use the allocator normally. + + void* foundMemory = m_Allocate(); + RTEAssert(foundMemory, "m_Allocate failed! to make memory!"); +#else + std::lock_guard guard(m_Mutex); RTEAssert(IsConcrete(), "Trying to get pool memory of an abstract Entity class!"); @@ -274,6 +287,7 @@ namespace RTE { m_AllocatedPool.pop_back(); RTEAssert(foundMemory, "Could not find an available instance in the pool, even after increasing its size!"); +#endif // Keep track of the number of instances passed out m_InstancesInUse++; @@ -285,8 +299,14 @@ namespace RTE { if (!returnedMemory) { return 0; } + +#ifdef __SANITIZE_ADDRESS__ + // If compiled with ASan, sidestep pooling and just use the allocator normally. + m_Deallocate(returnedMemory); +#else std::lock_guard guard(m_Mutex); m_AllocatedPool.push_back(returnedMemory); +#endif // Keep track of the number of instances passed in m_InstancesInUse--; diff --git a/Source/System/Entity.h b/Source/System/Entity.h index 8caa3ccae8..0bd377360b 100644 --- a/Source/System/Entity.h +++ b/Source/System/Entity.h @@ -7,6 +7,14 @@ #include #include +// Concoction based on: +// https://stackoverflow.com/questions/34813412/how-to-detect-if-building-with-address-sanitizer-when-building-with-gcc-4-8#78444624 +#if defined(__has_feature) // MSVC doesn't have this +# if __has_feature(address_sanitizer) // for Clang +# define __SANITIZE_ADDRESS__ // GCC and MSVC already set this +# endif +#endif + namespace RTE { typedef std::function MemoryAllocate; //!< Convenient name definition for the memory allocation callback function. @@ -173,7 +181,9 @@ namespace RTE { int m_PoolAllocBlockCount; //!< The number of instances to fill up the pool of this type with each time it runs dry. int m_InstancesInUse; //!< The number of allocated instances passed out from the pool. +#ifndef __ADDRESS_SANITIZER__ // Unused when ASan is enabled. std::mutex m_Mutex; //!< Mutex to ensure multiple things aren't grabbing/deallocating memory at the same time +#endif // Forbidding copying ClassInfo(const ClassInfo& reference) = delete;