[scudo][standalone] Make tests work on Fuchsia

Summary:
This CL makes unit tests compatible with Fuchsia's zxtest. This
required a few changes here and there, but also unearthed some
incompatibilities that had to be addressed.

A header is introduced to allow to account for the zxtest/gtest
differences, some `#if SCUDO_FUCHSIA` are used to disable incompatible
code (the 32-bit primary, or the exclusive TSD).

It also brought to my attention that I was using
`__scudo_default_options` in different tests, which ended up in a
single binary, and I am not sure how that ever worked. So move
this to the main cpp.

Additionally fully disable the secondary freelist on Fuchsia as we do
not track VMOs for secondary allocations, so no release possible.

With some modifications to Scudo's BUILD.gn in Fuchsia:
```
[==========] 79 tests from 23 test cases ran (10280 ms total).
[  PASSED  ] 79 tests
```

Reviewers: mcgrathr, phosek, hctim, pcc, eugenis, cferris

Subscribers: srhines, jfb, #sanitizers, llvm-commits

Tags: #sanitizers, #llvm

Differential Revision: https://reviews.llvm.org/D70682
This commit is contained in:
Kostya Kortchinsky 2019-11-25 10:28:57 -08:00
parent bcd0798c47
commit 0d3d4d3b0f
25 changed files with 159 additions and 92 deletions

View File

