You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
12 KiB
386 lines
12 KiB
/*
|
|
* Copyright (C) 2018 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 "utils/base/arena.h"
|
|
|
|
#include "utils/base/logging.h"
|
|
#include "utils/base/macros.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace libtextclassifier3 {
|
|
|
|
//------------------------------------------------------------------------
|
|
// Write random data to allocated memory
|
|
static void TestMemory(void* mem, int size) {
|
|
// Do some memory allocation to check that the arena doesn't mess up
|
|
// the internal memory allocator
|
|
char* tmp[100];
|
|
for (int i = 0; i < TC3_ARRAYSIZE(tmp); i++) {
|
|
tmp[i] = new char[i * i + 1];
|
|
}
|
|
|
|
memset(mem, 0xcc, size);
|
|
|
|
// Free up the allocated memory;
|
|
for (char* s : tmp) {
|
|
delete[] s;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Check memory ptr
|
|
static void CheckMemory(void* mem, int size) {
|
|
TC3_CHECK(mem != nullptr);
|
|
TestMemory(mem, size);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Check memory ptr and alignment
|
|
static void CheckAlignment(void* mem, int size, int alignment) {
|
|
TC3_CHECK(mem != nullptr);
|
|
ASSERT_EQ(0, (reinterpret_cast<uintptr_t>(mem) & (alignment - 1)))
|
|
<< "mem=" << mem << " alignment=" << alignment;
|
|
TestMemory(mem, size);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
template <class A>
|
|
void TestArena(const char* name, A* a, int blksize) {
|
|
TC3_VLOG(INFO) << "Testing arena '" << name << "': blksize = " << blksize
|
|
<< ": actual blksize = " << a->block_size();
|
|
|
|
int s;
|
|
blksize = a->block_size();
|
|
|
|
// Allocate zero bytes
|
|
TC3_CHECK(a->is_empty());
|
|
a->Alloc(0);
|
|
TC3_CHECK(a->is_empty());
|
|
|
|
// Allocate same as blksize
|
|
CheckMemory(a->Alloc(blksize), blksize);
|
|
TC3_CHECK(!a->is_empty());
|
|
|
|
// Allocate some chunks adding up to blksize
|
|
s = blksize / 4;
|
|
CheckMemory(a->Alloc(s), s);
|
|
CheckMemory(a->Alloc(s), s);
|
|
CheckMemory(a->Alloc(s), s);
|
|
|
|
int s2 = blksize - (s * 3);
|
|
CheckMemory(a->Alloc(s2), s2);
|
|
|
|
// Allocate large chunk
|
|
CheckMemory(a->Alloc(blksize * 2), blksize * 2);
|
|
CheckMemory(a->Alloc(blksize * 2 + 1), blksize * 2 + 1);
|
|
CheckMemory(a->Alloc(blksize * 2 + 2), blksize * 2 + 2);
|
|
CheckMemory(a->Alloc(blksize * 2 + 3), blksize * 2 + 3);
|
|
|
|
// Allocate aligned
|
|
s = blksize / 2;
|
|
CheckAlignment(a->AllocAligned(s, 1), s, 1);
|
|
CheckAlignment(a->AllocAligned(s + 1, 2), s + 1, 2);
|
|
CheckAlignment(a->AllocAligned(s + 2, 2), s + 2, 2);
|
|
CheckAlignment(a->AllocAligned(s + 3, 4), s + 3, 4);
|
|
CheckAlignment(a->AllocAligned(s + 4, 4), s + 4, 4);
|
|
CheckAlignment(a->AllocAligned(s + 5, 4), s + 5, 4);
|
|
CheckAlignment(a->AllocAligned(s + 6, 4), s + 6, 4);
|
|
|
|
// Free
|
|
for (int i = 0; i < 100; i++) {
|
|
int i2 = i * i;
|
|
a->Free(a->Alloc(i2), i2);
|
|
}
|
|
|
|
// Memdup
|
|
char mem[500];
|
|
for (int i = 0; i < 500; i++) mem[i] = i & 255;
|
|
char* mem2 = a->Memdup(mem, sizeof(mem));
|
|
TC3_CHECK_EQ(0, memcmp(mem, mem2, sizeof(mem)));
|
|
|
|
// MemdupPlusNUL
|
|
const char* msg_mpn = "won't use all this length";
|
|
char* msg2_mpn = a->MemdupPlusNUL(msg_mpn, 10);
|
|
TC3_CHECK_EQ(0, strcmp(msg2_mpn, "won't use "));
|
|
a->Free(msg2_mpn, 11);
|
|
|
|
// Strdup
|
|
const char* msg = "arena unit test is cool...";
|
|
char* msg2 = a->Strdup(msg);
|
|
TC3_CHECK_EQ(0, strcmp(msg, msg2));
|
|
a->Free(msg2, strlen(msg) + 1);
|
|
|
|
// Strndup
|
|
char* msg3 = a->Strndup(msg, 10);
|
|
TC3_CHECK_EQ(0, strncmp(msg3, msg, 10));
|
|
a->Free(msg3, 10);
|
|
TC3_CHECK(!a->is_empty());
|
|
|
|
// Reset
|
|
a->Reset();
|
|
TC3_CHECK(a->is_empty());
|
|
|
|
// Realloc
|
|
char* m1 = a->Alloc(blksize / 2);
|
|
CheckMemory(m1, blksize / 2);
|
|
TC3_CHECK(!a->is_empty());
|
|
CheckMemory(a->Alloc(blksize / 2), blksize / 2); // Allocate another block
|
|
m1 = a->Realloc(m1, blksize / 2, blksize);
|
|
CheckMemory(m1, blksize);
|
|
m1 = a->Realloc(m1, blksize, 23456);
|
|
CheckMemory(m1, 23456);
|
|
|
|
// Shrink
|
|
m1 = a->Shrink(m1, 200);
|
|
CheckMemory(m1, 200);
|
|
m1 = a->Shrink(m1, 100);
|
|
CheckMemory(m1, 100);
|
|
m1 = a->Shrink(m1, 1);
|
|
CheckMemory(m1, 1);
|
|
a->Free(m1, 1);
|
|
TC3_CHECK(!a->is_empty());
|
|
|
|
// Calloc
|
|
char* m2 = a->Calloc(2000);
|
|
for (int i = 0; i < 2000; ++i) {
|
|
TC3_CHECK_EQ(0, m2[i]);
|
|
}
|
|
|
|
// bytes_until_next_allocation
|
|
a->Reset();
|
|
TC3_CHECK(a->is_empty());
|
|
int alignment = blksize - a->bytes_until_next_allocation();
|
|
TC3_VLOG(INFO) << "Alignment overhead in initial block = " << alignment;
|
|
|
|
s = a->bytes_until_next_allocation() - 1;
|
|
CheckMemory(a->Alloc(s), s);
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), 1);
|
|
CheckMemory(a->Alloc(1), 1);
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), 0);
|
|
|
|
CheckMemory(a->Alloc(2 * blksize), 2 * blksize);
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), 0);
|
|
|
|
CheckMemory(a->Alloc(1), 1);
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), blksize - 1);
|
|
|
|
s = blksize / 2;
|
|
char* m0 = a->Alloc(s);
|
|
CheckMemory(m0, s);
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), blksize - s - 1);
|
|
m0 = a->Shrink(m0, 1);
|
|
CheckMemory(m0, 1);
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), blksize - 2);
|
|
|
|
a->Reset();
|
|
TC3_CHECK(a->is_empty());
|
|
TC3_CHECK_EQ(a->bytes_until_next_allocation(), blksize - alignment);
|
|
}
|
|
|
|
static void EnsureNoAddressInRangeIsPoisoned(void* buffer, size_t range_size) {
|
|
#ifdef ADDRESS_SANITIZER
|
|
TC3_CHECK_EQ(nullptr, __asan_region_is_poisoned(buffer, range_size));
|
|
#endif
|
|
}
|
|
|
|
static void DoTest(const char* label, int blksize, char* buffer) {
|
|
{
|
|
UnsafeArena ua(buffer, blksize);
|
|
TestArena((std::string("UnsafeArena") + label).c_str(), &ua, blksize);
|
|
}
|
|
EnsureNoAddressInRangeIsPoisoned(buffer, blksize);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
class BasicTest : public ::testing::TestWithParam<int> {};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(AllSizes, BasicTest,
|
|
::testing::Values(BaseArena::kDefaultAlignment + 1, 10,
|
|
100, 1024, 12345, 123450, 131072,
|
|
1234500));
|
|
|
|
TEST_P(BasicTest, DoTest) {
|
|
const int blksize = GetParam();
|
|
|
|
// Allocate some memory from heap first
|
|
char* tmp[100];
|
|
for (int i = 0; i < TC3_ARRAYSIZE(tmp); i++) {
|
|
tmp[i] = new char[i * i];
|
|
}
|
|
|
|
// Initial buffer for testing pre-allocated arenas
|
|
char* buffer = new char[blksize + BaseArena::kDefaultAlignment];
|
|
|
|
DoTest("", blksize, nullptr);
|
|
DoTest("(p0)", blksize, buffer + 0);
|
|
DoTest("(p1)", blksize, buffer + 1);
|
|
DoTest("(p2)", blksize, buffer + 2);
|
|
DoTest("(p3)", blksize, buffer + 3);
|
|
DoTest("(p4)", blksize, buffer + 4);
|
|
DoTest("(p5)", blksize, buffer + 5);
|
|
|
|
// Free up the allocated heap memory
|
|
for (char* s : tmp) {
|
|
delete[] s;
|
|
}
|
|
|
|
delete[] buffer;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// NOTE: these stats will only be accurate in non-debug mode (otherwise
|
|
// they'll all be 0). So: if you want accurate timing, run in "normal"
|
|
// or "opt" mode. If you want accurate stats, run in "debug" mode.
|
|
void ShowStatus(const char* const header, const BaseArena::Status& status) {
|
|
printf("\n--- status: %s\n", header);
|
|
printf(" %zu bytes allocated\n", status.bytes_allocated());
|
|
}
|
|
|
|
// This just tests the arena code proper, without use of allocators of
|
|
// gladiators or STL or anything like that
|
|
void TestArena2(UnsafeArena* const arena) {
|
|
const char sshort[] = "This is a short string";
|
|
char slong[3000];
|
|
memset(slong, 'a', sizeof(slong));
|
|
slong[sizeof(slong) - 1] = '\0';
|
|
|
|
char* s1 = arena->Strdup(sshort);
|
|
char* s2 = arena->Strdup(slong);
|
|
char* s3 = arena->Strndup(sshort, 100);
|
|
char* s4 = arena->Strndup(slong, 100);
|
|
char* s5 = arena->Memdup(sshort, 10);
|
|
char* s6 = arena->Realloc(s5, 10, 20);
|
|
arena->Shrink(s5, 10); // get s5 back to using 10 bytes again
|
|
char* s7 = arena->Memdup(slong, 10);
|
|
char* s8 = arena->Realloc(s7, 10, 5);
|
|
char* s9 = arena->Strdup(s1);
|
|
char* s10 = arena->Realloc(s4, 100, 10);
|
|
char* s11 = arena->Realloc(s4, 10, 100);
|
|
char* s12 = arena->Strdup(s9);
|
|
char* s13 = arena->Realloc(s9, sizeof(sshort) - 1, 100000); // won't fit :-)
|
|
|
|
TC3_CHECK_EQ(0, strcmp(s1, sshort));
|
|
TC3_CHECK_EQ(0, strcmp(s2, slong));
|
|
TC3_CHECK_EQ(0, strcmp(s3, sshort));
|
|
// s4 was realloced so it is not safe to read from
|
|
TC3_CHECK_EQ(0, strncmp(s5, sshort, 10));
|
|
TC3_CHECK_EQ(0, strncmp(s6, sshort, 10));
|
|
TC3_CHECK_EQ(s5, s6); // Should have room to grow here
|
|
// only the first 5 bytes of s7 should match; the realloc should have
|
|
// caused the next byte to actually go to s9
|
|
TC3_CHECK_EQ(0, strncmp(s7, slong, 5));
|
|
TC3_CHECK_EQ(s7, s8); // Realloc-smaller should cause us to realloc in place
|
|
// s9 was realloced so it is not safe to read from
|
|
TC3_CHECK_EQ(s10, s4); // Realloc-smaller should cause us to realloc in place
|
|
// Even though we're back to prev size, we had to move the pointer. Thus
|
|
// only the first 10 bytes are known since we grew from 10 to 100
|
|
TC3_CHECK_NE(s11, s4);
|
|
TC3_CHECK_EQ(0, strncmp(s11, slong, 10));
|
|
TC3_CHECK_EQ(0, strcmp(s12, s1));
|
|
TC3_CHECK_NE(s12, s13); // too big to grow-in-place, so we should move
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Test some fundamental STL containers
|
|
|
|
template <typename T>
|
|
struct test_hash {
|
|
int operator()(const T&) const { return 0; }
|
|
inline bool operator()(const T& s1, const T& s2) const { return s1 < s2; }
|
|
};
|
|
template <>
|
|
struct test_hash<const char*> {
|
|
int operator()(const char*) const { return 0; }
|
|
|
|
inline bool operator()(const char* s1, const char* s2) const {
|
|
return (s1 != s2) &&
|
|
(s2 == nullptr || (s1 != nullptr && strcmp(s1, s2) < 0));
|
|
}
|
|
};
|
|
|
|
// temp definitions from strutil.h, until the compiler error
|
|
// generated by #including that file is fixed.
|
|
struct streq {
|
|
bool operator()(const char* s1, const char* s2) const {
|
|
return ((s1 == nullptr && s2 == nullptr) ||
|
|
(s1 && s2 && *s1 == *s2 && strcmp(s1, s2) == 0));
|
|
}
|
|
};
|
|
struct strlt {
|
|
bool operator()(const char* s1, const char* s2) const {
|
|
return (s1 != s2) &&
|
|
(s2 == nullptr || (s1 != nullptr && strcmp(s1, s2) < 0));
|
|
}
|
|
};
|
|
|
|
void DoPoisonTest(BaseArena* b, size_t size) {
|
|
#ifdef ADDRESS_SANITIZER
|
|
TC3_LOG(INFO) << "DoPoisonTest(" << b << ", " << size << ")";
|
|
char* c1 = b->SlowAlloc(size);
|
|
char* c2 = b->SlowAlloc(size);
|
|
TC3_CHECK_EQ(nullptr, __asan_region_is_poisoned(c1, size));
|
|
TC3_CHECK_EQ(nullptr, __asan_region_is_poisoned(c2, size));
|
|
char* c3 = b->SlowRealloc(c2, size, size / 2);
|
|
TC3_CHECK_EQ(nullptr, __asan_region_is_poisoned(c3, size / 2));
|
|
TC3_CHECK_NE(nullptr, __asan_region_is_poisoned(c2, size));
|
|
b->Reset();
|
|
TC3_CHECK_NE(nullptr, __asan_region_is_poisoned(c1, size));
|
|
TC3_CHECK_NE(nullptr, __asan_region_is_poisoned(c2, size));
|
|
TC3_CHECK_NE(nullptr, __asan_region_is_poisoned(c3, size / 2));
|
|
#endif
|
|
}
|
|
|
|
TEST(ArenaTest, TestPoison) {
|
|
{
|
|
UnsafeArena arena(512);
|
|
DoPoisonTest(&arena, 128);
|
|
DoPoisonTest(&arena, 256);
|
|
DoPoisonTest(&arena, 512);
|
|
DoPoisonTest(&arena, 1024);
|
|
}
|
|
|
|
char* buffer = new char[512];
|
|
{
|
|
UnsafeArena arena(buffer, 512);
|
|
DoPoisonTest(&arena, 128);
|
|
DoPoisonTest(&arena, 256);
|
|
DoPoisonTest(&arena, 512);
|
|
DoPoisonTest(&arena, 1024);
|
|
}
|
|
EnsureNoAddressInRangeIsPoisoned(buffer, 512);
|
|
|
|
delete[] buffer;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
template <class A>
|
|
void TestStrndupUnterminated() {
|
|
const char kFoo[3] = {'f', 'o', 'o'};
|
|
char* source = new char[3];
|
|
memcpy(source, kFoo, sizeof(kFoo));
|
|
A arena(4096);
|
|
char* dup = arena.Strndup(source, sizeof(kFoo));
|
|
TC3_CHECK_EQ(0, memcmp(dup, kFoo, sizeof(kFoo)));
|
|
delete[] source;
|
|
}
|
|
|
|
TEST(ArenaTest, StrndupWithUnterminatedStringUnsafe) {
|
|
TestStrndupUnterminated<UnsafeArena>();
|
|
}
|
|
|
|
} // namespace libtextclassifier3
|