I was rewriting some code to use llvm::BumpPtrAllocator and std::vector, and once I’d finished MSAN began reporting a memory leak. I tried using llvm::SmallVector, and the leak went away. I’m not very good at C++, so I was hoping I could get help understanding why.
Originally, I had classes like this:
struct MyObject {};
struct MyContainer {
std::vector<std::unique_ptr<MyObject>> objects;
MyContainer(std::vector<std::unique_ptr<MyObject>> &objects)
: objects(std::move(objects)) {}
};
This worked great, no leaks – but my header files became unwieldy, because the std::unique_ptr above can’t be used with a forward declaration of MyObject.
So to avoid this problem, I tried allocating my objects using llvm::BumpPtrAllocator. I wrote a simple test case for this:
struct MyVector {
std::vector<MyObject *> objects;
MyVector(std::vector<MyObject *> &objects)
: objects(std::move(objects)) {}
};
TEST(AllocatorTest, Vector) {
llvm::BumpPtrAllocator allocator;
MyObject *myObject = new (allocator.Allocate<MyObject>()) MyObject();
std::vector<MyObject *> objects;
objects.push_back(std::move(myObject));
MyVector *vector = new (allocator.Allocate<MyVector>()) MyVector(objects);
// Destructor must manually be called here, otherwise LeakSanitizer reports:
//
// Direct leak of 8 byte(s) in 1 object(s) allocated from:
// #0 0x4f6488 in operator new(unsigned long) llvm-project/compiler-rt/lib/asan/asan_new_delete.cc:99:3
// #1 0x5006a3 in void std::vector<MyObject*, std::allocator<MyObject*> >::_M_realloc_insert<MyObject*>(__gnu_cxx::__normal_iterator<MyObject**, std::vector<MyObject*, std::allocator<MyObject*> > >, MyObject*&&) /usr/lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/vector.tcc:427:33
// #2 0x4f96e6 in AllocatorTest_Vector_Test::TestBody() examples/unittests/llvm/Support/AllocatorTest.cpp:45:11
vector->~MyVector();
}
I understand that one of the downsides of using llvm::BumpPtrAllocator is that destructors aren’t called when the allocator’s slabs are deallocated. And I guess I understand how std::vector::push_back would allocate 8 bytes of memory to store a pointer, MyObject *. However, I don’t quite understand why LeakSanitizer is perfectly happy with this:
struct MySmallVector {
llvm::SmallVector<MyObject *, 2> objects;
MySmallVector(llvm::SmallVectorImpl<MyObject *> &objects)
: objects(std::move(objects)) {}
};
TEST(AllocatorTest, SmallVector) {
llvm::BumpPtrAllocator allocator;
MyObject *object = new (allocator.Allocate<MyObject>()) MyObject();
llvm::SmallVector<MyObject *, 2> objects;
objects.push_back(object);
MySmallVector *smallVector =
new (allocator.Allocate<MySmallVector>()) MySmallVector(objects);
#pragma unused(smallVector)
}
My plan is to keep reading through the llvm::SmallVector internals to understand why this doesn’t result in the same memory leak of 8 bytes to store MyObject *, but so far from what I can see, the llvm::SmallVector destructor would have to be called for it to free the slabs that store those 8 bytes, and I don’t see how that would be called if MySmallVector’s default destructor isn’t called.
Does anyone know why my last example with llvm::SmallVector doesn’t leak memory, but my second-to-last example with std::vector does? (Apologies in advance if this is just a simple C++ question that doesn’t have much to do with LLVM.)