diff options
Diffstat (limited to 'lib/sanitizer_common/tests')
16 files changed, 1475 insertions, 88 deletions
diff --git a/lib/sanitizer_common/tests/CMakeLists.txt b/lib/sanitizer_common/tests/CMakeLists.txt index f83a89cbe37c7..25e57507ad148 100644 --- a/lib/sanitizer_common/tests/CMakeLists.txt +++ b/lib/sanitizer_common/tests/CMakeLists.txt @@ -2,15 +2,20 @@ include(CompilerRTCompile) set(SANITIZER_UNITTESTS sanitizer_allocator_test.cc + sanitizer_atomic_test.cc sanitizer_common_test.cc sanitizer_flags_test.cc sanitizer_libc_test.cc + sanitizer_linux_test.cc sanitizer_list_test.cc sanitizer_mutex_test.cc sanitizer_printf_test.cc sanitizer_scanf_interceptor_test.cc sanitizer_stackdepot_test.cc + sanitizer_stacktrace_test.cc + sanitizer_stoptheworld_test.cc sanitizer_test_main.cc + sanitizer_thread_registry_test.cc ) set(SANITIZER_TEST_HEADERS) @@ -18,6 +23,18 @@ foreach(header ${SANITIZER_HEADERS}) list(APPEND SANITIZER_TEST_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../${header}) endforeach() +set(SANITIZER_TEST_CFLAGS_COMMON + ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common + -DGTEST_HAS_RTTI=0 + -O2 -g -fno-rtti + -Wall -Werror -Werror=sign-compare) + +set(SANITIZER_TEST_LINK_FLAGS_COMMON + -lstdc++ -ldl) + include_directories(..) include_directories(../..) @@ -49,18 +66,12 @@ macro(add_sanitizer_tests_for_arch arch) get_target_flags_for_arch(${arch} TARGET_FLAGS) set(SANITIZER_TEST_SOURCES ${SANITIZER_UNITTESTS} ${COMPILER_RT_GTEST_SOURCE}) - set(SANITIZER_TEST_CFLAGS ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} - -I${COMPILER_RT_SOURCE_DIR}/include - -I${COMPILER_RT_SOURCE_DIR}/lib - -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common - -O2 -g ${TARGET_FLAGS}) - set(SANITIZER_TEST_LINK_FLAGS -lstdc++ -lpthread ${TARGET_FLAGS}) set(SANITIZER_TEST_OBJECTS) foreach(source ${SANITIZER_TEST_SOURCES}) get_filename_component(basename ${source} NAME) set(output_obj "${basename}.${arch}.o") clang_compile(${output_obj} ${source} - CFLAGS ${SANITIZER_TEST_CFLAGS} + CFLAGS ${SANITIZER_TEST_CFLAGS_COMMON} ${TARGET_FLAGS} DEPS gtest ${SANITIZER_RUNTIME_LIBRARIES} ${SANITIZER_TEST_HEADERS}) list(APPEND SANITIZER_TEST_OBJECTS ${output_obj}) @@ -73,7 +84,8 @@ macro(add_sanitizer_tests_for_arch arch) OBJECTS ${SANITIZER_TEST_OBJECTS} ${SANITIZER_COMMON_LIB_NAME} DEPS ${SANITIZER_TEST_OBJECTS} ${SANITIZER_COMMON_LIB} - LINK_FLAGS ${SANITIZER_TEST_LINK_FLAGS}) + LINK_FLAGS ${SANITIZER_TEST_LINK_FLAGS_COMMON} + -lpthread ${TARGET_FLAGS}) endmacro() if(COMPILER_RT_CAN_EXECUTE_TESTS) @@ -85,11 +97,13 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS) else() if(CAN_TARGET_x86_64) add_sanitizer_common_lib("RTSanitizerCommon.test.x86_64" - $<TARGET_OBJECTS:RTSanitizerCommon.x86_64>) + $<TARGET_OBJECTS:RTSanitizerCommon.x86_64> + $<TARGET_OBJECTS:RTSanitizerCommonLibc.x86_64>) endif() if(CAN_TARGET_i386) add_sanitizer_common_lib("RTSanitizerCommon.test.i386" - $<TARGET_OBJECTS:RTSanitizerCommon.i386>) + $<TARGET_OBJECTS:RTSanitizerCommon.i386> + $<TARGET_OBJECTS:RTSanitizerCommonLibc.i386>) endif() endif() if(CAN_TARGET_x86_64) @@ -118,21 +132,14 @@ if(ANDROID) add_executable(SanitizerTest ${SANITIZER_UNITTESTS} ${COMPILER_RT_GTEST_SOURCE} - $<TARGET_OBJECTS:RTSanitizerCommon.arm.android> - ) + $<TARGET_OBJECTS:RTSanitizerCommon.arm.android>) set_target_compile_flags(SanitizerTest ${SANITIZER_COMMON_CFLAGS} - ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} - -I${COMPILER_RT_SOURCE_DIR}/include - -I${COMPILER_RT_SOURCE_DIR}/lib - -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common - -O2 -g - ) + ${SANITIZER_TEST_CFLAGS_COMMON}) # Setup correct output directory and link flags. - get_unittest_directory(OUTPUT_DIR) set_target_properties(SanitizerTest PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) - set_target_link_flags(SanitizerTest ${SANITIZER_TEST_LINK_FLAGS}) + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_target_link_flags(SanitizerTest ${SANITIZER_TEST_LINK_FLAGS_COMMON}) # Add unit test to test suite. add_dependencies(SanitizerUnitTests SanitizerTest) endif() diff --git a/lib/sanitizer_common/tests/lit.cfg b/lib/sanitizer_common/tests/lit.cfg index d774753985ac9..303d56c910790 100644 --- a/lib/sanitizer_common/tests/lit.cfg +++ b/lib/sanitizer_common/tests/lit.cfg @@ -11,9 +11,8 @@ def get_required_attr(config, attr_name): return attr_value # Setup attributes common for all compiler-rt projects. -llvm_src_root = get_required_attr(config, 'llvm_src_root') -compiler_rt_lit_unit_cfg = os.path.join(llvm_src_root, "projects", - "compiler-rt", "lib", +compiler_rt_src_root = get_required_attr(config, 'compiler_rt_src_root') +compiler_rt_lit_unit_cfg = os.path.join(compiler_rt_src_root, "lib", "lit.common.unit.cfg") lit.load_config(config, compiler_rt_lit_unit_cfg) diff --git a/lib/sanitizer_common/tests/lit.site.cfg.in b/lib/sanitizer_common/tests/lit.site.cfg.in index bb9a28d6a6cb7..50485aa16ec2f 100644 --- a/lib/sanitizer_common/tests/lit.site.cfg.in +++ b/lib/sanitizer_common/tests/lit.site.cfg.in @@ -1,9 +1,16 @@ ## Autogenerated by LLVM/Clang configuration. # Do not edit! -config.build_type = "@CMAKE_BUILD_TYPE@" config.llvm_obj_root = "@LLVM_BINARY_DIR@" config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.compiler_rt_src_root = "@COMPILER_RT_SOURCE_DIR@" +config.llvm_build_mode = "@LLVM_BUILD_MODE@" + +try: + config.llvm_build_mode = config.llvm_build_mode % lit.params +except KeyError,e: + key, = e.args + lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) # Let the main config do the real work. lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/lib/sanitizer_common/tests/sanitizer_allocator_test.cc b/lib/sanitizer_common/tests/sanitizer_allocator_test.cc index d67f4636ef4f7..de949ca7defec 100644 --- a/lib/sanitizer_common/tests/sanitizer_allocator_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_allocator_test.cc @@ -22,6 +22,7 @@ #include <pthread.h> #include <algorithm> #include <vector> +#include <set> // Too slow for debug build #if TSAN_DEBUG == 0 @@ -40,8 +41,16 @@ typedef SizeClassAllocator64< static const u64 kAddressSpaceSize = 1ULL << 32; #endif +static const uptr kRegionSizeLog = FIRST_32_SECOND_64(20, 24); +static const uptr kFlatByteMapSize = kAddressSpaceSize >> kRegionSizeLog; + typedef SizeClassAllocator32< - 0, kAddressSpaceSize, 16, CompactSizeClassMap> Allocator32Compact; + 0, kAddressSpaceSize, + /*kMetadataSize*/16, + CompactSizeClassMap, + kRegionSizeLog, + FlatByteMap<kFlatByteMapSize> > + Allocator32Compact; template <class SizeClassMap> void TestSizeClassMap() { @@ -63,7 +72,8 @@ void TestSizeClassAllocator() { Allocator *a = new Allocator; a->Init(); SizeClassAllocatorLocalCache<Allocator> cache; - cache.Init(); + memset(&cache, 0, sizeof(cache)); + cache.Init(0); static const uptr sizes[] = {1, 16, 30, 40, 100, 1000, 10000, 50000, 60000, 100000, 120000, 300000, 500000, 1000000, 2000000}; @@ -77,7 +87,7 @@ void TestSizeClassAllocator() { uptr size = sizes[s]; if (!a->CanAllocate(size, 1)) continue; // printf("s = %ld\n", size); - uptr n_iter = std::max((uptr)6, 10000000 / size); + uptr n_iter = std::max((uptr)6, 8000000 / size); // fprintf(stderr, "size: %ld iter: %ld\n", size, n_iter); for (uptr i = 0; i < n_iter; i++) { uptr class_id0 = Allocator::SizeClassMapT::ClassID(size); @@ -114,6 +124,12 @@ void TestSizeClassAllocator() { CHECK_EQ(last_total_allocated, total_allocated); } + // Check that GetBlockBegin never crashes. + for (uptr x = 0, step = kAddressSpaceSize / 100000; + x < kAddressSpaceSize - step; x += step) + if (a->PointerIsMine(reinterpret_cast<void *>(x))) + Ident(a->GetBlockBegin(reinterpret_cast<void *>(x))); + a->TestOnlyUnmap(); delete a; } @@ -137,25 +153,28 @@ void SizeClassAllocatorMetadataStress() { Allocator *a = new Allocator; a->Init(); SizeClassAllocatorLocalCache<Allocator> cache; - cache.Init(); - static volatile void *sink; + memset(&cache, 0, sizeof(cache)); + cache.Init(0); - const uptr kNumAllocs = 10000; + const uptr kNumAllocs = 1 << 13; void *allocated[kNumAllocs]; + void *meta[kNumAllocs]; for (uptr i = 0; i < kNumAllocs; i++) { void *x = cache.Allocate(a, 1 + i % 50); allocated[i] = x; + meta[i] = a->GetMetaData(x); } // Get Metadata kNumAllocs^2 times. for (uptr i = 0; i < kNumAllocs * kNumAllocs; i++) { - sink = a->GetMetaData(allocated[i % kNumAllocs]); + uptr idx = i % kNumAllocs; + void *m = a->GetMetaData(allocated[idx]); + EXPECT_EQ(m, meta[idx]); } for (uptr i = 0; i < kNumAllocs; i++) { cache.Deallocate(a, 1 + i % 50, allocated[i]); } a->TestOnlyUnmap(); - (void)sink; delete a; } @@ -167,11 +186,47 @@ TEST(SanitizerCommon, SizeClassAllocator64MetadataStress) { TEST(SanitizerCommon, SizeClassAllocator64CompactMetadataStress) { SizeClassAllocatorMetadataStress<Allocator64Compact>(); } -#endif +#endif // SANITIZER_WORDSIZE == 64 TEST(SanitizerCommon, SizeClassAllocator32CompactMetadataStress) { SizeClassAllocatorMetadataStress<Allocator32Compact>(); } +template <class Allocator> +void SizeClassAllocatorGetBlockBeginStress() { + Allocator *a = new Allocator; + a->Init(); + SizeClassAllocatorLocalCache<Allocator> cache; + memset(&cache, 0, sizeof(cache)); + cache.Init(0); + + uptr max_size_class = Allocator::kNumClasses - 1; + uptr size = Allocator::SizeClassMapT::Size(max_size_class); + u64 G8 = 1ULL << 33; + // Make sure we correctly compute GetBlockBegin() w/o overflow. + for (size_t i = 0; i <= G8 / size; i++) { + void *x = cache.Allocate(a, max_size_class); + void *beg = a->GetBlockBegin(x); + // if ((i & (i - 1)) == 0) + // fprintf(stderr, "[%zd] %p %p\n", i, x, beg); + EXPECT_EQ(x, beg); + } + + a->TestOnlyUnmap(); + delete a; +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64GetBlockBegin) { + SizeClassAllocatorGetBlockBeginStress<Allocator64>(); +} +TEST(SanitizerCommon, SizeClassAllocator64CompactGetBlockBegin) { + SizeClassAllocatorGetBlockBeginStress<Allocator64Compact>(); +} +TEST(SanitizerCommon, SizeClassAllocator32CompactGetBlockBegin) { + SizeClassAllocatorGetBlockBeginStress<Allocator32Compact>(); +} +#endif // SANITIZER_WORDSIZE == 64 + struct TestMapUnmapCallback { static int map_count, unmap_count; void OnMap(uptr p, uptr size) const { map_count++; } @@ -191,8 +246,11 @@ TEST(SanitizerCommon, SizeClassAllocator64MapUnmapCallback) { a->Init(); EXPECT_EQ(TestMapUnmapCallback::map_count, 1); // Allocator state. SizeClassAllocatorLocalCache<Allocator64WithCallBack> cache; - cache.Init(); - a->AllocateBatch(&cache, 64); + memset(&cache, 0, sizeof(cache)); + cache.Init(0); + AllocatorStats stats; + stats.Init(); + a->AllocateBatch(&stats, &cache, 32); EXPECT_EQ(TestMapUnmapCallback::map_count, 3); // State + alloc + metadata. a->TestOnlyUnmap(); EXPECT_EQ(TestMapUnmapCallback::unmap_count, 1); // The whole thing. @@ -204,17 +262,25 @@ TEST(SanitizerCommon, SizeClassAllocator32MapUnmapCallback) { TestMapUnmapCallback::map_count = 0; TestMapUnmapCallback::unmap_count = 0; typedef SizeClassAllocator32< - 0, kAddressSpaceSize, 16, CompactSizeClassMap, - TestMapUnmapCallback> Allocator32WithCallBack; + 0, kAddressSpaceSize, + /*kMetadataSize*/16, + CompactSizeClassMap, + kRegionSizeLog, + FlatByteMap<kFlatByteMapSize>, + TestMapUnmapCallback> + Allocator32WithCallBack; Allocator32WithCallBack *a = new Allocator32WithCallBack; a->Init(); - EXPECT_EQ(TestMapUnmapCallback::map_count, 1); // Allocator state. + EXPECT_EQ(TestMapUnmapCallback::map_count, 0); SizeClassAllocatorLocalCache<Allocator32WithCallBack> cache; - cache.Init(); - a->AllocateBatch(&cache, 64); - EXPECT_EQ(TestMapUnmapCallback::map_count, 2); // alloc. + memset(&cache, 0, sizeof(cache)); + cache.Init(0); + AllocatorStats stats; + stats.Init(); + a->AllocateBatch(&stats, &cache, 32); + EXPECT_EQ(TestMapUnmapCallback::map_count, 1); a->TestOnlyUnmap(); - EXPECT_EQ(TestMapUnmapCallback::unmap_count, 2); // The whole thing + alloc. + EXPECT_EQ(TestMapUnmapCallback::unmap_count, 1); delete a; // fprintf(stderr, "Map: %d Unmap: %d\n", // TestMapUnmapCallback::map_count, @@ -226,9 +292,11 @@ TEST(SanitizerCommon, LargeMmapAllocatorMapUnmapCallback) { TestMapUnmapCallback::unmap_count = 0; LargeMmapAllocator<TestMapUnmapCallback> a; a.Init(); - void *x = a.Allocate(1 << 20, 1); + AllocatorStats stats; + stats.Init(); + void *x = a.Allocate(&stats, 1 << 20, 1); EXPECT_EQ(TestMapUnmapCallback::map_count, 1); - a.Deallocate(x); + a.Deallocate(&stats, x); EXPECT_EQ(TestMapUnmapCallback::unmap_count, 1); } @@ -237,9 +305,12 @@ void FailInAssertionOnOOM() { Allocator a; a.Init(); SizeClassAllocatorLocalCache<Allocator> cache; - cache.Init(); + memset(&cache, 0, sizeof(cache)); + cache.Init(0); + AllocatorStats stats; + stats.Init(); for (int i = 0; i < 1000000; i++) { - a.AllocateBatch(&cache, 64); + a.AllocateBatch(&stats, &cache, 52); } a.TestOnlyUnmap(); @@ -254,13 +325,15 @@ TEST(SanitizerCommon, SizeClassAllocator64Overflow) { TEST(SanitizerCommon, LargeMmapAllocator) { LargeMmapAllocator<> a; a.Init(); + AllocatorStats stats; + stats.Init(); static const int kNumAllocs = 1000; char *allocated[kNumAllocs]; static const uptr size = 4000; // Allocate some. for (int i = 0; i < kNumAllocs; i++) { - allocated[i] = (char *)a.Allocate(size, 1); + allocated[i] = (char *)a.Allocate(&stats, size, 1); CHECK(a.PointerIsMine(allocated[i])); } // Deallocate all. @@ -268,14 +341,14 @@ TEST(SanitizerCommon, LargeMmapAllocator) { for (int i = 0; i < kNumAllocs; i++) { char *p = allocated[i]; CHECK(a.PointerIsMine(p)); - a.Deallocate(p); + a.Deallocate(&stats, p); } // Check that non left. CHECK_EQ(a.TotalMemoryUsed(), 0); // Allocate some more, also add metadata. for (int i = 0; i < kNumAllocs; i++) { - char *x = (char *)a.Allocate(size, 1); + char *x = (char *)a.Allocate(&stats, size, 1); CHECK_GE(a.GetActuallyAllocatedSize(x), size); uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(x)); *meta = i; @@ -294,7 +367,7 @@ TEST(SanitizerCommon, LargeMmapAllocator) { uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(p)); CHECK_EQ(*meta, idx); CHECK(a.PointerIsMine(p)); - a.Deallocate(p); + a.Deallocate(&stats, p); } CHECK_EQ(a.TotalMemoryUsed(), 0); @@ -304,7 +377,7 @@ TEST(SanitizerCommon, LargeMmapAllocator) { const uptr kNumAlignedAllocs = 100; for (uptr i = 0; i < kNumAlignedAllocs; i++) { uptr size = ((i % 10) + 1) * 4096; - char *p = allocated[i] = (char *)a.Allocate(size, alignment); + char *p = allocated[i] = (char *)a.Allocate(&stats, size, alignment); CHECK_EQ(p, a.GetBlockBegin(p)); CHECK_EQ(p, a.GetBlockBegin(p + size - 1)); CHECK_EQ(p, a.GetBlockBegin(p + size / 2)); @@ -312,9 +385,17 @@ TEST(SanitizerCommon, LargeMmapAllocator) { p[0] = p[size - 1] = 0; } for (uptr i = 0; i < kNumAlignedAllocs; i++) { - a.Deallocate(allocated[i]); + a.Deallocate(&stats, allocated[i]); } } + + // Regression test for boundary condition in GetBlockBegin(). + uptr page_size = GetPageSizeCached(); + char *p = (char *)a.Allocate(&stats, page_size, 1); + CHECK_EQ(p, a.GetBlockBegin(p)); + CHECK_EQ(p, (char *)a.GetBlockBegin(p + page_size - 1)); + CHECK_NE(p, (char *)a.GetBlockBegin(p + page_size)); + a.Deallocate(&stats, p); } template @@ -327,7 +408,8 @@ void TestCombinedAllocator() { a->Init(); AllocatorCache cache; - cache.Init(); + memset(&cache, 0, sizeof(cache)); + a->InitCache(&cache); EXPECT_EQ(a->Allocate(&cache, -1, 1), (void*)0); EXPECT_EQ(a->Allocate(&cache, -1, 1024), (void*)0); @@ -363,6 +445,7 @@ void TestCombinedAllocator() { allocated.clear(); a->SwallowCache(&cache); } + a->DestroyCache(&cache); a->TestOnlyUnmap(); } @@ -388,14 +471,13 @@ TEST(SanitizerCommon, CombinedAllocator32Compact) { template <class AllocatorCache> void TestSizeClassAllocatorLocalCache() { - static AllocatorCache static_allocator_cache; - static_allocator_cache.Init(); AllocatorCache cache; typedef typename AllocatorCache::Allocator Allocator; Allocator *a = new Allocator(); a->Init(); - cache.Init(); + memset(&cache, 0, sizeof(cache)); + cache.Init(0); const uptr kNumAllocs = 10000; const int kNumIter = 100; @@ -466,6 +548,42 @@ TEST(SanitizerCommon, AllocatorLeakTest) { a.TestOnlyUnmap(); } + +// Struct which is allocated to pass info to new threads. The new thread frees +// it. +struct NewThreadParams { + AllocatorCache *thread_cache; + AllocatorCache::Allocator *allocator; + uptr class_id; +}; + +// Called in a new thread. Just frees its argument. +static void *DeallocNewThreadWorker(void *arg) { + NewThreadParams *params = reinterpret_cast<NewThreadParams*>(arg); + params->thread_cache->Deallocate(params->allocator, params->class_id, params); + return NULL; +} + +// The allocator cache is supposed to be POD and zero initialized. We should be +// able to call Deallocate on a zeroed cache, and it will self-initialize. +TEST(Allocator, AllocatorCacheDeallocNewThread) { + AllocatorCache::Allocator allocator; + allocator.Init(); + AllocatorCache main_cache; + AllocatorCache child_cache; + memset(&main_cache, 0, sizeof(main_cache)); + memset(&child_cache, 0, sizeof(child_cache)); + + uptr class_id = DefaultSizeClassMap::ClassID(sizeof(NewThreadParams)); + NewThreadParams *params = reinterpret_cast<NewThreadParams*>( + main_cache.Allocate(&allocator, class_id)); + params->thread_cache = &child_cache; + params->allocator = &allocator; + params->class_id = class_id; + pthread_t t; + EXPECT_EQ(0, pthread_create(&t, 0, DeallocNewThreadWorker, params)); + EXPECT_EQ(0, pthread_join(t, 0)); +} #endif TEST(Allocator, Basic) { @@ -507,4 +625,122 @@ TEST(Allocator, ScopedBuffer) { } } +class IterationTestCallback { + public: + explicit IterationTestCallback(std::set<void *> *chunks) + : chunks_(chunks) {} + void operator()(void *chunk) const { + chunks_->insert(chunk); + } + private: + std::set<void *> *chunks_; +}; + +template <class Allocator> +void TestSizeClassAllocatorIteration() { + Allocator *a = new Allocator; + a->Init(); + SizeClassAllocatorLocalCache<Allocator> cache; + memset(&cache, 0, sizeof(cache)); + cache.Init(0); + + static const uptr sizes[] = {1, 16, 30, 40, 100, 1000, 10000, + 50000, 60000, 100000, 120000, 300000, 500000, 1000000, 2000000}; + + std::vector<void *> allocated; + + // Allocate a bunch of chunks. + for (uptr s = 0; s < ARRAY_SIZE(sizes); s++) { + uptr size = sizes[s]; + if (!a->CanAllocate(size, 1)) continue; + // printf("s = %ld\n", size); + uptr n_iter = std::max((uptr)6, 80000 / size); + // fprintf(stderr, "size: %ld iter: %ld\n", size, n_iter); + for (uptr j = 0; j < n_iter; j++) { + uptr class_id0 = Allocator::SizeClassMapT::ClassID(size); + void *x = cache.Allocate(a, class_id0); + allocated.push_back(x); + } + } + + std::set<void *> reported_chunks; + IterationTestCallback callback(&reported_chunks); + a->ForceLock(); + a->ForEachChunk(callback); + a->ForceUnlock(); + + for (uptr i = 0; i < allocated.size(); i++) { + // Don't use EXPECT_NE. Reporting the first mismatch is enough. + ASSERT_NE(reported_chunks.find(allocated[i]), reported_chunks.end()); + } + + a->TestOnlyUnmap(); + delete a; +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64Iteration) { + TestSizeClassAllocatorIteration<Allocator64>(); +} +#endif + +TEST(SanitizerCommon, SizeClassAllocator32Iteration) { + TestSizeClassAllocatorIteration<Allocator32Compact>(); +} + +TEST(SanitizerCommon, LargeMmapAllocatorIteration) { + LargeMmapAllocator<> a; + a.Init(); + AllocatorStats stats; + stats.Init(); + + static const uptr kNumAllocs = 1000; + char *allocated[kNumAllocs]; + static const uptr size = 40; + // Allocate some. + for (uptr i = 0; i < kNumAllocs; i++) { + allocated[i] = (char *)a.Allocate(&stats, size, 1); + } + + std::set<void *> reported_chunks; + IterationTestCallback callback(&reported_chunks); + a.ForceLock(); + a.ForEachChunk(callback); + a.ForceUnlock(); + + for (uptr i = 0; i < kNumAllocs; i++) { + // Don't use EXPECT_NE. Reporting the first mismatch is enough. + ASSERT_NE(reported_chunks.find(allocated[i]), reported_chunks.end()); + } +} + +#if SANITIZER_WORDSIZE == 64 +// Regression test for out-of-memory condition in PopulateFreeList(). +TEST(SanitizerCommon, SizeClassAllocator64PopulateFreeListOOM) { + // In a world where regions are small and chunks are huge... + typedef SizeClassMap<63, 128, 16> SpecialSizeClassMap; + typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 0, + SpecialSizeClassMap> SpecialAllocator64; + const uptr kRegionSize = + kAllocatorSize / SpecialSizeClassMap::kNumClassesRounded; + SpecialAllocator64 *a = new SpecialAllocator64; + a->Init(); + SizeClassAllocatorLocalCache<SpecialAllocator64> cache; + memset(&cache, 0, sizeof(cache)); + cache.Init(0); + + // ...one man is on a mission to overflow a region with a series of + // successive allocations. + const uptr kClassID = 107; + const uptr kAllocationSize = DefaultSizeClassMap::Size(kClassID); + ASSERT_LT(2 * kAllocationSize, kRegionSize); + ASSERT_GT(3 * kAllocationSize, kRegionSize); + cache.Allocate(a, kClassID); + EXPECT_DEATH(cache.Allocate(a, kClassID) && cache.Allocate(a, kClassID), + "The process has exhausted"); + a->TestOnlyUnmap(); + delete a; +} +#endif + #endif // #if TSAN_DEBUG==0 diff --git a/lib/sanitizer_common/tests/sanitizer_atomic_test.cc b/lib/sanitizer_common/tests/sanitizer_atomic_test.cc new file mode 100644 index 0000000000000..a4a97c43e00f6 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_atomic_test.cc @@ -0,0 +1,55 @@ +//===-- sanitizer_atomic_test.cc ------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer/AddressSanitizer runtime. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_atomic.h" +#include "gtest/gtest.h" + +namespace __sanitizer { + +// Clang crashes while compiling this test for Android: +// http://llvm.org/bugs/show_bug.cgi?id=15587 +#if !SANITIZER_ANDROID +template<typename T> +void CheckAtomicCompareExchange() { + typedef typename T::Type Type; + { + Type old_val = 42; + Type new_val = 24; + Type var = old_val; + EXPECT_TRUE(atomic_compare_exchange_strong((T*)&var, &old_val, new_val, + memory_order_relaxed)); + EXPECT_FALSE(atomic_compare_exchange_strong((T*)&var, &old_val, new_val, + memory_order_relaxed)); + EXPECT_EQ(new_val, old_val); + } + { + Type old_val = 42; + Type new_val = 24; + Type var = old_val; + EXPECT_TRUE(atomic_compare_exchange_weak((T*)&var, &old_val, new_val, + memory_order_relaxed)); + EXPECT_FALSE(atomic_compare_exchange_weak((T*)&var, &old_val, new_val, + memory_order_relaxed)); + EXPECT_EQ(new_val, old_val); + } +} + +TEST(SanitizerCommon, AtomicCompareExchangeTest) { + CheckAtomicCompareExchange<atomic_uint8_t>(); + CheckAtomicCompareExchange<atomic_uint16_t>(); + CheckAtomicCompareExchange<atomic_uint32_t>(); + CheckAtomicCompareExchange<atomic_uint64_t>(); + CheckAtomicCompareExchange<atomic_uintptr_t>(); +} +#endif //!SANITIZER_ANDROID + +} // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_common_test.cc b/lib/sanitizer_common/tests/sanitizer_common_test.cc index 01d8b5a87c012..424c279d4adab 100644 --- a/lib/sanitizer_common/tests/sanitizer_common_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_common_test.cc @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_platform.h" #include "gtest/gtest.h" namespace __sanitizer { @@ -79,7 +80,7 @@ TEST(SanitizerCommon, MmapAlignedOrDie) { } } -#ifdef __linux__ +#if SANITIZER_LINUX TEST(SanitizerCommon, SanitizerSetThreadName) { const char *names[] = { "0123456789012", @@ -96,4 +97,65 @@ TEST(SanitizerCommon, SanitizerSetThreadName) { } #endif -} // namespace sanitizer +TEST(SanitizerCommon, InternalVector) { + InternalVector<uptr> vector(1); + for (uptr i = 0; i < 100; i++) { + EXPECT_EQ(i, vector.size()); + vector.push_back(i); + } + for (uptr i = 0; i < 100; i++) { + EXPECT_EQ(i, vector[i]); + } + for (int i = 99; i >= 0; i--) { + EXPECT_EQ((uptr)i, vector.back()); + vector.pop_back(); + EXPECT_EQ((uptr)i, vector.size()); + } +} + +void TestThreadInfo(bool main) { + uptr stk_addr = 0; + uptr stk_size = 0; + uptr tls_addr = 0; + uptr tls_size = 0; + GetThreadStackAndTls(main, &stk_addr, &stk_size, &tls_addr, &tls_size); + + int stack_var; + EXPECT_NE(stk_addr, (uptr)0); + EXPECT_NE(stk_size, (uptr)0); + EXPECT_GT((uptr)&stack_var, stk_addr); + EXPECT_LT((uptr)&stack_var, stk_addr + stk_size); + +#if SANITIZER_LINUX && defined(__x86_64__) + static __thread int thread_var; + EXPECT_NE(tls_addr, (uptr)0); + EXPECT_NE(tls_size, (uptr)0); + EXPECT_GT((uptr)&thread_var, tls_addr); + EXPECT_LT((uptr)&thread_var, tls_addr + tls_size); + + // Ensure that tls and stack do not intersect. + uptr tls_end = tls_addr + tls_size; + EXPECT_TRUE(tls_addr < stk_addr || tls_addr >= stk_addr + stk_size); + EXPECT_TRUE(tls_end < stk_addr || tls_end >= stk_addr + stk_size); + EXPECT_TRUE((tls_addr < stk_addr) == (tls_end < stk_addr)); +#endif +} + +static void *WorkerThread(void *arg) { + TestThreadInfo(false); + return 0; +} + +TEST(SanitizerCommon, ThreadStackTlsMain) { + InitTlsSize(); + TestThreadInfo(true); +} + +TEST(SanitizerCommon, ThreadStackTlsWorker) { + InitTlsSize(); + pthread_t t; + pthread_create(&t, 0, WorkerThread, 0); + pthread_join(t, 0); +} + +} // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_flags_test.cc b/lib/sanitizer_common/tests/sanitizer_flags_test.cc index c0589f4d2e900..cd3cac11bc807 100644 --- a/lib/sanitizer_common/tests/sanitizer_flags_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_flags_test.cc @@ -32,7 +32,7 @@ static void TestStrFlag(const char *start_value, const char *env, const char *final_value) { const char *flag = start_value; ParseFlag(env, &flag, kFlagName); - EXPECT_EQ(internal_strcmp(final_value, flag), 0); + EXPECT_EQ(0, internal_strcmp(final_value, flag)); } TEST(SanitizerCommon, BooleanFlags) { @@ -63,6 +63,24 @@ TEST(SanitizerCommon, StrFlags) { TestStrFlag("", "--flag_name='abc zxc'", "abc zxc"); TestStrFlag("", "--flag_name='abc zxcc'", "abc zxcc"); TestStrFlag("", "--flag_name=\"abc qwe\" asd", "abc qwe"); + TestStrFlag("", "other_flag_name=zzz", ""); +} + +static void TestTwoFlags(const char *env, bool expected_flag1, + const char *expected_flag2) { + bool flag1 = !expected_flag1; + const char *flag2 = ""; + ParseFlag(env, &flag1, "flag1"); + ParseFlag(env, &flag2, "flag2"); + EXPECT_EQ(expected_flag1, flag1); + EXPECT_EQ(0, internal_strcmp(flag2, expected_flag2)); +} + +TEST(SanitizerCommon, MultipleFlags) { + TestTwoFlags("flag1=1 flag2='zzz'", true, "zzz"); + TestTwoFlags("flag2='qxx' flag1=0", false, "qxx"); + TestTwoFlags("flag1=false:flag2='zzz'", false, "zzz"); + TestTwoFlags("flag2=qxx:flag1=yes", true, "qxx"); } } // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_libc_test.cc b/lib/sanitizer_common/tests/sanitizer_libc_test.cc index b9d8414e0cbf9..39c29d3573274 100644 --- a/lib/sanitizer_common/tests/sanitizer_libc_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_libc_test.cc @@ -9,9 +9,18 @@ // Tests for sanitizer_libc.h. //===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_platform.h" #include "gtest/gtest.h" +#if SANITIZER_LINUX || SANITIZER_MAC +# define SANITIZER_TEST_HAS_STAT_H 1 +# include <sys/stat.h> +#else +# define SANITIZER_TEST_HAS_STAT_H 0 +#endif + // A regression test for internal_memmove() implementation. TEST(SanitizerCommon, InternalMemmoveRegression) { char src[] = "Hello World"; @@ -40,3 +49,69 @@ TEST(SanitizerCommon, mem_is_zero) { } delete [] x; } + +struct stat_and_more { + struct stat st; + unsigned char z; +}; + +TEST(SanitizerCommon, FileOps) { + const char *str1 = "qwerty"; + uptr len1 = internal_strlen(str1); + const char *str2 = "zxcv"; + uptr len2 = internal_strlen(str2); + + u32 uid = GetUid(); + char temp_filename[128]; +#if SANITIZER_ANDROID + // I don't know a way to query temp directory location on Android without + // going through Java interfaces. The code below is not ideal, but should + // work. May require "adb root", but it is needed for almost any use of ASan + // on Android already. + internal_snprintf(temp_filename, sizeof(temp_filename), + "%s/sanitizer_common.tmp.%d", + GetEnv("EXTERNAL_STORAGE"), uid); +#else + internal_snprintf(temp_filename, sizeof(temp_filename), + "/tmp/sanitizer_common.tmp.%d", uid); +#endif + uptr openrv = OpenFile(temp_filename, true); + EXPECT_FALSE(internal_iserror(openrv)); + fd_t fd = openrv; + EXPECT_EQ(len1, internal_write(fd, str1, len1)); + EXPECT_EQ(len2, internal_write(fd, str2, len2)); + internal_close(fd); + + openrv = OpenFile(temp_filename, false); + EXPECT_FALSE(internal_iserror(openrv)); + fd = openrv; + uptr fsize = internal_filesize(fd); + EXPECT_EQ(len1 + len2, fsize); + +#if SANITIZER_TEST_HAS_STAT_H + struct stat st1, st2, st3; + EXPECT_EQ(0u, internal_stat(temp_filename, &st1)); + EXPECT_EQ(0u, internal_lstat(temp_filename, &st2)); + EXPECT_EQ(0u, internal_fstat(fd, &st3)); + EXPECT_EQ(fsize, (uptr)st3.st_size); + + // Verify that internal_fstat does not write beyond the end of the supplied + // buffer. + struct stat_and_more sam; + memset(&sam, 0xAB, sizeof(sam)); + EXPECT_EQ(0u, internal_fstat(fd, &sam.st)); + EXPECT_EQ(0xAB, sam.z); + EXPECT_NE(0xAB, sam.st.st_size); + EXPECT_NE(0, sam.st.st_size); +#endif + + char buf[64] = {}; + EXPECT_EQ(len1, internal_read(fd, buf, len1)); + EXPECT_EQ(0, internal_memcmp(buf, str1, len1)); + EXPECT_EQ((char)0, buf[len1 + 1]); + internal_memset(buf, 0, len1); + EXPECT_EQ(len2, internal_read(fd, buf, len2)); + EXPECT_EQ(0, internal_memcmp(buf, str2, len2)); + internal_close(fd); +} + diff --git a/lib/sanitizer_common/tests/sanitizer_linux_test.cc b/lib/sanitizer_common/tests/sanitizer_linux_test.cc new file mode 100644 index 0000000000000..b18aeb030acff --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_linux_test.cc @@ -0,0 +1,253 @@ +//===-- sanitizer_linux_test.cc -------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tests for sanitizer_linux.h +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_platform.h" +#if SANITIZER_LINUX + +#include "sanitizer_common/sanitizer_linux.h" + +#include "sanitizer_common/sanitizer_common.h" +#include "gtest/gtest.h" + +#ifdef __x86_64__ +#include <asm/prctl.h> +#endif +#include <pthread.h> +#include <sched.h> +#include <stdlib.h> + +#include <algorithm> +#include <vector> + +#ifdef __x86_64__ +extern "C" int arch_prctl(int code, __sanitizer::uptr *addr); +#endif + +namespace __sanitizer { + +struct TidReporterArgument { + TidReporterArgument() { + pthread_mutex_init(&terminate_thread_mutex, NULL); + pthread_mutex_init(&tid_reported_mutex, NULL); + pthread_cond_init(&terminate_thread_cond, NULL); + pthread_cond_init(&tid_reported_cond, NULL); + terminate_thread = false; + } + + ~TidReporterArgument() { + pthread_mutex_destroy(&terminate_thread_mutex); + pthread_mutex_destroy(&tid_reported_mutex); + pthread_cond_destroy(&terminate_thread_cond); + pthread_cond_destroy(&tid_reported_cond); + } + + pid_t reported_tid; + // For signaling to spawned threads that they should terminate. + pthread_cond_t terminate_thread_cond; + pthread_mutex_t terminate_thread_mutex; + bool terminate_thread; + // For signaling to main thread that a child thread has reported its tid. + pthread_cond_t tid_reported_cond; + pthread_mutex_t tid_reported_mutex; + + private: + // Disallow evil constructors + TidReporterArgument(const TidReporterArgument &); + void operator=(const TidReporterArgument &); +}; + +class ThreadListerTest : public ::testing::Test { + protected: + virtual void SetUp() { + pthread_t pthread_id; + pid_t tid; + for (uptr i = 0; i < kThreadCount; i++) { + SpawnTidReporter(&pthread_id, &tid); + pthread_ids_.push_back(pthread_id); + tids_.push_back(tid); + } + } + + virtual void TearDown() { + pthread_mutex_lock(&thread_arg.terminate_thread_mutex); + thread_arg.terminate_thread = true; + pthread_cond_broadcast(&thread_arg.terminate_thread_cond); + pthread_mutex_unlock(&thread_arg.terminate_thread_mutex); + for (uptr i = 0; i < pthread_ids_.size(); i++) + pthread_join(pthread_ids_[i], NULL); + } + + void SpawnTidReporter(pthread_t *pthread_id, pid_t *tid); + + static const uptr kThreadCount = 20; + + std::vector<pthread_t> pthread_ids_; + std::vector<pid_t> tids_; + + TidReporterArgument thread_arg; +}; + +// Writes its TID once to reported_tid and waits until signaled to terminate. +void *TidReporterThread(void *argument) { + TidReporterArgument *arg = reinterpret_cast<TidReporterArgument *>(argument); + pthread_mutex_lock(&arg->tid_reported_mutex); + arg->reported_tid = GetTid(); + pthread_cond_broadcast(&arg->tid_reported_cond); + pthread_mutex_unlock(&arg->tid_reported_mutex); + + pthread_mutex_lock(&arg->terminate_thread_mutex); + while (!arg->terminate_thread) + pthread_cond_wait(&arg->terminate_thread_cond, + &arg->terminate_thread_mutex); + pthread_mutex_unlock(&arg->terminate_thread_mutex); + return NULL; +} + +void ThreadListerTest::SpawnTidReporter(pthread_t *pthread_id, + pid_t *tid) { + pthread_mutex_lock(&thread_arg.tid_reported_mutex); + thread_arg.reported_tid = -1; + ASSERT_EQ(0, pthread_create(pthread_id, NULL, + TidReporterThread, + &thread_arg)); + while (thread_arg.reported_tid == -1) + pthread_cond_wait(&thread_arg.tid_reported_cond, + &thread_arg.tid_reported_mutex); + pthread_mutex_unlock(&thread_arg.tid_reported_mutex); + *tid = thread_arg.reported_tid; +} + +static std::vector<pid_t> ReadTidsToVector(ThreadLister *thread_lister) { + std::vector<pid_t> listed_tids; + pid_t tid; + while ((tid = thread_lister->GetNextTID()) >= 0) + listed_tids.push_back(tid); + EXPECT_FALSE(thread_lister->error()); + return listed_tids; +} + +static bool Includes(std::vector<pid_t> first, std::vector<pid_t> second) { + std::sort(first.begin(), first.end()); + std::sort(second.begin(), second.end()); + return std::includes(first.begin(), first.end(), + second.begin(), second.end()); +} + +static bool HasElement(std::vector<pid_t> vector, pid_t element) { + return std::find(vector.begin(), vector.end(), element) != vector.end(); +} + +// ThreadLister's output should include the current thread's TID and the TID of +// every thread we spawned. +TEST_F(ThreadListerTest, ThreadListerSeesAllSpawnedThreads) { + pid_t self_tid = GetTid(); + ThreadLister thread_lister(getpid()); + std::vector<pid_t> listed_tids = ReadTidsToVector(&thread_lister); + ASSERT_TRUE(HasElement(listed_tids, self_tid)); + ASSERT_TRUE(Includes(listed_tids, tids_)); +} + +// Calling Reset() should not cause ThreadLister to forget any threads it's +// supposed to know about. +TEST_F(ThreadListerTest, ResetDoesNotForgetThreads) { + ThreadLister thread_lister(getpid()); + + // Run the loop body twice, because Reset() might behave differently if called + // on a freshly created object. + for (uptr i = 0; i < 2; i++) { + thread_lister.Reset(); + std::vector<pid_t> listed_tids = ReadTidsToVector(&thread_lister); + ASSERT_TRUE(Includes(listed_tids, tids_)); + } +} + +// If new threads have spawned during ThreadLister object's lifetime, calling +// Reset() should cause ThreadLister to recognize their existence. +TEST_F(ThreadListerTest, ResetMakesNewThreadsKnown) { + ThreadLister thread_lister(getpid()); + std::vector<pid_t> threads_before_extra = ReadTidsToVector(&thread_lister); + + pthread_t extra_pthread_id; + pid_t extra_tid; + SpawnTidReporter(&extra_pthread_id, &extra_tid); + // Register the new thread so it gets terminated in TearDown(). + pthread_ids_.push_back(extra_pthread_id); + + // It would be very bizarre if the new TID had been listed before we even + // spawned that thread, but it would also cause a false success in this test, + // so better check for that. + ASSERT_FALSE(HasElement(threads_before_extra, extra_tid)); + + thread_lister.Reset(); + + std::vector<pid_t> threads_after_extra = ReadTidsToVector(&thread_lister); + ASSERT_TRUE(HasElement(threads_after_extra, extra_tid)); +} + +TEST(SanitizerCommon, SetEnvTest) { + const char kEnvName[] = "ENV_FOO"; + SetEnv(kEnvName, "value"); + EXPECT_STREQ("value", getenv(kEnvName)); + unsetenv(kEnvName); + EXPECT_EQ(0, getenv(kEnvName)); +} + +#ifdef __x86_64__ +// libpthread puts the thread descriptor (%fs:0x0) at the end of stack space. +void *thread_descriptor_test_func(void *arg) { + uptr fs; + arch_prctl(ARCH_GET_FS, &fs); + pthread_attr_t attr; + pthread_getattr_np(pthread_self(), &attr); + void *stackaddr; + uptr stacksize; + pthread_attr_getstack(&attr, &stackaddr, &stacksize); + return (void *)((uptr)stackaddr + stacksize - fs); +} + +TEST(SanitizerLinux, ThreadDescriptorSize) { + pthread_t tid; + void *result; + pthread_create(&tid, 0, thread_descriptor_test_func, 0); + ASSERT_EQ(0, pthread_join(tid, &result)); + EXPECT_EQ((uptr)result, ThreadDescriptorSize()); +} +#endif + +TEST(SanitizerCommon, LibraryNameIs) { + EXPECT_FALSE(LibraryNameIs("", "")); + + char full_name[256]; + const char *paths[] = { "", "/", "/path/to/" }; + const char *suffixes[] = { "", "-linux", ".1.2", "-linux.1.2" }; + const char *base_names[] = { "lib", "lib.0", "lib-i386" }; + const char *wrong_names[] = { "", "lib.9", "lib-x86_64" }; + for (uptr i = 0; i < ARRAY_SIZE(paths); i++) + for (uptr j = 0; j < ARRAY_SIZE(suffixes); j++) { + for (uptr k = 0; k < ARRAY_SIZE(base_names); k++) { + internal_snprintf(full_name, ARRAY_SIZE(full_name), "%s%s%s.so", + paths[i], base_names[k], suffixes[j]); + EXPECT_TRUE(LibraryNameIs(full_name, base_names[k])) + << "Full name " << full_name + << " doesn't match base name " << base_names[k]; + for (uptr m = 0; m < ARRAY_SIZE(wrong_names); m++) + EXPECT_FALSE(LibraryNameIs(full_name, wrong_names[m])) + << "Full name " << full_name + << " matches base name " << wrong_names[m]; + } + } +} + +} // namespace __sanitizer + +#endif // SANITIZER_LINUX diff --git a/lib/sanitizer_common/tests/sanitizer_mutex_test.cc b/lib/sanitizer_common/tests/sanitizer_mutex_test.cc index 6bb2ae29a188c..1dc9bef207104 100644 --- a/lib/sanitizer_common/tests/sanitizer_mutex_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_mutex_test.cc @@ -92,6 +92,12 @@ static void *try_thread(void *param) { return 0; } +template<typename MutexType> +static void check_locked(MutexType *mtx) { + GenericScopedLock<MutexType> l(mtx); + mtx->CheckLocked(); +} + TEST(SanitizerCommon, SpinMutex) { SpinMutex mtx; mtx.Init(); @@ -123,6 +129,7 @@ TEST(SanitizerCommon, BlockingMutex) { pthread_create(&threads[i], 0, lock_thread<BlockingMutex>, &data); for (int i = 0; i < kThreads; i++) pthread_join(threads[i], 0); + check_locked(mtx); } } // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc b/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc index 00b260479da98..1df2bcfd4bec7 100644 --- a/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc @@ -19,45 +19,72 @@ using namespace __sanitizer; -#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ +#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ ((std::vector<unsigned> *)ctx)->push_back(size) #include "sanitizer_common/sanitizer_common_interceptors_scanf.inc" -static void testScanf2(void *ctx, const char *format, ...) { +static const char scanf_buf[] = "Test string."; +static size_t scanf_buf_size = sizeof(scanf_buf); +static const unsigned SCANF_ARGS_MAX = 16; + +static void testScanf3(void *ctx, int result, bool allowGnuMalloc, + const char *format, ...) { va_list ap; va_start(ap, format); - scanf_common(ctx, format, ap); + scanf_common(ctx, result, allowGnuMalloc, format, ap); va_end(ap); } -static void testScanf(const char *format, unsigned n, ...) { +static void testScanf2(const char *format, int scanf_result, + bool allowGnuMalloc, unsigned n, + va_list expected_sizes) { std::vector<unsigned> scanf_sizes; // 16 args should be enough. - testScanf2((void *)&scanf_sizes, format, - (void*)0, (void*)0, (void*)0, (void*)0, - (void*)0, (void*)0, (void*)0, (void*)0, - (void*)0, (void*)0, (void*)0, (void*)0, - (void*)0, (void*)0, (void*)0, (void*)0); - ASSERT_EQ(n, scanf_sizes.size()) << - "Unexpected number of format arguments: '" << format << "'"; + testScanf3((void *)&scanf_sizes, scanf_result, allowGnuMalloc, format, + scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, + scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, + scanf_buf, scanf_buf, scanf_buf, scanf_buf); + ASSERT_EQ(n, scanf_sizes.size()) << "Unexpected number of format arguments: '" + << format << "'"; + for (unsigned i = 0; i < n; ++i) + EXPECT_EQ(va_arg(expected_sizes, unsigned), scanf_sizes[i]) + << "Unexpect write size for argument " << i << ", format string '" + << format << "'"; +} + +static void testScanf(const char *format, unsigned n, ...) { va_list ap; va_start(ap, n); - for (unsigned i = 0; i < n; ++i) - EXPECT_EQ(va_arg(ap, unsigned), scanf_sizes[i]) << - "Unexpect write size for argument " << i << ", format string '" << - format << "'"; + testScanf2(format, SCANF_ARGS_MAX, /* allowGnuMalloc */ true, n, ap); + va_end(ap); +} + +static void testScanfPartial(const char *format, int scanf_result, unsigned n, + ...) { + va_list ap; + va_start(ap, n); + testScanf2(format, scanf_result, /* allowGnuMalloc */ true, n, ap); + va_end(ap); +} + +static void testScanfNoGnuMalloc(const char *format, unsigned n, ...) { + va_list ap; + va_start(ap, n); + testScanf2(format, SCANF_ARGS_MAX, /* allowGnuMalloc */ false, n, ap); va_end(ap); } TEST(SanitizerCommonInterceptors, Scanf) { - const unsigned I = sizeof(int); // NOLINT - const unsigned L = sizeof(long); // NOLINT - const unsigned LL = sizeof(long long); // NOLINT - const unsigned S = sizeof(short); // NOLINT - const unsigned C = sizeof(char); // NOLINT - const unsigned D = sizeof(double); // NOLINT - const unsigned F = sizeof(float); // NOLINT + const unsigned I = sizeof(int); // NOLINT + const unsigned L = sizeof(long); // NOLINT + const unsigned LL = sizeof(long long); // NOLINT + const unsigned S = sizeof(short); // NOLINT + const unsigned C = sizeof(char); // NOLINT + const unsigned D = sizeof(double); // NOLINT + const unsigned LD = sizeof(long double); // NOLINT + const unsigned F = sizeof(float); // NOLINT + const unsigned P = sizeof(char *); // NOLINT testScanf("%d", 1, I); testScanf("%d%d%d", 3, I, I, I); @@ -65,6 +92,7 @@ TEST(SanitizerCommonInterceptors, Scanf) { testScanf("%ld", 1, L); testScanf("%llu", 1, LL); testScanf("a %hd%hhx", 2, S, C); + testScanf("%c", 1, C); testScanf("%%", 0); testScanf("a%%", 0); @@ -79,7 +107,72 @@ TEST(SanitizerCommonInterceptors, Scanf) { testScanf("%nf", 1, I); testScanf("%10s", 1, 11); + testScanf("%10c", 1, 10); testScanf("%%10s", 0); testScanf("%*10s", 0); testScanf("%*d", 0); + + testScanf("%4d%8f%c", 3, I, F, C); + testScanf("%s%d", 2, scanf_buf_size, I); + testScanf("%[abc]", 1, scanf_buf_size); + testScanf("%4[bcdef]", 1, 5); + testScanf("%[]]", 1, scanf_buf_size); + testScanf("%8[^]%d0-9-]%c", 2, 9, C); + + testScanf("%*[^:]%n:%d:%1[ ]%n", 4, I, I, 2, I); + + testScanf("%*d%u", 1, I); + + testScanf("%c%d", 2, C, I); + testScanf("%A%lf", 2, F, D); + + testScanf("%ms %Lf", 2, P, LD); + testScanf("s%Las", 1, LD); + testScanf("%ar", 1, F); + + // In the cases with std::min below the format spec can be interpreted as + // either floating-something, or (GNU extension) callee-allocated string. + // Our conservative implementation reports one of the two possibilities with + // the least store range. + testScanf("%a[", 0); + testScanf("%a[]", 0); + testScanf("%a[]]", 1, std::min(F, P)); + testScanf("%a[abc]", 1, std::min(F, P)); + testScanf("%a[^abc]", 1, std::min(F, P)); + testScanf("%a[ab%c] %d", 0); + testScanf("%a[^ab%c] %d", 0); + testScanf("%as", 1, std::min(F, P)); + testScanf("%aS", 1, std::min(F, P)); + testScanf("%a13S", 1, std::min(F, P)); + testScanf("%alS", 1, std::min(F, P)); + + testScanfNoGnuMalloc("s%Las", 1, LD); + testScanfNoGnuMalloc("%ar", 1, F); + testScanfNoGnuMalloc("%a[", 1, F); + testScanfNoGnuMalloc("%a[]", 1, F); + testScanfNoGnuMalloc("%a[]]", 1, F); + testScanfNoGnuMalloc("%a[abc]", 1, F); + testScanfNoGnuMalloc("%a[^abc]", 1, F); + testScanfNoGnuMalloc("%a[ab%c] %d", 3, F, C, I); + testScanfNoGnuMalloc("%a[^ab%c] %d", 3, F, C, I); + testScanfNoGnuMalloc("%as", 1, F); + testScanfNoGnuMalloc("%aS", 1, F); + testScanfNoGnuMalloc("%a13S", 1, F); + testScanfNoGnuMalloc("%alS", 1, F); + + testScanf("%5$d", 0); + testScanf("%md", 0); + testScanf("%m10s", 0); + + testScanfPartial("%d%d%d%d //1\n", 1, 1, I); + testScanfPartial("%d%d%d%d //2\n", 2, 2, I, I); + testScanfPartial("%d%d%d%d //3\n", 3, 3, I, I, I); + testScanfPartial("%d%d%d%d //4\n", 4, 4, I, I, I, I); + + testScanfPartial("%d%n%n%d //1\n", 1, 1, I); + testScanfPartial("%d%n%n%d //2\n", 2, 4, I, I, I, I); + + testScanfPartial("%d%n%n%d %s %s", 3, 5, I, I, I, I, scanf_buf_size); + testScanfPartial("%d%n%n%d %s %s", 4, 6, I, I, I, I, scanf_buf_size, + scanf_buf_size); } diff --git a/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cc b/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cc new file mode 100644 index 0000000000000..3d352cb97a5e3 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cc @@ -0,0 +1,96 @@ +//===-- sanitizer_stacktrace_test.cc --------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer/AddressSanitizer runtime. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "gtest/gtest.h" + +namespace __sanitizer { + +class FastUnwindTest : public ::testing::Test { + protected: + virtual void SetUp(); + + uptr fake_stack[10]; + uptr start_pc; + uptr fake_top; + uptr fake_bottom; + StackTrace trace; +}; + +static uptr PC(uptr idx) { + return (1<<20) + idx; +} + +void FastUnwindTest::SetUp() { + // Fill an array of pointers with fake fp+retaddr pairs. Frame pointers have + // even indices. + for (uptr i = 0; i+1 < ARRAY_SIZE(fake_stack); i += 2) { + fake_stack[i] = (uptr)&fake_stack[i+2]; // fp + fake_stack[i+1] = PC(i + 1); // retaddr + } + // Mark the last fp as zero to terminate the stack trace. + fake_stack[RoundDownTo(ARRAY_SIZE(fake_stack) - 1, 2)] = 0; + + // Top is two slots past the end because FastUnwindStack subtracts two. + fake_top = (uptr)&fake_stack[ARRAY_SIZE(fake_stack) + 2]; + // Bottom is one slot before the start because FastUnwindStack uses >. + fake_bottom = (uptr)&fake_stack[-1]; + start_pc = PC(0); + + // This is common setup done by __asan::GetStackTrace(). + trace.size = 0; + trace.max_size = ARRAY_SIZE(fake_stack); + trace.trace[0] = start_pc; +} + +TEST_F(FastUnwindTest, Basic) { + trace.FastUnwindStack(start_pc, (uptr)&fake_stack[0], + fake_top, fake_bottom); + // Should get all on-stack retaddrs and start_pc. + EXPECT_EQ(6U, trace.size); + EXPECT_EQ(start_pc, trace.trace[0]); + for (uptr i = 1; i <= 5; i++) { + EXPECT_EQ(PC(i*2 - 1), trace.trace[i]); + } +} + +// From: http://code.google.com/p/address-sanitizer/issues/detail?id=162 +TEST_F(FastUnwindTest, FramePointerLoop) { + // Make one fp point to itself. + fake_stack[4] = (uptr)&fake_stack[4]; + trace.FastUnwindStack(start_pc, (uptr)&fake_stack[0], + fake_top, fake_bottom); + // Should get all on-stack retaddrs up to the 4th slot and start_pc. + EXPECT_EQ(4U, trace.size); + EXPECT_EQ(start_pc, trace.trace[0]); + for (uptr i = 1; i <= 3; i++) { + EXPECT_EQ(PC(i*2 - 1), trace.trace[i]); + } +} + +TEST_F(FastUnwindTest, MisalignedFramePointer) { + // Make one fp misaligned. + fake_stack[4] += 3; + trace.FastUnwindStack(start_pc, (uptr)&fake_stack[0], + fake_top, fake_bottom); + // Should get all on-stack retaddrs up to the 4th slot and start_pc. + EXPECT_EQ(4U, trace.size); + EXPECT_EQ(start_pc, trace.trace[0]); + for (uptr i = 1; i < 4U; i++) { + EXPECT_EQ(PC(i*2 - 1), trace.trace[i]); + } +} + + +} // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc b/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc new file mode 100644 index 0000000000000..a5f8516df575d --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc @@ -0,0 +1,194 @@ +//===-- sanitizer_stoptheworld_test.cc ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tests for sanitizer_stoptheworld.h +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_platform.h" +#if SANITIZER_LINUX + +#include "sanitizer_common/sanitizer_stoptheworld.h" +#include "gtest/gtest.h" + +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_common.h" + +#include <pthread.h> +#include <sched.h> + +namespace __sanitizer { + +static pthread_mutex_t incrementer_thread_exit_mutex; + +struct CallbackArgument { + volatile int counter; + volatile bool threads_stopped; + volatile bool callback_executed; + CallbackArgument() + : counter(0), + threads_stopped(false), + callback_executed(false) {} +}; + +void *IncrementerThread(void *argument) { + CallbackArgument *callback_argument = (CallbackArgument *)argument; + while (true) { + __sync_fetch_and_add(&callback_argument->counter, 1); + if (pthread_mutex_trylock(&incrementer_thread_exit_mutex) == 0) { + pthread_mutex_unlock(&incrementer_thread_exit_mutex); + return NULL; + } else { + sched_yield(); + } + } +} + +// This callback checks that IncrementerThread is suspended at the time of its +// execution. +void Callback(const SuspendedThreadsList &suspended_threads_list, + void *argument) { + CallbackArgument *callback_argument = (CallbackArgument *)argument; + callback_argument->callback_executed = true; + int counter_at_init = __sync_fetch_and_add(&callback_argument->counter, 0); + for (uptr i = 0; i < 1000; i++) { + sched_yield(); + if (__sync_fetch_and_add(&callback_argument->counter, 0) != + counter_at_init) { + callback_argument->threads_stopped = false; + return; + } + } + callback_argument->threads_stopped = true; +} + +TEST(StopTheWorld, SuspendThreadsSimple) { + pthread_mutex_init(&incrementer_thread_exit_mutex, NULL); + CallbackArgument argument; + pthread_t thread_id; + int pthread_create_result; + pthread_mutex_lock(&incrementer_thread_exit_mutex); + pthread_create_result = pthread_create(&thread_id, NULL, IncrementerThread, + &argument); + ASSERT_EQ(0, pthread_create_result); + StopTheWorld(&Callback, &argument); + pthread_mutex_unlock(&incrementer_thread_exit_mutex); + EXPECT_TRUE(argument.callback_executed); + EXPECT_TRUE(argument.threads_stopped); + // argument is on stack, so we have to wait for the incrementer thread to + // terminate before we can return from this function. + ASSERT_EQ(0, pthread_join(thread_id, NULL)); + pthread_mutex_destroy(&incrementer_thread_exit_mutex); +} + +// A more comprehensive test where we spawn a bunch of threads while executing +// StopTheWorld in parallel. +static const uptr kThreadCount = 50; +static const uptr kStopWorldAfter = 10; // let this many threads spawn first + +static pthread_mutex_t advanced_incrementer_thread_exit_mutex; + +struct AdvancedCallbackArgument { + volatile uptr thread_index; + volatile int counters[kThreadCount]; + pthread_t thread_ids[kThreadCount]; + volatile bool threads_stopped; + volatile bool callback_executed; + volatile bool fatal_error; + AdvancedCallbackArgument() + : thread_index(0), + threads_stopped(false), + callback_executed(false), + fatal_error(false) {} +}; + +void *AdvancedIncrementerThread(void *argument) { + AdvancedCallbackArgument *callback_argument = + (AdvancedCallbackArgument *)argument; + uptr this_thread_index = __sync_fetch_and_add( + &callback_argument->thread_index, 1); + // Spawn the next thread. + int pthread_create_result; + if (this_thread_index + 1 < kThreadCount) { + pthread_create_result = + pthread_create(&callback_argument->thread_ids[this_thread_index + 1], + NULL, AdvancedIncrementerThread, argument); + // Cannot use ASSERT_EQ in non-void-returning functions. If there's a + // problem, defer failing to the main thread. + if (pthread_create_result != 0) { + callback_argument->fatal_error = true; + __sync_fetch_and_add(&callback_argument->thread_index, + kThreadCount - callback_argument->thread_index); + } + } + // Do the actual work. + while (true) { + __sync_fetch_and_add(&callback_argument->counters[this_thread_index], 1); + if (pthread_mutex_trylock(&advanced_incrementer_thread_exit_mutex) == 0) { + pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex); + return NULL; + } else { + sched_yield(); + } + } +} + +void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list, + void *argument) { + AdvancedCallbackArgument *callback_argument = + (AdvancedCallbackArgument *)argument; + callback_argument->callback_executed = true; + + int counters_at_init[kThreadCount]; + for (uptr j = 0; j < kThreadCount; j++) + counters_at_init[j] = __sync_fetch_and_add(&callback_argument->counters[j], + 0); + for (uptr i = 0; i < 10; i++) { + sched_yield(); + for (uptr j = 0; j < kThreadCount; j++) + if (__sync_fetch_and_add(&callback_argument->counters[j], 0) != + counters_at_init[j]) { + callback_argument->threads_stopped = false; + return; + } + } + callback_argument->threads_stopped = true; +} + +TEST(StopTheWorld, SuspendThreadsAdvanced) { + pthread_mutex_init(&advanced_incrementer_thread_exit_mutex, NULL); + AdvancedCallbackArgument argument; + + pthread_mutex_lock(&advanced_incrementer_thread_exit_mutex); + int pthread_create_result; + pthread_create_result = pthread_create(&argument.thread_ids[0], NULL, + AdvancedIncrementerThread, + &argument); + ASSERT_EQ(0, pthread_create_result); + // Wait for several threads to spawn before proceeding. + while (__sync_fetch_and_add(&argument.thread_index, 0) < kStopWorldAfter) + sched_yield(); + StopTheWorld(&AdvancedCallback, &argument); + EXPECT_TRUE(argument.callback_executed); + EXPECT_TRUE(argument.threads_stopped); + + // Wait for all threads to spawn before we start terminating them. + while (__sync_fetch_and_add(&argument.thread_index, 0) < kThreadCount) + sched_yield(); + ASSERT_FALSE(argument.fatal_error); // a pthread_create has failed + // Signal the threads to terminate. + pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex); + for (uptr i = 0; i < kThreadCount; i++) + ASSERT_EQ(0, pthread_join(argument.thread_ids[i], NULL)); + pthread_mutex_destroy(&advanced_incrementer_thread_exit_mutex); +} + +} // namespace __sanitizer + +#endif // SANITIZER_LINUX diff --git a/lib/sanitizer_common/tests/sanitizer_stoptheworld_testlib.cc b/lib/sanitizer_common/tests/sanitizer_stoptheworld_testlib.cc new file mode 100644 index 0000000000000..d8be2afb19e9c --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_stoptheworld_testlib.cc @@ -0,0 +1,53 @@ +//===-- sanitizer_stoptheworld_testlib.cc ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Dynamic library to test StopTheWorld functionality. +// When loaded with LD_PRELOAD, it will periodically suspend all threads. +//===----------------------------------------------------------------------===// +/* Usage: +clang++ -fno-exceptions -g -fPIC -I. \ + sanitizer_common/tests/sanitizer_stoptheworld_testlib.cc \ + sanitizer_common/sanitizer_*.cc -shared -lpthread -o teststoptheworld.so +LD_PRELOAD=`pwd`/teststoptheworld.so /your/app +*/ + +#include "sanitizer_common/sanitizer_platform.h" +#if SANITIZER_LINUX + +#include <dlfcn.h> +#include <stddef.h> +#include <stdio.h> +#include <pthread.h> +#include <unistd.h> + +#include "sanitizer_common/sanitizer_stoptheworld.h" + +namespace { +const uptr kSuspendDuration = 3; +const uptr kRunDuration = 3; + +void Callback(const SuspendedThreadsList &suspended_threads_list, + void *argument) { + sleep(kSuspendDuration); +} + +void *SuspenderThread(void *argument) { + while (true) { + sleep(kRunDuration); + StopTheWorld(Callback, NULL); + } + return NULL; +} + +__attribute__((constructor)) void StopTheWorldTestLibConstructor(void) { + pthread_t thread_id; + pthread_create(&thread_id, NULL, SuspenderThread, NULL); +} +} // namespace + +#endif // SANITIZER_LINUX diff --git a/lib/sanitizer_common/tests/sanitizer_test_utils.h b/lib/sanitizer_common/tests/sanitizer_test_utils.h index 6129ea8a5370a..a770d0fbd39ec 100644 --- a/lib/sanitizer_common/tests/sanitizer_test_utils.h +++ b/lib/sanitizer_common/tests/sanitizer_test_utils.h @@ -36,12 +36,14 @@ typedef __int64 int64_t; #define __has_feature(x) 0 #endif -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS \ - __attribute__((no_address_safety_analysis)) -#else -# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS -#endif +#ifndef ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS +# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS \ + __attribute__((no_sanitize_address)) +# else +# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS +# endif +#endif // ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS #if __LP64__ || defined(_WIN64) # define SANITIZER_WORDSIZE 64 diff --git a/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cc b/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cc new file mode 100644 index 0000000000000..e080403fb56c9 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cc @@ -0,0 +1,230 @@ +//===-- sanitizer_thread_registry_test.cc ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of shared sanitizer runtime. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_thread_registry.h" +#include "gtest/gtest.h" + +#include <vector> + +namespace __sanitizer { + +static BlockingMutex tctx_allocator_lock(LINKER_INITIALIZED); +static LowLevelAllocator tctx_allocator; + +template<typename TCTX> +static ThreadContextBase *GetThreadContext(u32 tid) { + BlockingMutexLock l(&tctx_allocator_lock); + void *mem = tctx_allocator.Allocate(sizeof(TCTX)); + return new(mem) TCTX(tid); +} + +static const u32 kMaxRegistryThreads = 1000; +static const u32 kRegistryQuarantine = 2; + +static void CheckThreadQuantity(ThreadRegistry *registry, uptr exp_total, + uptr exp_running, uptr exp_alive) { + uptr total, running, alive; + registry->GetNumberOfThreads(&total, &running, &alive); + EXPECT_EQ(exp_total, total); + EXPECT_EQ(exp_running, running); + EXPECT_EQ(exp_alive, alive); +} + +static bool is_detached(u32 tid) { + return (tid % 2 == 0); +} + +static uptr get_uid(u32 tid) { + return tid * 2; +} + +static bool HasName(ThreadContextBase *tctx, void *arg) { + char *name = (char*)arg; + return (tctx->name && 0 == internal_strcmp(tctx->name, name)); +} + +static bool HasUid(ThreadContextBase *tctx, void *arg) { + uptr uid = (uptr)arg; + return (tctx->user_id == uid); +} + +static void MarkUidAsPresent(ThreadContextBase *tctx, void *arg) { + bool *arr = (bool*)arg; + arr[tctx->tid] = true; +} + +static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) { + // Create and start a main thread. + EXPECT_EQ(0U, registry->CreateThread(get_uid(0), true, -1, 0)); + registry->StartThread(0, 0, 0); + // Create a bunch of threads. + for (u32 i = 1; i <= 10; i++) { + EXPECT_EQ(i, registry->CreateThread(get_uid(i), is_detached(i), 0, 0)); + } + CheckThreadQuantity(registry, 11, 1, 11); + // Start some of them. + for (u32 i = 1; i <= 5; i++) { + registry->StartThread(i, 0, 0); + } + CheckThreadQuantity(registry, 11, 6, 11); + // Finish, create and start more threads. + for (u32 i = 1; i <= 5; i++) { + registry->FinishThread(i); + if (!is_detached(i)) + registry->JoinThread(i, 0); + } + for (u32 i = 6; i <= 10; i++) { + registry->StartThread(i, 0, 0); + } + std::vector<u32> new_tids; + for (u32 i = 11; i <= 15; i++) { + new_tids.push_back( + registry->CreateThread(get_uid(i), is_detached(i), 0, 0)); + } + ASSERT_LE(kRegistryQuarantine, 5U); + u32 exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0); + CheckThreadQuantity(registry, exp_total, 6, 11); + // Test SetThreadName and FindThread. + registry->SetThreadName(6, "six"); + registry->SetThreadName(7, "seven"); + EXPECT_EQ(7U, registry->FindThread(HasName, (void*)"seven")); + EXPECT_EQ(ThreadRegistry::kUnknownTid, + registry->FindThread(HasName, (void*)"none")); + EXPECT_EQ(0U, registry->FindThread(HasUid, (void*)get_uid(0))); + EXPECT_EQ(10U, registry->FindThread(HasUid, (void*)get_uid(10))); + EXPECT_EQ(ThreadRegistry::kUnknownTid, + registry->FindThread(HasUid, (void*)0x1234)); + // Detach and finish and join remaining threads. + for (u32 i = 6; i <= 10; i++) { + registry->DetachThread(i); + registry->FinishThread(i); + } + for (u32 i = 0; i < new_tids.size(); i++) { + u32 tid = new_tids[i]; + registry->StartThread(tid, 0, 0); + registry->DetachThread(tid); + registry->FinishThread(tid); + } + CheckThreadQuantity(registry, exp_total, 1, 1); + // Test methods that require the caller to hold a ThreadRegistryLock. + bool has_tid[16]; + internal_memset(&has_tid[0], 0, sizeof(has_tid)); + { + ThreadRegistryLock l(registry); + registry->RunCallbackForEachThreadLocked(MarkUidAsPresent, &has_tid[0]); + } + for (u32 i = 0; i < exp_total; i++) { + EXPECT_TRUE(has_tid[i]); + } + { + ThreadRegistryLock l(registry); + registry->CheckLocked(); + ThreadContextBase *main_thread = registry->GetThreadLocked(0); + EXPECT_EQ(main_thread, registry->FindThreadContextLocked( + HasUid, (void*)get_uid(0))); + } + EXPECT_EQ(11U, registry->GetMaxAliveThreads()); +} + +TEST(SanitizerCommon, ThreadRegistryTest) { + ThreadRegistry quarantine_registry(GetThreadContext<ThreadContextBase>, + kMaxRegistryThreads, + kRegistryQuarantine); + TestRegistry(&quarantine_registry, true); + + ThreadRegistry no_quarantine_registry(GetThreadContext<ThreadContextBase>, + kMaxRegistryThreads, + kMaxRegistryThreads); + TestRegistry(&no_quarantine_registry, false); +} + +static const int kThreadsPerShard = 20; +static const int kNumShards = 25; + +static int num_created[kNumShards + 1]; +static int num_started[kNumShards + 1]; +static int num_joined[kNumShards + 1]; + +namespace { + +struct RunThreadArgs { + ThreadRegistry *registry; + uptr shard; // started from 1. +}; + +class TestThreadContext : public ThreadContextBase { + public: + explicit TestThreadContext(int tid) : ThreadContextBase(tid) {} + void OnJoined(void *arg) { + uptr shard = (uptr)arg; + num_joined[shard]++; + } + void OnStarted(void *arg) { + uptr shard = (uptr)arg; + num_started[shard]++; + } + void OnCreated(void *arg) { + uptr shard = (uptr)arg; + num_created[shard]++; + } +}; + +} // namespace + +void *RunThread(void *arg) { + RunThreadArgs *args = static_cast<RunThreadArgs*>(arg); + std::vector<int> tids; + for (int i = 0; i < kThreadsPerShard; i++) + tids.push_back( + args->registry->CreateThread(0, false, 0, (void*)args->shard)); + for (int i = 0; i < kThreadsPerShard; i++) + args->registry->StartThread(tids[i], 0, (void*)args->shard); + for (int i = 0; i < kThreadsPerShard; i++) + args->registry->FinishThread(tids[i]); + for (int i = 0; i < kThreadsPerShard; i++) + args->registry->JoinThread(tids[i], (void*)args->shard); + return 0; +} + +static void ThreadedTestRegistry(ThreadRegistry *registry) { + // Create and start a main thread. + EXPECT_EQ(0U, registry->CreateThread(0, true, -1, 0)); + registry->StartThread(0, 0, 0); + pthread_t threads[kNumShards]; + RunThreadArgs args[kNumShards]; + for (int i = 0; i < kNumShards; i++) { + args[i].registry = registry; + args[i].shard = i + 1; + pthread_create(&threads[i], 0, RunThread, &args[i]); + } + for (int i = 0; i < kNumShards; i++) { + pthread_join(threads[i], 0); + } + // Check that each thread created/started/joined correct amount + // of "threads" in thread_registry. + EXPECT_EQ(1, num_created[0]); + EXPECT_EQ(1, num_started[0]); + EXPECT_EQ(0, num_joined[0]); + for (int i = 1; i <= kNumShards; i++) { + EXPECT_EQ(kThreadsPerShard, num_created[i]); + EXPECT_EQ(kThreadsPerShard, num_started[i]); + EXPECT_EQ(kThreadsPerShard, num_joined[i]); + } +} + +TEST(SanitizerCommon, ThreadRegistryThreadedTest) { + ThreadRegistry registry(GetThreadContext<TestThreadContext>, + kThreadsPerShard * kNumShards + 1, 10); + ThreadedTestRegistry(®istry); +} + +} // namespace __sanitizer |