diff options
Diffstat (limited to 'lib/scudo/standalone/tests')
-rw-r--r-- | lib/scudo/standalone/tests/CMakeLists.txt | 69 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/atomic_test.cc | 112 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/bytemap_test.cc | 73 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/checksum_test.cc | 58 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/flags_test.cc | 119 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/list_test.cc | 185 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/map_test.cc | 65 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/mutex_test.cc | 121 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/release_test.cc | 260 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/report_test.cc | 47 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/scudo_unit_test_main.cc | 14 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/secondary_test.cc | 137 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/size_class_map_test.cc | 38 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/stats_test.cc | 45 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/strings_test.cc | 98 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/vector_test.cc | 43 |
16 files changed, 1484 insertions, 0 deletions
diff --git a/lib/scudo/standalone/tests/CMakeLists.txt b/lib/scudo/standalone/tests/CMakeLists.txt new file mode 100644 index 000000000..182d6a26a --- /dev/null +++ b/lib/scudo/standalone/tests/CMakeLists.txt @@ -0,0 +1,69 @@ +include_directories(..) + +add_custom_target(ScudoUnitTests) +set_target_properties(ScudoUnitTests PROPERTIES + FOLDER "Compiler-RT Tests") + +set(SCUDO_UNITTEST_CFLAGS + ${COMPILER_RT_UNITTEST_CFLAGS} + ${COMPILER_RT_GTEST_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/scudo/standalone + -DGTEST_HAS_RTTI=0) + +set(SCUDO_TEST_ARCH ${SCUDO_STANDALONE_SUPPORTED_ARCH}) + +# gtests requires c++ +set(LINK_FLAGS ${COMPILER_RT_UNITTEST_LINK_FLAGS}) +foreach(lib ${SANITIZER_TEST_CXX_LIBRARIES}) + list(APPEND LINK_FLAGS -l${lib}) +endforeach() +list(APPEND LINK_FLAGS -pthread) + +set(TEST_HEADERS) +foreach (header ${SCUDO_HEADERS}) + list(APPEND TEST_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../${header}) +endforeach() + +# add_scudo_unittest(<name> +# SOURCES <sources list> +# HEADERS <extra headers list>) +macro(add_scudo_unittest testname) + cmake_parse_arguments(TEST "" "" "SOURCES;HEADERS" ${ARGN}) + if(COMPILER_RT_HAS_SCUDO_STANDALONE) + foreach(arch ${SCUDO_TEST_ARCH}) + set(ScudoUnitTestsObjects) + add_library("RTScudoStandalone.test.${arch}" STATIC + $<TARGET_OBJECTS:RTScudoStandalone.${arch}>) + generate_compiler_rt_tests(ScudoUnitTestsObjects ScudoUnitTests + "${testname}-${arch}-Test" ${arch} + SOURCES ${TEST_SOURCES} ${COMPILER_RT_GTEST_SOURCE} + COMPILE_DEPS ${TEST_HEADERS} + DEPS gtest scudo_standalone + RUNTIME RTScudoStandalone.test.${arch} + CFLAGS ${SCUDO_UNITTEST_CFLAGS} + LINK_FLAGS ${LINK_FLAGS}) + endforeach() + endif() +endmacro() + +set(SCUDO_UNIT_TEST_SOURCES + atomic_test.cc + bytemap_test.cc + checksum_test.cc + flags_test.cc + list_test.cc + map_test.cc + mutex_test.cc + release_test.cc + report_test.cc + secondary_test.cc + size_class_map_test.cc + stats_test.cc + strings_test.cc + vector_test.cc + scudo_unit_test_main.cc) + +add_scudo_unittest(ScudoUnitTest + SOURCES ${SCUDO_UNIT_TEST_SOURCES}) diff --git a/lib/scudo/standalone/tests/atomic_test.cc b/lib/scudo/standalone/tests/atomic_test.cc new file mode 100644 index 000000000..3095451b9 --- /dev/null +++ b/lib/scudo/standalone/tests/atomic_test.cc @@ -0,0 +1,112 @@ +//===-- atomic_test.cc ------------------------------------------*- 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 "scudo/standalone/atomic_helpers.h" +#include "gtest/gtest.h" + +namespace scudo { + +template <typename T> struct ValAndMagic { + typename T::Type Magic0; + T A; + typename T::Type Magic1; + + static ValAndMagic<T> *Sink; +}; + +template <typename T> ValAndMagic<T> *ValAndMagic<T>::Sink; + +template <typename T, memory_order LoadMO, memory_order StoreMO> +void checkStoreLoad() { + typedef typename T::Type Type; + ValAndMagic<T> Val; + // Prevent the compiler from scalarizing the struct. + ValAndMagic<T>::Sink = &Val; + // Ensure that surrounding memory is not overwritten. + Val.Magic0 = Val.Magic1 = (Type)-3; + for (u64 I = 0; I < 100; I++) { + // Generate A value that occupies all bytes of the variable. + u64 V = I; + V |= V << 8; + V |= V << 16; + V |= V << 32; + Val.A.ValDoNotUse = (Type)V; + EXPECT_EQ(atomic_load(&Val.A, LoadMO), (Type)V); + Val.A.ValDoNotUse = (Type)-1; + atomic_store(&Val.A, (Type)V, StoreMO); + EXPECT_EQ(Val.A.ValDoNotUse, (Type)V); + } + EXPECT_EQ(Val.Magic0, (Type)-3); + EXPECT_EQ(Val.Magic1, (Type)-3); +} + +TEST(ScudoAtomicTest, AtomicStoreLoad) { + checkStoreLoad<atomic_u8, memory_order_relaxed, memory_order_relaxed>(); + checkStoreLoad<atomic_u8, memory_order_consume, memory_order_relaxed>(); + checkStoreLoad<atomic_u8, memory_order_acquire, memory_order_relaxed>(); + checkStoreLoad<atomic_u8, memory_order_relaxed, memory_order_release>(); + checkStoreLoad<atomic_u8, memory_order_seq_cst, memory_order_seq_cst>(); + + checkStoreLoad<atomic_u16, memory_order_relaxed, memory_order_relaxed>(); + checkStoreLoad<atomic_u16, memory_order_consume, memory_order_relaxed>(); + checkStoreLoad<atomic_u16, memory_order_acquire, memory_order_relaxed>(); + checkStoreLoad<atomic_u16, memory_order_relaxed, memory_order_release>(); + checkStoreLoad<atomic_u16, memory_order_seq_cst, memory_order_seq_cst>(); + + checkStoreLoad<atomic_u32, memory_order_relaxed, memory_order_relaxed>(); + checkStoreLoad<atomic_u32, memory_order_consume, memory_order_relaxed>(); + checkStoreLoad<atomic_u32, memory_order_acquire, memory_order_relaxed>(); + checkStoreLoad<atomic_u32, memory_order_relaxed, memory_order_release>(); + checkStoreLoad<atomic_u32, memory_order_seq_cst, memory_order_seq_cst>(); + + checkStoreLoad<atomic_u64, memory_order_relaxed, memory_order_relaxed>(); + checkStoreLoad<atomic_u64, memory_order_consume, memory_order_relaxed>(); + checkStoreLoad<atomic_u64, memory_order_acquire, memory_order_relaxed>(); + checkStoreLoad<atomic_u64, memory_order_relaxed, memory_order_release>(); + checkStoreLoad<atomic_u64, memory_order_seq_cst, memory_order_seq_cst>(); + + checkStoreLoad<atomic_uptr, memory_order_relaxed, memory_order_relaxed>(); + checkStoreLoad<atomic_uptr, memory_order_consume, memory_order_relaxed>(); + checkStoreLoad<atomic_uptr, memory_order_acquire, memory_order_relaxed>(); + checkStoreLoad<atomic_uptr, memory_order_relaxed, memory_order_release>(); + checkStoreLoad<atomic_uptr, memory_order_seq_cst, memory_order_seq_cst>(); +} + +template <typename T> void checkAtomicCompareExchange() { + typedef typename T::Type Type; + { + Type OldVal = 42; + Type NewVal = 24; + Type V = OldVal; + EXPECT_TRUE(atomic_compare_exchange_strong( + reinterpret_cast<T *>(&V), &OldVal, NewVal, memory_order_relaxed)); + EXPECT_FALSE(atomic_compare_exchange_strong( + reinterpret_cast<T *>(&V), &OldVal, NewVal, memory_order_relaxed)); + EXPECT_EQ(NewVal, OldVal); + } + { + Type OldVal = 42; + Type NewVal = 24; + Type V = OldVal; + EXPECT_TRUE(atomic_compare_exchange_weak(reinterpret_cast<T *>(&V), &OldVal, + NewVal, memory_order_relaxed)); + EXPECT_FALSE(atomic_compare_exchange_weak( + reinterpret_cast<T *>(&V), &OldVal, NewVal, memory_order_relaxed)); + EXPECT_EQ(NewVal, OldVal); + } +} + +TEST(ScudoAtomicTest, AtomicCompareExchangeTest) { + checkAtomicCompareExchange<atomic_u8>(); + checkAtomicCompareExchange<atomic_u16>(); + checkAtomicCompareExchange<atomic_u32>(); + checkAtomicCompareExchange<atomic_u64>(); + checkAtomicCompareExchange<atomic_uptr>(); +} + +} // namespace scudo diff --git a/lib/scudo/standalone/tests/bytemap_test.cc b/lib/scudo/standalone/tests/bytemap_test.cc new file mode 100644 index 000000000..615b946c5 --- /dev/null +++ b/lib/scudo/standalone/tests/bytemap_test.cc @@ -0,0 +1,73 @@ +//===-- bytemap_test.cc -----------------------------------------*- 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 "bytemap.h" + +#include "gtest/gtest.h" + +#include <string.h> + +template <typename T> void testMap(T &Map, scudo::uptr Size) { + Map.init(); + for (scudo::uptr I = 0; I < Size; I += 7) + Map.set(I, (I % 100) + 1); + for (scudo::uptr J = 0; J < Size; J++) { + if (J % 7) + EXPECT_EQ(Map[J], 0); + else + EXPECT_EQ(Map[J], (J % 100) + 1); + } +} + +TEST(ScudoByteMapTest, FlatByteMap) { + const scudo::uptr Size = 1U << 10; + scudo::FlatByteMap<Size> Map; + testMap(Map, Size); +} + +TEST(ScudoByteMapTest, TwoLevelByteMap) { + const scudo::uptr Size1 = 1U << 6, Size2 = 1U << 12; + scudo::TwoLevelByteMap<Size1, Size2> Map; + testMap(Map, Size1 * Size2); + Map.reset(); +} + +using TestByteMap = scudo::TwoLevelByteMap<1U << 12, 1U << 13>; + +struct TestByteMapParam { + TestByteMap *Map; + scudo::uptr Shard; + scudo::uptr NumberOfShards; +}; + +void *populateByteMap(void *Param) { + TestByteMapParam *P = reinterpret_cast<TestByteMapParam *>(Param); + for (scudo::uptr I = P->Shard; I < P->Map->size(); I += P->NumberOfShards) { + scudo::u8 V = static_cast<scudo::u8>((I % 100) + 1); + P->Map->set(I, V); + EXPECT_EQ((*P->Map)[I], V); + } + return 0; +} + +TEST(ScudoByteMapTest, ThreadedTwoLevelByteMap) { + TestByteMap Map; + Map.init(); + static const scudo::uptr NumberOfThreads = 16U; + pthread_t T[NumberOfThreads]; + TestByteMapParam P[NumberOfThreads]; + for (scudo::uptr I = 0; I < NumberOfThreads; I++) { + P[I].Map = ⤅ + P[I].Shard = I; + P[I].NumberOfShards = NumberOfThreads; + pthread_create(&T[I], 0, populateByteMap, &P[I]); + } + for (scudo::uptr I = 0; I < NumberOfThreads; I++) + pthread_join(T[I], 0); + Map.reset(); +} diff --git a/lib/scudo/standalone/tests/checksum_test.cc b/lib/scudo/standalone/tests/checksum_test.cc new file mode 100644 index 000000000..2e8dc8a7b --- /dev/null +++ b/lib/scudo/standalone/tests/checksum_test.cc @@ -0,0 +1,58 @@ +//===-- checksum_test.cc ----------------------------------------*- 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 "checksum.h" + +#include "gtest/gtest.h" + +#include <string.h> + +scudo::u16 computeSoftwareChecksum(scudo::u32 Seed, scudo::uptr *Array, + scudo::uptr ArraySize) { + scudo::u16 Checksum = static_cast<scudo::u16>(Seed & 0xffff); + for (scudo::uptr I = 0; I < ArraySize; I++) + Checksum = scudo::computeBSDChecksum(Checksum, Array[I]); + return Checksum; +} + +scudo::u16 computeHardwareChecksum(scudo::u32 Seed, scudo::uptr *Array, + scudo::uptr ArraySize) { + scudo::u32 Crc = Seed; + for (scudo::uptr I = 0; I < ArraySize; I++) + Crc = scudo::computeHardwareCRC32(Crc, Array[I]); + return static_cast<scudo::u16>((Crc & 0xffff) ^ (Crc >> 16)); +} + +typedef scudo::u16 (*ComputeChecksum)(scudo::u32, scudo::uptr *, scudo::uptr); + +// This verifies that flipping bits in the data being checksummed produces a +// different checksum. We do not use random data to avoid flakyness. +template <ComputeChecksum F> void verifyChecksumFunctionBitFlip() { + scudo::uptr Array[sizeof(scudo::u64) / sizeof(scudo::uptr)]; + const scudo::uptr ArraySize = ARRAY_SIZE(Array); + memset(Array, 0xaa, sizeof(Array)); + const scudo::u32 Seed = 0x41424343U; + const scudo::u16 Reference = F(Seed, Array, ArraySize); + scudo::u8 IdenticalChecksums = 0; + for (scudo::uptr I = 0; I < ArraySize; I++) { + for (scudo::uptr J = 0; J < SCUDO_WORDSIZE; J++) { + Array[I] ^= 1U << J; + if (F(Seed, Array, ArraySize) == Reference) + IdenticalChecksums++; + Array[I] ^= 1U << J; + } + } + // Allow for a couple of identical checksums over the whole set of flips. + EXPECT_LE(IdenticalChecksums, 2); +} + +TEST(ScudoChecksumTest, ChecksumFunctions) { + verifyChecksumFunctionBitFlip<computeSoftwareChecksum>(); + if (&scudo::computeHardwareCRC32 && scudo::hasHardwareCRC32()) + verifyChecksumFunctionBitFlip<computeHardwareChecksum>(); +} diff --git a/lib/scudo/standalone/tests/flags_test.cc b/lib/scudo/standalone/tests/flags_test.cc new file mode 100644 index 000000000..2808a46cd --- /dev/null +++ b/lib/scudo/standalone/tests/flags_test.cc @@ -0,0 +1,119 @@ +//===-- flags_test.cc -------------------------------------------*- 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 "flags.h" +#include "flags_parser.h" + +#include "gtest/gtest.h" + +#include <string.h> + +static const char FlagName[] = "flag_name"; +static const char FlagDesc[] = "flag description"; + +template <typename T> +static void testFlag(scudo::FlagType Type, T StartValue, const char *Env, + T FinalValue) { + scudo::FlagParser Parser; + T Flag = StartValue; + Parser.registerFlag(FlagName, FlagDesc, Type, &Flag); + Parser.parseString(Env); + EXPECT_EQ(FinalValue, Flag); + // Reporting unrecognized flags is needed to reset them. + scudo::reportUnrecognizedFlags(); +} + +TEST(ScudoFlagsTest, BooleanFlags) { + testFlag(scudo::FlagType::FT_bool, false, "flag_name=1", true); + testFlag(scudo::FlagType::FT_bool, false, "flag_name=yes", true); + testFlag(scudo::FlagType::FT_bool, false, "flag_name='yes'", true); + testFlag(scudo::FlagType::FT_bool, false, "flag_name=true", true); + testFlag(scudo::FlagType::FT_bool, true, "flag_name=0", false); + testFlag(scudo::FlagType::FT_bool, true, "flag_name=\"0\"", false); + testFlag(scudo::FlagType::FT_bool, true, "flag_name=no", false); + testFlag(scudo::FlagType::FT_bool, true, "flag_name=false", false); + testFlag(scudo::FlagType::FT_bool, true, "flag_name='false'", false); +} + +TEST(ScudoFlagsDeathTest, BooleanFlags) { + EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name", true), + "expected '='"); + EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=", true), + "invalid value for bool option: ''"); + EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=2", true), + "invalid value for bool option: '2'"); + EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=-1", true), + "invalid value for bool option: '-1'"); + EXPECT_DEATH(testFlag(scudo::FlagType::FT_bool, false, "flag_name=on", true), + "invalid value for bool option: 'on'"); +} + +TEST(ScudoFlagsTest, IntFlags) { + testFlag(scudo::FlagType::FT_int, -11, nullptr, -11); + testFlag(scudo::FlagType::FT_int, -11, "flag_name=0", 0); + testFlag(scudo::FlagType::FT_int, -11, "flag_name='0'", 0); + testFlag(scudo::FlagType::FT_int, -11, "flag_name=42", 42); + testFlag(scudo::FlagType::FT_int, -11, "flag_name=-42", -42); + testFlag(scudo::FlagType::FT_int, -11, "flag_name=\"-42\"", -42); + + // Unrecognized flags are ignored. + testFlag(scudo::FlagType::FT_int, -11, "--flag_name=42", -11); + testFlag(scudo::FlagType::FT_int, -11, "zzzzzzz=42", -11); +} + +TEST(ScudoFlagsDeathTest, IntFlags) { + EXPECT_DEATH(testFlag(scudo::FlagType::FT_int, -11, "flag_name", 0), + "expected '='"); + EXPECT_DEATH(testFlag(scudo::FlagType::FT_int, -11, "flag_name=42U", 0), + "invalid value for int option"); +} + +static void testTwoFlags(const char *Env, bool ExpectedFlag1, + const int ExpectedFlag2, const char *Name1 = "flag1", + const char *Name2 = "flag2") { + scudo::FlagParser Parser; + bool Flag1 = !ExpectedFlag1; + int Flag2; + Parser.registerFlag(Name1, FlagDesc, scudo::FlagType::FT_bool, &Flag1); + Parser.registerFlag(Name2, FlagDesc, scudo::FlagType::FT_int, &Flag2); + Parser.parseString(Env); + EXPECT_EQ(ExpectedFlag1, Flag1); + EXPECT_EQ(Flag2, ExpectedFlag2); + // Reporting unrecognized flags is needed to reset them. + scudo::reportUnrecognizedFlags(); +} + +TEST(ScudoFlagsTest, MultipleFlags) { + testTwoFlags("flag1=1 flag2=42", true, 42); + testTwoFlags("flag2=-1 flag1=0", false, -1); + testTwoFlags("flag1=false:flag2=1337", false, 1337); + testTwoFlags("flag2=42:flag1=yes", true, 42); + testTwoFlags("flag2=42\nflag1=yes", true, 42); + testTwoFlags("flag2=42\r\nflag1=yes", true, 42); + testTwoFlags("flag2=42\tflag1=yes", true, 42); +} + +TEST(ScudoFlagsTest, CommonSuffixFlags) { + testTwoFlags("flag=1 other_flag=42", true, 42, "flag", "other_flag"); + testTwoFlags("other_flag=42 flag=1", true, 42, "flag", "other_flag"); +} + +TEST(ScudoFlagsTest, AllocatorFlags) { + scudo::FlagParser Parser; + scudo::Flags Flags; + scudo::registerFlags(&Parser, &Flags); + Flags.setDefaults(); + Flags.dealloc_type_mismatch = false; + Flags.delete_size_mismatch = false; + Flags.quarantine_max_chunk_size = 1024; + Parser.parseString("dealloc_type_mismatch=true:delete_size_mismatch=true:" + "quarantine_max_chunk_size=2048"); + EXPECT_TRUE(Flags.dealloc_type_mismatch); + EXPECT_TRUE(Flags.delete_size_mismatch); + EXPECT_EQ(2048, Flags.quarantine_max_chunk_size); +} diff --git a/lib/scudo/standalone/tests/list_test.cc b/lib/scudo/standalone/tests/list_test.cc new file mode 100644 index 000000000..e4053d8b0 --- /dev/null +++ b/lib/scudo/standalone/tests/list_test.cc @@ -0,0 +1,185 @@ +//===-- list_test.cc --------------------------------------------*- 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 "scudo/standalone/list.h" +#include "gtest/gtest.h" + +struct ListItem { + ListItem *Next; +}; + +typedef scudo::IntrusiveList<ListItem> List; + +static List StaticList; + +static void setList(List *L, ListItem *X = nullptr, ListItem *Y = nullptr, + ListItem *Z = nullptr) { + L->clear(); + if (X) + L->push_back(X); + if (Y) + L->push_back(Y); + if (Z) + L->push_back(Z); +} + +static void checkList(List *L, ListItem *I1, ListItem *I2 = nullptr, + ListItem *I3 = nullptr, ListItem *I4 = nullptr, + ListItem *I5 = nullptr, ListItem *I6 = nullptr) { + if (I1) { + EXPECT_EQ(L->front(), I1); + L->pop_front(); + } + if (I2) { + EXPECT_EQ(L->front(), I2); + L->pop_front(); + } + if (I3) { + EXPECT_EQ(L->front(), I3); + L->pop_front(); + } + if (I4) { + EXPECT_EQ(L->front(), I4); + L->pop_front(); + } + if (I5) { + EXPECT_EQ(L->front(), I5); + L->pop_front(); + } + if (I6) { + EXPECT_EQ(L->front(), I6); + L->pop_front(); + } + EXPECT_TRUE(L->empty()); +} + +TEST(ScudoListTest, IntrusiveList) { + ListItem Items[6]; + EXPECT_EQ(StaticList.size(), 0U); + + List L; + L.clear(); + + ListItem *X = &Items[0]; + ListItem *Y = &Items[1]; + ListItem *Z = &Items[2]; + ListItem *A = &Items[3]; + ListItem *B = &Items[4]; + ListItem *C = &Items[5]; + + EXPECT_EQ(L.size(), 0U); + L.push_back(X); + EXPECT_EQ(L.size(), 1U); + EXPECT_EQ(L.back(), X); + EXPECT_EQ(L.front(), X); + L.pop_front(); + EXPECT_TRUE(L.empty()); + L.checkConsistency(); + + L.push_front(X); + EXPECT_EQ(L.size(), 1U); + EXPECT_EQ(L.back(), X); + EXPECT_EQ(L.front(), X); + L.pop_front(); + EXPECT_TRUE(L.empty()); + L.checkConsistency(); + + L.push_front(X); + L.push_front(Y); + L.push_front(Z); + EXPECT_EQ(L.size(), 3U); + EXPECT_EQ(L.front(), Z); + EXPECT_EQ(L.back(), X); + L.checkConsistency(); + + L.pop_front(); + EXPECT_EQ(L.size(), 2U); + EXPECT_EQ(L.front(), Y); + EXPECT_EQ(L.back(), X); + L.pop_front(); + L.pop_front(); + EXPECT_TRUE(L.empty()); + L.checkConsistency(); + + L.push_back(X); + L.push_back(Y); + L.push_back(Z); + EXPECT_EQ(L.size(), 3U); + EXPECT_EQ(L.front(), X); + EXPECT_EQ(L.back(), Z); + L.checkConsistency(); + + L.pop_front(); + EXPECT_EQ(L.size(), 2U); + EXPECT_EQ(L.front(), Y); + EXPECT_EQ(L.back(), Z); + L.pop_front(); + L.pop_front(); + EXPECT_TRUE(L.empty()); + L.checkConsistency(); + + L.push_back(X); + L.push_back(Y); + L.push_back(Z); + L.extract(X, Y); + EXPECT_EQ(L.size(), 2U); + EXPECT_EQ(L.front(), X); + EXPECT_EQ(L.back(), Z); + L.checkConsistency(); + L.extract(X, Z); + EXPECT_EQ(L.size(), 1U); + EXPECT_EQ(L.front(), X); + EXPECT_EQ(L.back(), X); + L.checkConsistency(); + L.pop_front(); + EXPECT_TRUE(L.empty()); + + List L1, L2; + L1.clear(); + L2.clear(); + + L1.append_front(&L2); + EXPECT_TRUE(L1.empty()); + EXPECT_TRUE(L2.empty()); + + L1.append_back(&L2); + EXPECT_TRUE(L1.empty()); + EXPECT_TRUE(L2.empty()); + + setList(&L1, X); + checkList(&L1, X); + + setList(&L1, X, Y, Z); + setList(&L2, A, B, C); + L1.append_back(&L2); + checkList(&L1, X, Y, Z, A, B, C); + EXPECT_TRUE(L2.empty()); + + setList(&L1, X, Y); + setList(&L2); + L1.append_front(&L2); + checkList(&L1, X, Y); + EXPECT_TRUE(L2.empty()); +} + +TEST(ScudoListTest, IntrusiveListAppendEmpty) { + ListItem I; + List L; + L.clear(); + L.push_back(&I); + List L2; + L2.clear(); + L.append_back(&L2); + EXPECT_EQ(L.back(), &I); + EXPECT_EQ(L.front(), &I); + EXPECT_EQ(L.size(), 1U); + L.append_front(&L2); + EXPECT_EQ(L.back(), &I); + EXPECT_EQ(L.front(), &I); + EXPECT_EQ(L.size(), 1U); +} diff --git a/lib/scudo/standalone/tests/map_test.cc b/lib/scudo/standalone/tests/map_test.cc new file mode 100644 index 000000000..7c726e947 --- /dev/null +++ b/lib/scudo/standalone/tests/map_test.cc @@ -0,0 +1,65 @@ +//===-- map_test.cc ---------------------------------------------*- 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 "common.h" + +#include "gtest/gtest.h" + +#include <string.h> + +static const char *MappingName = "scudo:test"; + +TEST(ScudoMapTest, MapNoAccessUnmap) { + const scudo::uptr Size = 4 * scudo::getPageSizeCached(); + scudo::MapPlatformData Data = {}; + void *P = scudo::map(nullptr, Size, MappingName, MAP_NOACCESS, &Data); + EXPECT_NE(P, nullptr); + EXPECT_DEATH(memset(P, 0xaa, Size), ""); + scudo::unmap(P, Size, UNMAP_ALL, &Data); +} + +TEST(ScudoMapTest, MapUnmap) { + const scudo::uptr Size = 4 * scudo::getPageSizeCached(); + scudo::MapPlatformData Data = {}; + void *P = scudo::map(nullptr, Size, MappingName, 0, &Data); + EXPECT_NE(P, nullptr); + memset(P, 0xaa, Size); + scudo::unmap(P, Size, 0, &Data); + EXPECT_DEATH(memset(P, 0xbb, Size), ""); +} + +TEST(ScudoMapTest, MapWithGuardUnmap) { + const scudo::uptr PageSize = scudo::getPageSizeCached(); + const scudo::uptr Size = 4 * PageSize; + scudo::MapPlatformData Data = {}; + void *P = scudo::map(nullptr, Size + 2 * PageSize, MappingName, MAP_NOACCESS, + &Data); + EXPECT_NE(P, nullptr); + void *Q = + reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) + PageSize); + EXPECT_EQ(scudo::map(Q, Size, MappingName, 0, &Data), Q); + memset(Q, 0xaa, Size); + EXPECT_DEATH(memset(Q, 0xaa, Size + 1), ""); + scudo::unmap(P, Size + 2 * PageSize, UNMAP_ALL, &Data); +} + +TEST(ScudoMapTest, MapGrowUnmap) { + const scudo::uptr PageSize = scudo::getPageSizeCached(); + const scudo::uptr Size = 4 * PageSize; + scudo::MapPlatformData Data = {}; + void *P = scudo::map(nullptr, Size, MappingName, MAP_NOACCESS, &Data); + EXPECT_NE(P, nullptr); + void *Q = + reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) + PageSize); + EXPECT_EQ(scudo::map(Q, PageSize, MappingName, 0, &Data), Q); + memset(Q, 0xaa, PageSize); + Q = reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(Q) + PageSize); + EXPECT_EQ(scudo::map(Q, PageSize, MappingName, 0, &Data), Q); + memset(Q, 0xbb, PageSize); + scudo::unmap(P, Size, UNMAP_ALL, &Data); +} diff --git a/lib/scudo/standalone/tests/mutex_test.cc b/lib/scudo/standalone/tests/mutex_test.cc new file mode 100644 index 000000000..ce33db58b --- /dev/null +++ b/lib/scudo/standalone/tests/mutex_test.cc @@ -0,0 +1,121 @@ +//===-- mutex_test.cc -------------------------------------------*- 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 "mutex.h" + +#include "gtest/gtest.h" + +#include <string.h> + +template <typename MutexType> class TestData { +public: + explicit TestData(MutexType *M) : Mutex(M) { + for (scudo::u32 I = 0; I < Size; I++) + Data[I] = 0; + } + + void write() { + Lock L(Mutex); + T V0 = Data[0]; + for (scudo::u32 I = 0; I < Size; I++) { + EXPECT_EQ(Data[I], V0); + Data[I]++; + } + } + + void tryWrite() { + if (!Mutex->tryLock()) + return; + T V0 = Data[0]; + for (scudo::u32 I = 0; I < Size; I++) { + EXPECT_EQ(Data[I], V0); + Data[I]++; + } + Mutex->unlock(); + } + + void backoff() { + volatile T LocalData[Size] = {}; + for (scudo::u32 I = 0; I < Size; I++) { + LocalData[I]++; + EXPECT_EQ(LocalData[I], 1U); + } + } + +private: + typedef scudo::GenericScopedLock<MutexType> Lock; + static const scudo::u32 Size = 64U; + typedef scudo::u64 T; + MutexType *Mutex; + ALIGNED(SCUDO_CACHE_LINE_SIZE) T Data[Size]; +}; + +const scudo::u32 NumberOfThreads = 8; +#if SCUDO_DEBUG +const scudo::u32 NumberOfIterations = 4 * 1024; +#else +const scudo::u32 NumberOfIterations = 16 * 1024; +#endif + +template <typename MutexType> static void *lockThread(void *Param) { + TestData<MutexType> *Data = reinterpret_cast<TestData<MutexType> *>(Param); + for (scudo::u32 I = 0; I < NumberOfIterations; I++) { + Data->write(); + Data->backoff(); + } + return 0; +} + +template <typename MutexType> static void *tryThread(void *Param) { + TestData<MutexType> *Data = reinterpret_cast<TestData<MutexType> *>(Param); + for (scudo::u32 I = 0; I < NumberOfIterations; I++) { + Data->tryWrite(); + Data->backoff(); + } + return 0; +} + +template <typename MutexType> static void checkLocked(MutexType *M) { + scudo::GenericScopedLock<MutexType> L(M); + M->checkLocked(); +} + +TEST(ScudoMutexTest, SpinMutex) { + scudo::SpinMutex M; + M.init(); + TestData<scudo::SpinMutex> Data(&M); + pthread_t Threads[NumberOfThreads]; + for (scudo::u32 I = 0; I < NumberOfThreads; I++) + pthread_create(&Threads[I], 0, lockThread<scudo::SpinMutex>, &Data); + for (scudo::u32 I = 0; I < NumberOfThreads; I++) + pthread_join(Threads[I], 0); +} + +TEST(ScudoMutexTest, SpinMutexTry) { + scudo::SpinMutex M; + M.init(); + TestData<scudo::SpinMutex> Data(&M); + pthread_t Threads[NumberOfThreads]; + for (scudo::u32 I = 0; I < NumberOfThreads; I++) + pthread_create(&Threads[I], 0, tryThread<scudo::SpinMutex>, &Data); + for (scudo::u32 I = 0; I < NumberOfThreads; I++) + pthread_join(Threads[I], 0); +} + +TEST(ScudoMutexTest, BlockingMutex) { + scudo::u64 MutexMemory[1024] = {}; + scudo::BlockingMutex *M = + new (MutexMemory) scudo::BlockingMutex(scudo::LINKER_INITIALIZED); + TestData<scudo::BlockingMutex> Data(M); + pthread_t Threads[NumberOfThreads]; + for (scudo::u32 I = 0; I < NumberOfThreads; I++) + pthread_create(&Threads[I], 0, lockThread<scudo::BlockingMutex>, &Data); + for (scudo::u32 I = 0; I < NumberOfThreads; I++) + pthread_join(Threads[I], 0); + checkLocked(M); +} diff --git a/lib/scudo/standalone/tests/release_test.cc b/lib/scudo/standalone/tests/release_test.cc new file mode 100644 index 000000000..2279d5d15 --- /dev/null +++ b/lib/scudo/standalone/tests/release_test.cc @@ -0,0 +1,260 @@ +//===-- release_test.cc -----------------------------------------*- 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 "list.h" +#include "release.h" +#include "size_class_map.h" + +#include "gtest/gtest.h" + +#include <string.h> + +#include <algorithm> +#include <random> + +TEST(ScudoReleaseTest, PackedCounterArray) { + for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) { + // Various valid counter's max values packed into one word. + scudo::PackedCounterArray Counters2N(1, 1UL << I); + EXPECT_EQ(sizeof(scudo::uptr), Counters2N.getBufferSize()); + // Check the "all bit set" values too. + scudo::PackedCounterArray Counters2N1_1(1, ~0UL >> I); + EXPECT_EQ(sizeof(scudo::uptr), Counters2N1_1.getBufferSize()); + // Verify the packing ratio, the counter is Expected to be packed into the + // closest power of 2 bits. + scudo::PackedCounterArray Counters(SCUDO_WORDSIZE, 1UL << I); + EXPECT_EQ(sizeof(scudo::uptr) * scudo::roundUpToPowerOfTwo(I + 1), + Counters.getBufferSize()); + } + + // Go through 1, 2, 4, 8, .. {32,64} bits per counter. + for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) { + // Make sure counters request one memory page for the buffer. + const scudo::uptr NumCounters = + (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I); + scudo::PackedCounterArray Counters(NumCounters, 1UL << ((1UL << I) - 1)); + Counters.inc(0); + for (scudo::uptr C = 1; C < NumCounters - 1; C++) { + EXPECT_EQ(0UL, Counters.get(C)); + Counters.inc(C); + EXPECT_EQ(1UL, Counters.get(C - 1)); + } + EXPECT_EQ(0UL, Counters.get(NumCounters - 1)); + Counters.inc(NumCounters - 1); + if (I > 0) { + Counters.incRange(0, NumCounters - 1); + for (scudo::uptr C = 0; C < NumCounters; C++) + EXPECT_EQ(2UL, Counters.get(C)); + } + } +} + +class StringRangeRecorder { +public: + std::string ReportedPages; + + StringRangeRecorder() + : PageSizeScaledLog(scudo::getLog2(scudo::getPageSizeCached())) {} + + void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) { + From >>= PageSizeScaledLog; + To >>= PageSizeScaledLog; + EXPECT_LT(From, To); + if (!ReportedPages.empty()) + EXPECT_LT(LastPageReported, From); + ReportedPages.append(From - LastPageReported, '.'); + ReportedPages.append(To - From, 'x'); + LastPageReported = To; + } + +private: + const scudo::uptr PageSizeScaledLog; + scudo::uptr LastPageReported = 0; +}; + +TEST(ScudoReleaseTest, FreePagesRangeTracker) { + // 'x' denotes a page to be released, '.' denotes a page to be kept around. + const char *TestCases[] = { + "", + ".", + "x", + "........", + "xxxxxxxxxxx", + "..............xxxxx", + "xxxxxxxxxxxxxxxxxx.....", + "......xxxxxxxx........", + "xxx..........xxxxxxxxxxxxxxx", + "......xxxx....xxxx........", + "xxx..........xxxxxxxx....xxxxxxx", + "x.x.x.x.x.x.x.x.x.x.x.x.", + ".x.x.x.x.x.x.x.x.x.x.x.x", + ".x.x.x.x.x.x.x.x.x.x.x.x.", + "x.x.x.x.x.x.x.x.x.x.x.x.x", + }; + typedef scudo::FreePagesRangeTracker<StringRangeRecorder> RangeTracker; + + for (auto TestCase : TestCases) { + StringRangeRecorder Recorder; + RangeTracker Tracker(&Recorder); + for (scudo::uptr I = 0; TestCase[I] != 0; I++) + Tracker.processNextPage(TestCase[I] == 'x'); + Tracker.finish(); + // Strip trailing '.'-pages before comparing the results as they are not + // going to be reported to range_recorder anyway. + const char *LastX = strrchr(TestCase, 'x'); + std::string Expected(TestCase, + LastX == nullptr ? 0 : (LastX - TestCase + 1)); + EXPECT_STREQ(Expected.c_str(), Recorder.ReportedPages.c_str()); + } +} + +class ReleasedPagesRecorder { +public: + std::set<scudo::uptr> ReportedPages; + + void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) { + const scudo::uptr PageSize = scudo::getPageSizeCached(); + for (scudo::uptr I = From; I < To; I += PageSize) + ReportedPages.insert(I); + } +}; + +// Simplified version of a TransferBatch. +template <class SizeClassMap> struct FreeBatch { + static const scudo::u32 MaxCount = SizeClassMap::MaxNumCachedHint; + void clear() { Count = 0; } + void add(scudo::uptr P) { + DCHECK_LT(Count, MaxCount); + Batch[Count++] = P; + } + scudo::u32 getCount() const { return Count; } + scudo::uptr get(scudo::u32 I) const { + DCHECK_LE(I, Count); + return Batch[I]; + } + FreeBatch *Next; + +private: + scudo::u32 Count; + scudo::uptr Batch[MaxCount]; +}; + +template <class SizeClassMap> void testReleaseFreeMemoryToOS() { + typedef FreeBatch<SizeClassMap> Batch; + const scudo::uptr AllocatedPagesCount = 1024; + const scudo::uptr PageSize = scudo::getPageSizeCached(); + std::mt19937 R; + scudo::u32 RandState = 42; + + for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) { + const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I); + const scudo::uptr MaxBlocks = AllocatedPagesCount * PageSize / BlockSize; + + // Generate the random free list. + std::vector<scudo::uptr> FreeArray; + bool InFreeRange = false; + scudo::uptr CurrentRangeEnd = 0; + for (scudo::uptr I = 0; I < MaxBlocks; I++) { + if (I == CurrentRangeEnd) { + InFreeRange = (scudo::getRandomU32(&RandState) & 1U) == 1; + CurrentRangeEnd += (scudo::getRandomU32(&RandState) & 0x7f) + 1; + } + if (InFreeRange) + FreeArray.push_back(I * BlockSize); + } + if (FreeArray.empty()) + continue; + // Shuffle the array to ensure that the order is irrelevant. + std::shuffle(FreeArray.begin(), FreeArray.end(), R); + + // Build the FreeList from the FreeArray. + scudo::IntrusiveList<Batch> FreeList; + FreeList.clear(); + Batch *CurrentBatch = nullptr; + for (auto const &Block : FreeArray) { + if (!CurrentBatch) { + CurrentBatch = new Batch; + CurrentBatch->clear(); + FreeList.push_back(CurrentBatch); + } + CurrentBatch->add(Block); + if (CurrentBatch->getCount() == Batch::MaxCount) + CurrentBatch = nullptr; + } + + // Release the memory. + ReleasedPagesRecorder Recorder; + releaseFreeMemoryToOS(&FreeList, 0, AllocatedPagesCount, BlockSize, + &Recorder); + + // Verify that there are no released pages touched by used chunks and all + // ranges of free chunks big enough to contain the entire memory pages had + // these pages released. + scudo::uptr VerifiedReleasedPages = 0; + std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end()); + + scudo::uptr CurrentBlock = 0; + InFreeRange = false; + scudo::uptr CurrentFreeRangeStart = 0; + for (scudo::uptr I = 0; I <= MaxBlocks; I++) { + const bool IsFreeBlock = + FreeBlocks.find(CurrentBlock) != FreeBlocks.end(); + if (IsFreeBlock) { + if (!InFreeRange) { + InFreeRange = true; + CurrentFreeRangeStart = CurrentBlock; + } + } else { + // Verify that this used chunk does not touch any released page. + const scudo::uptr StartPage = CurrentBlock / PageSize; + const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize; + for (scudo::uptr J = StartPage; J <= EndPage; J++) { + const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) != + Recorder.ReportedPages.end(); + EXPECT_EQ(false, PageReleased); + } + + if (InFreeRange) { + InFreeRange = false; + // Verify that all entire memory pages covered by this range of free + // chunks were released. + scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize); + while (P + PageSize <= CurrentBlock) { + const bool PageReleased = + Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end(); + EXPECT_EQ(true, PageReleased); + VerifiedReleasedPages++; + P += PageSize; + } + } + } + + CurrentBlock += BlockSize; + } + + EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages); + + while (!FreeList.empty()) { + CurrentBatch = FreeList.front(); + FreeList.pop_front(); + delete CurrentBatch; + } + } +} + +TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) { + testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>(); +} + +TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) { + testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>(); +} + +TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) { + testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>(); +} diff --git a/lib/scudo/standalone/tests/report_test.cc b/lib/scudo/standalone/tests/report_test.cc new file mode 100644 index 000000000..ce7eda59c --- /dev/null +++ b/lib/scudo/standalone/tests/report_test.cc @@ -0,0 +1,47 @@ +//===-- report_test.cc ------------------------------------------*- 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 "scudo/standalone/report.h" +#include "gtest/gtest.h" + +TEST(ScudoReportTest, Generic) { + 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"); + EXPECT_DEATH(scudo::reportHeaderRace(P), "Scudo ERROR.*42424242"); + EXPECT_DEATH(scudo::reportSanityCheckError("XYZ"), "Scudo ERROR.*XYZ"); + EXPECT_DEATH(scudo::reportAlignmentTooBig(123, 456), "Scudo ERROR.*123.*456"); + EXPECT_DEATH(scudo::reportAllocationSizeTooBig(123, 456, 789), + "Scudo ERROR.*123.*456.*789"); + EXPECT_DEATH(scudo::reportOutOfMemory(4242), "Scudo ERROR.*4242"); + EXPECT_DEATH( + scudo::reportInvalidChunkState(scudo::AllocatorAction::Recycling, P), + "Scudo ERROR.*recycling.*42424242"); + EXPECT_DEATH( + scudo::reportInvalidChunkState(scudo::AllocatorAction::Sizing, P), + "Scudo ERROR.*sizing.*42424242"); + EXPECT_DEATH( + scudo::reportMisalignedPointer(scudo::AllocatorAction::Deallocating, P), + "Scudo ERROR.*deallocating.*42424242"); + EXPECT_DEATH(scudo::reportDeallocTypeMismatch( + scudo::AllocatorAction::Reallocating, P, 0, 1), + "Scudo ERROR.*reallocating.*42424242"); + EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456), + "Scudo ERROR.*42424242.*123.*456"); +} + +TEST(ScudoReportTest, CSpecific) { + EXPECT_DEATH(scudo::reportAlignmentNotPowerOfTwo(123), "Scudo ERROR.*123"); + EXPECT_DEATH(scudo::reportCallocOverflow(123, 456), "Scudo ERROR.*123.*456"); + EXPECT_DEATH(scudo::reportInvalidPosixMemalignAlignment(789), + "Scudo ERROR.*789"); + EXPECT_DEATH(scudo::reportPvallocOverflow(123), "Scudo ERROR.*123"); + EXPECT_DEATH(scudo::reportInvalidAlignedAllocAlignment(123, 456), + "Scudo ERROR.*123.*456"); +} diff --git a/lib/scudo/standalone/tests/scudo_unit_test_main.cc b/lib/scudo/standalone/tests/scudo_unit_test_main.cc new file mode 100644 index 000000000..16398e5da --- /dev/null +++ b/lib/scudo/standalone/tests/scudo_unit_test_main.cc @@ -0,0 +1,14 @@ +//===-- scudo_unit_test_main.cc ---------------------------------*- 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 "gtest/gtest.h" + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/scudo/standalone/tests/secondary_test.cc b/lib/scudo/standalone/tests/secondary_test.cc new file mode 100644 index 000000000..8eed16e0e --- /dev/null +++ b/lib/scudo/standalone/tests/secondary_test.cc @@ -0,0 +1,137 @@ +//===-- secondary_test.cc ---------------------------------------*- 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 "secondary.h" + +#include "gtest/gtest.h" + +#include <stdio.h> + +#include <condition_variable> +#include <mutex> +#include <thread> + +TEST(ScudoSecondaryTest, SecondaryBasic) { + scudo::GlobalStats S; + S.init(); + scudo::MapAllocator *L = new scudo::MapAllocator; + L->init(&S); + const scudo::uptr Size = 1U << 16; + void *P = L->allocate(Size); + EXPECT_NE(P, nullptr); + memset(P, 'A', Size); + EXPECT_GE(scudo::MapAllocator::getBlockSize(P), Size); + L->deallocate(P); + EXPECT_DEATH(memset(P, 'A', Size), ""); + + const scudo::uptr Align = 1U << 16; + P = L->allocate(Size + Align, Align); + EXPECT_NE(P, nullptr); + void *AlignedP = reinterpret_cast<void *>( + scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align)); + memset(AlignedP, 'A', Size); + L->deallocate(P); + + std::vector<void *> V; + for (scudo::u8 I = 0; I < 32; I++) + V.push_back(L->allocate(Size)); + std::random_shuffle(V.begin(), V.end()); + while (!V.empty()) { + L->deallocate(V.back()); + V.pop_back(); + } + L->printStats(); +} + +// This exercises a variety of combinations of size and alignment for the +// MapAllocator. The size computation done here mimic the ones done by the +// combined allocator. +TEST(ScudoSecondaryTest, SecondaryCombinations) { + constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16); + constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign); + scudo::MapAllocator *L = new scudo::MapAllocator; + L->init(nullptr); + for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) { + for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16; + AlignLog++) { + const scudo::uptr Align = 1U << AlignLog; + for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) { + if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0) + continue; + const scudo::uptr UserSize = + scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign); + const scudo::uptr Size = + HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0); + void *P = L->allocate(Size, Align); + EXPECT_NE(P, nullptr); + void *AlignedP = reinterpret_cast<void *>( + scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align)); + memset(AlignedP, 0xff, UserSize); + L->deallocate(P); + } + } + } + L->printStats(); +} + +TEST(ScudoSecondaryTest, SecondaryIterate) { + scudo::MapAllocator *L = new scudo::MapAllocator; + L->init(nullptr); + std::vector<void *> V; + const scudo::uptr PageSize = scudo::getPageSizeCached(); + for (scudo::u8 I = 0; I < 32; I++) + V.push_back(L->allocate((std::rand() % 16) * PageSize)); + auto Lambda = [V](scudo::uptr Block) { + EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)), + V.end()); + }; + L->disable(); + L->iterateOverBlocks(Lambda); + L->enable(); + while (!V.empty()) { + L->deallocate(V.back()); + V.pop_back(); + } + L->printStats(); +} + +std::mutex Mutex; +std::condition_variable Cv; +bool Ready = false; + +static void performAllocations(scudo::MapAllocator *L) { + { + std::unique_lock<std::mutex> Lock(Mutex); + while (!Ready) + Cv.wait(Lock); + } + std::vector<void *> V; + const scudo::uptr PageSize = scudo::getPageSizeCached(); + for (scudo::u8 I = 0; I < 32; I++) + V.push_back(L->allocate((std::rand() % 16) * PageSize)); + while (!V.empty()) { + L->deallocate(V.back()); + V.pop_back(); + } +} + +TEST(ScudoSecondaryTest, SecondaryThreadsRace) { + scudo::MapAllocator *L = new scudo::MapAllocator; + L->init(nullptr); + std::thread Threads[10]; + for (scudo::uptr I = 0; I < 10; I++) + Threads[I] = std::thread(performAllocations, L); + { + std::unique_lock<std::mutex> Lock(Mutex); + Ready = true; + Cv.notify_all(); + } + for (auto &T : Threads) + T.join(); + L->printStats(); +} diff --git a/lib/scudo/standalone/tests/size_class_map_test.cc b/lib/scudo/standalone/tests/size_class_map_test.cc new file mode 100644 index 000000000..d857aa4b2 --- /dev/null +++ b/lib/scudo/standalone/tests/size_class_map_test.cc @@ -0,0 +1,38 @@ +//===-- size_class_map_test.cc ----------------------------------*- 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 "scudo/standalone/size_class_map.h" +#include "gtest/gtest.h" + +template <class SizeClassMap> void testSizeClassMap() { + typedef SizeClassMap SCMap; + SCMap::print(); + SCMap::validate(); +} + +TEST(ScudoSizeClassMapTest, DefaultSizeClassMap) { + testSizeClassMap<scudo::DefaultSizeClassMap>(); +} + +TEST(ScudoSizeClassMapTest, SvelteSizeClassMap) { + testSizeClassMap<scudo::SvelteSizeClassMap>(); +} + +TEST(ScudoSizeClassMapTest, AndroidSizeClassMap) { + testSizeClassMap<scudo::AndroidSizeClassMap>(); +} + +TEST(ScudoSizeClassMapTest, OneClassSizeClassMap) { + testSizeClassMap<scudo::SizeClassMap<1, 5, 5, 5, 0, 0>>(); +} + +#if SCUDO_CAN_USE_PRIMARY64 +TEST(ScudoSizeClassMapTest, LargeMaxSizeClassMap) { + testSizeClassMap<scudo::SizeClassMap<3, 4, 8, 63, 128, 16>>(); +} +#endif diff --git a/lib/scudo/standalone/tests/stats_test.cc b/lib/scudo/standalone/tests/stats_test.cc new file mode 100644 index 000000000..9ed105d3d --- /dev/null +++ b/lib/scudo/standalone/tests/stats_test.cc @@ -0,0 +1,45 @@ +//===-- stats_test.cc -------------------------------------------*- 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 "scudo/standalone/stats.h" +#include "gtest/gtest.h" + +TEST(ScudoStatsTest, LocalStats) { + scudo::LocalStats LStats; + LStats.init(); + for (scudo::uptr I = 0; I < scudo::StatCount; I++) + EXPECT_EQ(LStats.get(static_cast<scudo::StatType>(I)), 0U); + LStats.add(scudo::StatAllocated, 4096U); + EXPECT_EQ(LStats.get(scudo::StatAllocated), 4096U); + LStats.sub(scudo::StatAllocated, 4096U); + EXPECT_EQ(LStats.get(scudo::StatAllocated), 0U); + LStats.set(scudo::StatAllocated, 4096U); + EXPECT_EQ(LStats.get(scudo::StatAllocated), 4096U); +} + +TEST(ScudoStatsTest, GlobalStats) { + scudo::GlobalStats GStats; + GStats.init(); + scudo::uptr Counters[scudo::StatCount] = {}; + GStats.get(Counters); + for (scudo::uptr I = 0; I < scudo::StatCount; I++) + EXPECT_EQ(Counters[I], 0U); + scudo::LocalStats LStats; + LStats.init(); + GStats.link(&LStats); + for (scudo::uptr I = 0; I < scudo::StatCount; I++) + LStats.add(static_cast<scudo::StatType>(I), 4096U); + GStats.get(Counters); + for (scudo::uptr I = 0; I < scudo::StatCount; I++) + EXPECT_EQ(Counters[I], 4096U); + // Unlinking the local stats move numbers to the global stats. + GStats.unlink(&LStats); + GStats.get(Counters); + for (scudo::uptr I = 0; I < scudo::StatCount; I++) + EXPECT_EQ(Counters[I], 4096U); +} diff --git a/lib/scudo/standalone/tests/strings_test.cc b/lib/scudo/standalone/tests/strings_test.cc new file mode 100644 index 000000000..31e59c403 --- /dev/null +++ b/lib/scudo/standalone/tests/strings_test.cc @@ -0,0 +1,98 @@ +//===-- strings_test.cc -----------------------------------------*- 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 "scudo/standalone/string_utils.h" +#include "gtest/gtest.h" + +#include <limits.h> + +TEST(ScudoStringsTest, Basic) { + scudo::ScopedString Str(128); + Str.append("a%db%zdc%ue%zuf%xh%zxq%pe%sr", static_cast<int>(-1), + static_cast<scudo::uptr>(-2), static_cast<unsigned>(-4), + static_cast<scudo::uptr>(5), static_cast<unsigned>(10), + static_cast<scudo::uptr>(11), reinterpret_cast<void *>(0x123), + "_string_"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + + std::string expectedString = "a-1b-2c4294967292e5fahbq0x"; + expectedString += std::string(SCUDO_POINTER_FORMAT_LENGTH - 3, '0'); + expectedString += "123e_string_r"; + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ(expectedString.c_str(), Str.data()); +} + +TEST(ScudoStringsTest, Precision) { + scudo::ScopedString Str(128); + Str.append("%.*s", 3, "12345"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ("123", Str.data()); + Str.clear(); + Str.append("%.*s", 6, "12345"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ("12345", Str.data()); + Str.clear(); + Str.append("%-6s", "12345"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ("12345 ", Str.data()); +} + +static void fillString(scudo::ScopedString &Str, scudo::uptr Size) { + for (scudo::uptr I = 0; I < Size; I++) + Str.append("A"); +} + +TEST(ScudoStringTest, PotentialOverflows) { + // Use a ScopedString that spans a page, and attempt to write past the end + // of it with variations of append. The expectation is for nothing to crash. + const scudo::uptr PageSize = scudo::getPageSizeCached(); + scudo::ScopedString Str(PageSize); + Str.clear(); + fillString(Str, 2 * PageSize); + Str.clear(); + fillString(Str, PageSize - 64); + Str.append("%-128s", "12345"); + Str.clear(); + fillString(Str, PageSize - 16); + Str.append("%024x", 12345); + Str.clear(); + fillString(Str, PageSize - 16); + Str.append("EEEEEEEEEEEEEEEEEEEEEEEE"); +} + +template <typename T> +static void testAgainstLibc(const char *Format, T Arg1, T Arg2) { + scudo::ScopedString Str(128); + Str.append(Format, Arg1, Arg2); + char Buffer[128]; + snprintf(Buffer, sizeof(Buffer), Format, Arg1, Arg2); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ(Buffer, Str.data()); +} + +TEST(ScudoStringsTest, MinMax) { + testAgainstLibc<int>("%d-%d", INT_MIN, INT_MAX); + testAgainstLibc<unsigned>("%u-%u", 0, UINT_MAX); + testAgainstLibc<unsigned>("%x-%x", 0, UINT_MAX); + testAgainstLibc<long>("%zd-%zd", LONG_MIN, LONG_MAX); + testAgainstLibc<unsigned long>("%zu-%zu", 0, ULONG_MAX); + testAgainstLibc<unsigned long>("%zx-%zx", 0, ULONG_MAX); +} + +TEST(ScudoStringsTest, Padding) { + testAgainstLibc<int>("%3d - %3d", 1, 0); + testAgainstLibc<int>("%3d - %3d", -1, 123); + testAgainstLibc<int>("%3d - %3d", -1, -123); + testAgainstLibc<int>("%3d - %3d", 12, 1234); + testAgainstLibc<int>("%3d - %3d", -12, -1234); + testAgainstLibc<int>("%03d - %03d", 1, 0); + testAgainstLibc<int>("%03d - %03d", -1, 123); + testAgainstLibc<int>("%03d - %03d", -1, -123); + testAgainstLibc<int>("%03d - %03d", 12, 1234); + testAgainstLibc<int>("%03d - %03d", -12, -1234); +} diff --git a/lib/scudo/standalone/tests/vector_test.cc b/lib/scudo/standalone/tests/vector_test.cc new file mode 100644 index 000000000..ebfcc43cc --- /dev/null +++ b/lib/scudo/standalone/tests/vector_test.cc @@ -0,0 +1,43 @@ +//===-- vector_test.cc ------------------------------------------*- 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 "vector.h" + +#include "gtest/gtest.h" + +TEST(ScudoVectorTest, Basic) { + scudo::Vector<int> V; + EXPECT_EQ(V.size(), 0U); + V.push_back(42); + EXPECT_EQ(V.size(), 1U); + EXPECT_EQ(V[0], 42); + V.push_back(43); + EXPECT_EQ(V.size(), 2U); + EXPECT_EQ(V[0], 42); + EXPECT_EQ(V[1], 43); +} + +TEST(ScudoVectorTest, Stride) { + scudo::Vector<int> V; + for (int i = 0; i < 1000; i++) { + V.push_back(i); + EXPECT_EQ(V.size(), i + 1U); + EXPECT_EQ(V[i], i); + } + for (int i = 0; i < 1000; i++) + EXPECT_EQ(V[i], i); +} + +TEST(ScudoVectorTest, ResizeReduction) { + scudo::Vector<int> V; + V.push_back(0); + V.push_back(0); + EXPECT_EQ(V.size(), 2U); + V.resize(1); + EXPECT_EQ(V.size(), 1U); +} |