Files
filament/libs/utils/test/test_Allocators.cpp
Mathias Agopian 53ff133b03 fix a possible memory corruption in debug builds
TrackingPolicy::Debug didn't store the base pointer of the Area, and
instead relied on the first allocation to discover it, however, because
of alignment, the first allocation may not match the base pointer.
Because of that there could be an overflow in onRewind(), i.e. we
could rewind to a pointer before the (wrongly computed) base. This
overflow caused the debug memset to go awry and stomped on memory.

This is fixed by passing the base pointer to the constructor of the
TrackingPolicy. This base pointer could be nullptr with certain
allocators, but in that case, onReset/onRewind should never be called;
and this is enforced at compile time.

Also fixed a (luckily) harmless buffer overflow when preparing the
dynamic lights, if the number of lights wasn't a multiple of 4. This
was harmless because we use a linear allocator, so overflows are not 
really overflows.
2019-11-07 10:03:41 -08:00

333 lines
9.3 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <bitset>
#include <functional>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <utils/Allocator.h>
using namespace utils;
TEST(AllocatorTest, LinearAllocator) {
char scratch[1024];
void* p = nullptr;
void* q = nullptr;
LinearAllocator la(scratch, scratch+sizeof(scratch));
p = la.alloc(1024, 1, 0);
// check we can allocate the whole block
EXPECT_EQ(scratch, p);
// check we can free everything and reallocate the whole block
la.reset();
p = la.alloc(1024, 1, 0);
EXPECT_EQ(scratch, p);
// check we can rewind
la.rewind(scratch + 512);
p = la.alloc(512, 1, 0);
EXPECT_EQ(scratch + 512, p);
// check we can't allocate more than the area size
la.reset();
p = la.alloc(1025, 1, 0);
EXPECT_EQ(nullptr, p);
// check that after failure, we can allocate to the area size
p = la.alloc(1024, 1, 0);
EXPECT_EQ(scratch, p);
// check small allocations
la.reset();
p = la.alloc(1, 1, 0);
EXPECT_EQ(scratch, p);
p = la.alloc(7, 1, 0);
EXPECT_EQ(scratch+1, p);
p = la.alloc(8, 1, 0);
EXPECT_EQ(scratch+8, p);
// check alignment
p = la.alloc(1, 1, 0);
p = la.alloc(24, 32, 0);
EXPECT_NE(nullptr, p);
EXPECT_EQ(0, uintptr_t(p) & 31);
// now check that next allocation doesn't overlap previous one
q = la.alloc(1, 1, 0);
EXPECT_EQ(uintptr_t(q), uintptr_t(p) + 24);
// check alignment + offset
p = la.alloc(3, 1, 0);
p = la.alloc(sizeof(float)*4, 32, 4);
EXPECT_EQ(0, uintptr_t(p) & 31);
// now check that next allocation doesn't overlap previous one
q = la.alloc(1, 1, 0);
EXPECT_EQ(uintptr_t(q), uintptr_t(p) + sizeof(float)*4);
}
TEST(AllocatorTest, PoolAllocator) {
char scratch[1024 + 31];
void* p = nullptr;
void* q = nullptr;
std::bitset<16> used;
// verify buffers have not been clobbered
auto check = [](char const* p, int v, size_t s)->bool {
for (size_t i = 0; i<s ; ++i) {
if (p[i] != v) {
return false;
}
}
return true;
};
// pool of 64-bytes objects aligned on 32 bytes
PoolAllocator<64, 32> pa(scratch, scratch+sizeof(scratch));
void* const b = pointermath::align(scratch, 32, 0);
// repeat the test multiple times
for (size_t k=0 ; k<16 ; k++) {
// make sure we can allocate exactly 16 of those objects
for (size_t i = 0; i < 16; i++) {
p = pa.alloc();
EXPECT_NE(nullptr, p);
EXPECT_EQ(0, uintptr_t(p) & 31);
size_t j = (uintptr_t(p) - uintptr_t(b)) / 64;
//printf("%3d", j);
memset(p, int(j + 1), 64);
}
//printf("\n");
// check an extra one fails
q = pa.alloc();
EXPECT_EQ(nullptr, q);
// check that buffers where not clobbered
q = b;
for (size_t i = 0; i < 16; i++) {
EXPECT_TRUE(check((char const*)q, int(i + 1), 64));
q = pointermath::add(q, 64);
}
// now free all our buffers
used.set();
q = b;
for (size_t i = 0; i < 16; i++) {
// use gray-coding so we don't free exactly linearly
size_t j = ((i^k) >> 1) ^ (i^k);
p = pointermath::add(q, j * 64);
pa.free(p);
used[j] = false;
if (j > 0 && used[j - 1]) {
// check that the previous buffer didn't get clobbered
EXPECT_TRUE(check((char const*) pointermath::add(p, -64), int(j - 1 + 1), 64));
}
if (j < 15 && used[j + 1]) {
// check that the following buffer didn't get clobbered
EXPECT_TRUE(check((char const*) pointermath::add(p, +64), int(j + 1 + 1), 64));
}
}
EXPECT_FALSE(used.any());
}
}
TEST(AllocatorTest, CppAllocator) {
struct Tracking {
Tracking() noexcept { }
Tracking(const char* name, void const* base, size_t size) noexcept { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) {
allocations.push_back(p);
}
void onFree(void* p) {
auto pos = std::find(allocations.begin(), allocations.end(), p);
EXPECT_TRUE(pos != allocations.end());
}
std::vector<void*> allocations;
};
using CppArena = Arena<PoolAllocator<8, 8, sizeof(void*)>, LockingPolicy::NoLock, Tracking>;
static int count = 0;
count = 0;
struct Foo {
~Foo() {
++count;
}
struct Tag {
CppArena* arena;
};
static void* operator new(size_t size, CppArena& arena) {
void* p = arena.alloc(size, alignof(Foo), sizeof(Tag));
Tag* tag = static_cast<Tag*>(p) - 1;
tag->arena = &arena;
return p;
}
static void operator delete(void* p, size_t s) {
// don't do anything
Tag* tag = static_cast<Tag*>(p) - 1;
tag->arena->free(p);
}
char dummy[8];
};
CppArena arena("CppArena", 1024);
// check we can override operator new and use one of our allocator
Foo* p0 = new(arena) Foo;
EXPECT_NE(nullptr, p0);
Foo* p1 = new(arena) Foo;
EXPECT_NE(nullptr, p1);
EXPECT_EQ(0, count);
delete p0;
EXPECT_EQ(1, count);
delete p1;
EXPECT_EQ(2, count);
}
TEST(AllocatorTest, ScopedStackArena) {
void* p = nullptr;
struct Foo {
explicit Foo(std::function<void(void)> f) : dtor(std::move(f)) { }
~Foo() { dtor(); }
private:
std::function<void(void)> dtor;
};
struct Pod {
int a;
float b;
};
struct PodWithDtor {
int a;
float b;
~PodWithDtor() { };
};
int dtorCalled = 0;
using Allocator = Arena<LinearAllocator, LockingPolicy::NoLock>;
Allocator allocator("ArenaScope", 1024);
{
ArenaScope<Allocator> ssa(allocator);
Foo* f0 = ssa.make<Foo>([&dtorCalled](){ dtorCalled++; });
EXPECT_NE(nullptr, f0);
Foo* f1 = ssa.make<Foo>([&dtorCalled](){ dtorCalled++; });
EXPECT_NE(nullptr, f1);
Foo* f2 = ssa.make<Foo>([&dtorCalled](){ dtorCalled++; });
EXPECT_NE(nullptr, f2);
EXPECT_EQ(0, dtorCalled);
}
allocator.getAllocator().reset();
// check dtors have been called
EXPECT_EQ(3, dtorCalled);
{
ArenaScope<Allocator> ssa(allocator);
// check that we can allocate everything at this point
p = ssa.allocate(1024);
EXPECT_NE(nullptr, p);
}
allocator.getAllocator().reset();
{
ArenaScope<Allocator> ssa(allocator);
// check that we fail allocating too much
p = ssa.allocate(1025);
EXPECT_EQ(nullptr, p);
}
allocator.getAllocator().reset();
{
ArenaScope<Allocator> ssa(allocator);
Pod* p0 = ssa.make<Pod>();
Pod* p1 = ssa.make<Pod>();
EXPECT_EQ(sizeof(Pod), uintptr_t(p1) - uintptr_t(p0));
PodWithDtor* pd0 = ssa.make<PodWithDtor>();
PodWithDtor* pd1 = ssa.make<PodWithDtor>();
EXPECT_NE(sizeof(PodWithDtor), uintptr_t(pd1) - uintptr_t(pd0));
}
allocator.getAllocator().reset();
}
TEST(AllocatorTest, STLAllocator) {
struct Tracking {
Tracking() noexcept { }
Tracking(const char* name, void const* base, size_t size) noexcept { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) {
allocations.push_back(p);
}
void onFree(void* p, size_t) {
auto pos = std::find(allocations.begin(), allocations.end(), p);
EXPECT_TRUE(pos != allocations.end());
allocations.erase(pos);
}
std::vector<void*> allocations;
};
using Arena = Arena<LinearAllocator, LockingPolicy::NoLock, Tracking>;
Arena arena("arena", 1204);
Arena arena2("arena2", 1204);
STLAllocator<int, Arena> allocator(arena);
STLAllocator<int, Arena> allocator2(arena2);
EXPECT_TRUE(allocator != allocator2);
EXPECT_TRUE(allocator == allocator);
STLAllocator<int, Arena>::rebind<char>::other charAllocator(arena);
EXPECT_TRUE(allocator == charAllocator);
STLAllocator<int, Arena> allocatorCopy(allocator);
EXPECT_TRUE(allocator == allocatorCopy);
STLAllocator<int, Arena> allocatorFromCharCopy(charAllocator);
EXPECT_TRUE(allocatorFromCharCopy == charAllocator);
{
std::vector<int, STLAllocator<int, Arena>> vector(allocator);
vector.push_back(1);
EXPECT_GT(arena.getListener().allocations.size(), 0);
vector.push_back(2);
vector.push_back(3);
vector.push_back(4);
vector.clear();
}
EXPECT_EQ(0, arena.getListener().allocations.size());
}