@ -67,7 +67,7 @@ struct AndroidSvelteConfig {
struct FuchsiaConfig {
// 1GB Regions
typedef SizeClassAllocator64<DefaultSizeClassMap, 30U> Primary;
typedef MapAllocator<> Secondary;
typedef MapAllocator<0U> Secondary;
template <class A>
using TSDRegistryT = TSDRegistrySharedT<A, 8U>; // Shared, max 8 TSDs.
};

View File

@ -50,6 +50,10 @@ static Header *getHeader(const void *Ptr) {
template <uptr MaxFreeListSize = 32U> class MapAllocator {
public:
// Ensure the freelist is disabled on Fuchsia, since it doesn't support
// releasing Secondary blocks yet.
COMPILER_CHECK(!SCUDO_FUCHSIA || MaxFreeListSize == 0U);
void initLinkerInitialized(GlobalStats *S) {
Stats.initLinkerInitialized();
if (LIKELY(S))
@ -205,10 +209,11 @@ void *MapAllocator<MaxFreeListSize>::allocate(uptr Size, uptr AlignmentHint,
template <uptr MaxFreeListSize>
void MapAllocator<MaxFreeListSize>::deallocate(void *Ptr) {
LargeBlock::Header *H = LargeBlock::getHeader(Ptr);
const uptr Block = reinterpret_cast<uptr>(H);
{
ScopedLock L(Mutex);
InUseBlocks.remove(H);
const uptr CommitSize = H->BlockEnd - reinterpret_cast<uptr>(H);
const uptr CommitSize = H->BlockEnd - Block;
FreedBytes += CommitSize;
NumberOfFrees++;
Stats.sub(StatAllocated, CommitSize);
@ -225,11 +230,10 @@ void MapAllocator<MaxFreeListSize>::deallocate(void *Ptr) {
if (!Inserted)
FreeBlocks.push_back(H);
const uptr RoundedAllocationStart =
roundUpTo(reinterpret_cast<uptr>(H) + LargeBlock::getHeaderSize(),
getPageSizeCached());
roundUpTo(Block + LargeBlock::getHeaderSize(), getPageSizeCached());
MapPlatformData Data = H->Data;
// TODO(kostyak): use release_to_os_interval_ms
releasePagesToOS(H->MapBase, RoundedAllocationStart - H->MapBase,
releasePagesToOS(Block, RoundedAllocationStart - Block,
H->BlockEnd - RoundedAllocationStart, &Data);
return;
}

View File

@ -6,8 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "scudo/standalone/atomic_helpers.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include "atomic_helpers.h"
namespace scudo {

View File

@ -6,10 +6,11 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "bytemap.h"
#include "gtest/gtest.h"
#include <pthread.h>
#include <string.h>
template <typename T> void testMap(T &Map, scudo::uptr Size) {

View File

@ -6,9 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "checksum.h"
#include "tests/scudo_unit_test.h"
#include "gtest/gtest.h"
#include "checksum.h"
#include <string.h>

View File

@ -6,9 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "chunk.h"
#include "tests/scudo_unit_test.h"
#include "gtest/gtest.h"
#include "chunk.h"
#include <stdlib.h>

View File

@ -6,14 +6,15 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "allocator_config.h"
#include "combined.h"
#include "gtest/gtest.h"
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
static std::mutex Mutex;
static std::condition_variable Cv;
@ -21,17 +22,6 @@ static bool Ready = false;
static constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc;
// This allows us to turn on the Quarantine for specific tests. The Quarantine
// parameters are on the low end, to avoid having to loop excessively in some
// tests.
static bool UseQuarantine = false;
extern "C" const char *__scudo_default_options() {
if (!UseQuarantine)
return "";
return "quarantine_size_kb=256:thread_local_quarantine_size_kb=128:"
"quarantine_max_chunk_size=1024";
}
template <class Config> static void testAllocator() {
using AllocatorT = scudo::Allocator<Config>;
auto Deleter = [](AllocatorT *A) {
@ -168,15 +158,15 @@ template <class Config> static void testAllocator() {
}
TEST(ScudoCombinedTest, BasicCombined) {
testAllocator<scudo::DefaultConfig>();
#if SCUDO_WORDSIZE == 64U
testAllocator<scudo::FuchsiaConfig>();
#endif
// The following configs should work on all platforms.
UseQuarantine = true;
testAllocator<scudo::AndroidConfig>();
UseQuarantine = false;
testAllocator<scudo::AndroidSvelteConfig>();
#if SCUDO_FUCHSIA
testAllocator<scudo::FuchsiaConfig>();
#else
testAllocator<scudo::DefaultConfig>();
UseQuarantine = true;
testAllocator<scudo::AndroidConfig>();
#endif
}
template <typename AllocatorT> static void stressAllocator(AllocatorT *A) {
@ -223,20 +213,21 @@ template <class Config> static void testAllocatorThreaded() {
}
TEST(ScudoCombinedTest, ThreadedCombined) {
testAllocatorThreaded<scudo::DefaultConfig>();
#if SCUDO_WORDSIZE == 64U
testAllocatorThreaded<scudo::FuchsiaConfig>();
#endif
UseQuarantine = true;
testAllocatorThreaded<scudo::AndroidConfig>();
UseQuarantine = false;
testAllocatorThreaded<scudo::AndroidSvelteConfig>();
#if SCUDO_FUCHSIA
testAllocatorThreaded<scudo::FuchsiaConfig>();
#else
testAllocatorThreaded<scudo::DefaultConfig>();
UseQuarantine = true;
testAllocatorThreaded<scudo::AndroidConfig>();
#endif
}
struct DeathConfig {
// Tiny allocator, its Primary only serves chunks of 1024 bytes.
using DeathSizeClassMap = scudo::SizeClassMap<1U, 10U, 10U, 10U, 1U, 10U>;
typedef scudo::SizeClassAllocator32<DeathSizeClassMap, 18U> Primary;
typedef scudo::SizeClassAllocator64<DeathSizeClassMap, 20U> Primary;
typedef scudo::MapAllocator<0U> Secondary;
template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U>;
};
@ -258,8 +249,8 @@ TEST(ScudoCombinedTest, DeathCombined) {
// Invalid sized deallocation.
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), "");
// Misaligned pointer.
void *MisalignedP =
// Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available.
UNUSED void *MisalignedP =
reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U);
EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), "");
EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), "");

View File

@ -6,11 +6,11 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "flags.h"
#include "flags_parser.h"
#include "gtest/gtest.h"
#include <string.h>
static const char FlagName[] = "flag_name";

View File

@ -6,8 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "scudo/standalone/list.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include "list.h"
struct ListItem {
ListItem *Next;

View File

@ -6,9 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "common.h"
#include "tests/scudo_unit_test.h"
#include "gtest/gtest.h"
#include "common.h"
#include <string.h>
#include <unistd.h>
@ -31,11 +31,10 @@ TEST(ScudoMapTest, MapNoAccessUnmap) {
TEST(ScudoMapTest, MapUnmap) {
const scudo::uptr Size = 4 * scudo::getPageSizeCached();
scudo::MapPlatformData Data = {};
void *P = scudo::map(nullptr, Size, MappingName, 0, &Data);
void *P = scudo::map(nullptr, Size, MappingName, 0, nullptr);
EXPECT_NE(P, nullptr);
memset(P, 0xaa, Size);
scudo::unmap(P, Size, 0, &Data);
scudo::unmap(P, Size, 0, nullptr);
EXPECT_DEATH(memset(P, 0xbb, Size), "");
}

View File

@ -6,10 +6,11 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "mutex.h"
#include "gtest/gtest.h"
#include <pthread.h>
#include <string.h>
class TestData {

View File

@ -6,15 +6,16 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "primary32.h"
#include "primary64.h"
#include "size_class_map.h"
#include "gtest/gtest.h"
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
// Note that with small enough regions, the SizeClassAllocator64 also works on
// 32-bit architectures. It's not something we want to encourage, but we still
@ -53,7 +54,9 @@ template <typename Primary> static void testPrimary() {
TEST(ScudoPrimaryTest, BasicPrimary) {
using SizeClassMap = scudo::DefaultSizeClassMap;
#if !SCUDO_FUCHSIA
testPrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
#endif
testPrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
}
@ -78,7 +81,7 @@ TEST(ScudoPrimaryTest, Primary64OOM) {
AllocationFailed = true;
break;
}
for (scudo::uptr J = 0; J < B->getCount(); J++)
for (scudo::u32 J = 0; J < B->getCount(); J++)
memset(B->get(J), 'B', Size);
Batches.push_back(B);
}
@ -136,7 +139,9 @@ template <typename Primary> static void testIteratePrimary() {
TEST(ScudoPrimaryTest, PrimaryIterate) {
using SizeClassMap = scudo::DefaultSizeClassMap;
#if !SCUDO_FUCHSIA
testIteratePrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
#endif
testIteratePrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
}
@ -193,7 +198,9 @@ template <typename Primary> static void testPrimaryThreaded() {
TEST(ScudoPrimaryTest, PrimaryThreaded) {
using SizeClassMap = scudo::SvelteSizeClassMap;
#if !SCUDO_FUCHSIA
testPrimaryThreaded<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
#endif
testPrimaryThreaded<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
}
@ -221,6 +228,8 @@ template <typename Primary> static void testReleaseToOS() {
TEST(ScudoPrimaryTest, ReleaseToOS) {
using SizeClassMap = scudo::DefaultSizeClassMap;
#if !SCUDO_FUCHSIA
testReleaseToOS<scudo::SizeClassAllocator32<SizeClassMap, 18U>>();
#endif
testReleaseToOS<scudo::SizeClassAllocator64<SizeClassMap, 24U>>();
}

View File

@ -6,10 +6,11 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "quarantine.h"
#include "gtest/gtest.h"
#include <pthread.h>
#include <stdlib.h>
static void *FakePtr = reinterpret_cast<void *>(0xFA83FA83);

View File

@ -6,16 +6,17 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "list.h"
#include "release.h"
#include "size_class_map.h"
#include "gtest/gtest.h"
#include <string.h>
#include <algorithm>
#include <random>
#include <set>
TEST(ScudoReleaseTest, PackedCounterArray) {
for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) {

View File

@ -6,11 +6,13 @@
//
//===----------------------------------------------------------------------===//
#include "scudo/standalone/report.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include "report.h"
TEST(ScudoReportTest, Generic) {
void *P = reinterpret_cast<void *>(0x42424242U);
// Potentially unused if EXPECT_DEATH isn't defined.
UNUSED void *P = reinterpret_cast<void *>(0x42424242U);
EXPECT_DEATH(scudo::reportError("TEST123"), "Scudo ERROR.*TEST123");
EXPECT_DEATH(scudo::reportInvalidFlag("ABC", "DEF"), "Scudo ERROR.*ABC.*DEF");
EXPECT_DEATH(scudo::reportHeaderCorruption(P), "Scudo ERROR.*42424242");

View File

@ -0,0 +1,29 @@
//===-- scudo_unit_test.h ---------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "platform.h"
#if SCUDO_FUCHSIA
#include <zxtest/zxtest.h>
#else
#include "gtest/gtest.h"
#endif
// If EXPECT_DEATH isn't defined, make it a no-op.
#ifndef EXPECT_DEATH
#define EXPECT_DEATH(X, Y) \
do { \
} while (0)
#endif
// If EXPECT_STREQ isn't defined, define our own simple one.
#ifndef EXPECT_STREQ
#define EXPECT_STREQ(X, Y) EXPECT_EQ(strcmp(X, Y), 0)
#endif
extern bool UseQuarantine;

View File

@ -6,9 +6,25 @@
//
//===----------------------------------------------------------------------===//
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
// This allows us to turn on/off a Quarantine for specific tests. The Quarantine
// parameters are on the low end, to avoid having to loop excessively in some
// tests.
bool UseQuarantine = true;
extern "C" __attribute__((visibility("default"))) const char *
__scudo_default_options() {
if (!UseQuarantine)
return "dealloc_type_mismatch=true";
return "quarantine_size_kb=256:thread_local_quarantine_size_kb=128:"
"quarantine_max_chunk_size=512:dealloc_type_mismatch=true";
}
int main(int argc, char **argv) {
#if !SCUDO_FUCHSIA
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
#else
return RUN_ALL_TESTS(argc, argv);
#endif
}

View File

@ -6,9 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "secondary.h"
#include "tests/scudo_unit_test.h"
#include "gtest/gtest.h"
#include "secondary.h"
#include <stdio.h>
@ -16,6 +16,7 @@
#include <mutex>
#include <random>
#include <thread>
#include <vector>
template <class SecondaryT> static void testSecondaryBasic(void) {
scudo::GlobalStats S;
@ -54,12 +55,18 @@ template <class SecondaryT> static void testSecondaryBasic(void) {
}
TEST(ScudoSecondaryTest, SecondaryBasic) {
testSecondaryBasic<scudo::MapAllocator<>>();
testSecondaryBasic<scudo::MapAllocator<0U>>();
#if !SCUDO_FUCHSIA
testSecondaryBasic<scudo::MapAllocator<>>();
testSecondaryBasic<scudo::MapAllocator<64U>>();
#endif
}
#if SCUDO_FUCHSIA
using LargeAllocator = scudo::MapAllocator<0U>;
#else
using LargeAllocator = scudo::MapAllocator<>;
#endif
// This exercises a variety of combinations of size and alignment for the
// MapAllocator. The size computation done here mimic the ones done by the

View File

@ -6,8 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "scudo/standalone/size_class_map.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include "size_class_map.h"
template <class SizeClassMap> void testSizeClassMap() {
typedef SizeClassMap SCMap;

View File

@ -6,8 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "scudo/standalone/stats.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include "stats.h"
TEST(ScudoStatsTest, LocalStats) {
scudo::LocalStats LStats;

View File

@ -6,8 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "scudo/standalone/string_utils.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include "string_utils.h"
#include <limits.h>

View File

@ -6,11 +6,11 @@
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "tsd_exclusive.h"
#include "tsd_shared.h"
#include "gtest/gtest.h"
#include <condition_variable>
#include <mutex>
#include <thread>
@ -108,7 +108,9 @@ template <class AllocatorT> static void testRegistry() {
TEST(ScudoTSDTest, TSDRegistryBasic) {
testRegistry<MockAllocator<OneCache>>();
testRegistry<MockAllocator<SharedCaches>>();
#if !SCUDO_FUCHSIA
testRegistry<MockAllocator<ExclusiveCaches>>();
#endif
}
static std::mutex Mutex;
@ -164,5 +166,7 @@ template <class AllocatorT> static void testRegistryThreaded() {
TEST(ScudoTSDTest, TSDRegistryThreaded) {
testRegistryThreaded<MockAllocator<OneCache>>();
testRegistryThreaded<MockAllocator<SharedCaches>>();
#if !SCUDO_FUCHSIA
testRegistryThreaded<MockAllocator<ExclusiveCaches>>();
#endif
}

View File

@ -6,9 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "vector.h"
#include "tests/scudo_unit_test.h"
#include "gtest/gtest.h"
#include "vector.h"
TEST(ScudoVectorTest, Basic) {
scudo::Vector<int> V;

View File

@ -6,10 +6,9 @@
//
//===----------------------------------------------------------------------===//
#include "platform.h"
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include <errno.h>
#include <limits.h>
#include <malloc.h>
#include <stdlib.h>
@ -32,11 +31,6 @@ int malloc_iterate(uintptr_t base, size_t size,
// We have to use a small quarantine to make sure that our double-free tests
// trigger. Otherwise EXPECT_DEATH ends up reallocating the chunk that was just
// freed (this depends on the size obviously) and the following free succeeds.
extern "C" __attribute__((visibility("default"))) const char *
__scudo_default_options() {
return "quarantine_size_kb=256:thread_local_quarantine_size_kb=128:"
"quarantine_max_chunk_size=512";
}
static const size_t Size = 100U;
@ -200,6 +194,7 @@ TEST(ScudoWrappersCTest, Realloc) {
#define M_PURGE -101
#endif
#if !SCUDO_FUCHSIA
TEST(ScudoWrappersCTest, MallOpt) {
errno = 0;
EXPECT_EQ(mallopt(-1000, 1), 0);
@ -213,8 +208,10 @@ TEST(ScudoWrappersCTest, MallOpt) {
EXPECT_EQ(mallopt(M_DECAY_TIME, 1), 1);
EXPECT_EQ(mallopt(M_DECAY_TIME, 0), 1);
}
#endif
TEST(ScudoWrappersCTest, OtherAlloc) {
#if !SCUDO_FUCHSIA
const size_t PageSize = sysconf(_SC_PAGESIZE);
void *P = pvalloc(Size);
@ -229,10 +226,12 @@ TEST(ScudoWrappersCTest, OtherAlloc) {
EXPECT_NE(P, nullptr);
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) & (PageSize - 1), 0U);
free(P);
#endif
EXPECT_EQ(valloc(SIZE_MAX), nullptr);
}
#if !SCUDO_FUCHSIA
TEST(ScudoWrappersCTest, MallInfo) {
const size_t BypassQuarantineSize = 1024U;
@ -248,6 +247,7 @@ TEST(ScudoWrappersCTest, MallInfo) {
MI = mallinfo();
EXPECT_GE(static_cast<size_t>(MI.fordblks), Free + BypassQuarantineSize);
}
#endif
static uintptr_t BoundaryP;
static size_t Count;
@ -282,6 +282,7 @@ TEST(ScudoWrappersCTest, MallocIterateBoundary) {
free(P);
}
#if !SCUDO_FUCHSIA
TEST(ScudoWrappersCTest, MallocInfo) {
char Buffer[64];
FILE *F = fmemopen(Buffer, sizeof(Buffer), "w+");
@ -292,3 +293,4 @@ TEST(ScudoWrappersCTest, MallocInfo) {
fclose(F);
EXPECT_EQ(strncmp(Buffer, "<malloc version=\"scudo-", 23), 0);
}
#endif

View File

@ -6,11 +6,12 @@
//
//===----------------------------------------------------------------------===//
#include "gtest/gtest.h"
#include "tests/scudo_unit_test.h"
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
void operator delete(void *, size_t) noexcept;
void operator delete[](void *, size_t) noexcept;
@ -18,12 +19,6 @@ void operator delete[](void *, size_t) noexcept;
// Note that every Cxx allocation function in the test binary will be fulfilled
// by Scudo. See the comment in the C counterpart of this file.
extern "C" __attribute__((visibility("default"))) const char *
__scudo_default_options() {
return "quarantine_size_kb=256:thread_local_quarantine_size_kb=128:"
"quarantine_max_chunk_size=512:dealloc_type_mismatch=true";
}
template <typename T> static void testCxxNew() {
T *P = new T;
EXPECT_NE(P, nullptr);