Summary
PoolAllocator::Free() and PoolAllocator::Clear() reinterpret raw memory as a PoolFreeNode via a C-style cast and write through the resulting pointer without constructing the object via placement new. This is undefined behaviour under the C++ object model.
Location
ZEngine/ZEngine/Core/Memory/Allocator.cpp:268 — Free():
PoolFreeNode* node = (PoolFreeNode*) ptr;
node->Next = head;
ZEngine/ZEngine/Core/Memory/Allocator.cpp:282 — Clear():
PoolFreeNode* node = (PoolFreeNode*) ptr;
node->Next = head;
Detail
Writing through a pointer cast from raw memory where no object of that type has been constructed is UB per [basic.life] — the object's lifetime has not begun. The compiler is permitted to assume no aliasing and eliminate or reorder such writes.
In practice every compiler generates correct code for trivially-copyable PoolFreeNode on x86-64 / ARM64. But the UB is latent and may be exposed by aggressive LTO, sanitizers (UBSAN will flag it), or future compiler versions.
Fix
Use placement new to start the object's lifetime before writing:
// Free():
auto* node = ::new (ptr) PoolFreeNode{head};
head = node;
// Clear():
auto* node = ::new (&memory[i * chunk_size]) PoolFreeNode{head};
head = node;
PoolFreeNode is trivially destructible so no matching destroy call is needed.
Impact
Low practical risk on current toolchain; UBSAN will flag it. Alignment: the chunk is guaranteed >= sizeof(PoolFreeNode) and aligned to the pool's alignment — so only the lifetime issue remains.
Summary
PoolAllocator::Free()andPoolAllocator::Clear()reinterpret raw memory as aPoolFreeNodevia a C-style cast and write through the resulting pointer without constructing the object via placement new. This is undefined behaviour under the C++ object model.Location
ZEngine/ZEngine/Core/Memory/Allocator.cpp:268—Free():ZEngine/ZEngine/Core/Memory/Allocator.cpp:282—Clear():Detail
Writing through a pointer cast from raw memory where no object of that type has been constructed is UB per [basic.life] — the object's lifetime has not begun. The compiler is permitted to assume no aliasing and eliminate or reorder such writes.
In practice every compiler generates correct code for trivially-copyable
PoolFreeNodeon x86-64 / ARM64. But the UB is latent and may be exposed by aggressive LTO, sanitizers (UBSAN will flag it), or future compiler versions.Fix
Use placement new to start the object's lifetime before writing:
PoolFreeNodeis trivially destructible so no matching destroy call is needed.Impact
Low practical risk on current toolchain; UBSAN will flag it. Alignment: the chunk is guaranteed
>= sizeof(PoolFreeNode)and aligned to the pool's alignment — so only the lifetime issue remains.