//===-- 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" #include <pthread.h> #include <sched.h> #include <stdlib.h> #include <algorithm> #include <vector> 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)); } #if defined(__x86_64__) || defined(__i386__) void *thread_self_offset_test_func(void *arg) { bool result = *(uptr *)((char *)ThreadSelf() + ThreadSelfOffset()) == ThreadSelf(); return (void *)result; } TEST(SanitizerLinux, ThreadSelfOffset) { EXPECT_TRUE((bool)thread_self_offset_test_func(0)); pthread_t tid; void *result; ASSERT_EQ(0, pthread_create(&tid, 0, thread_self_offset_test_func, 0)); ASSERT_EQ(0, pthread_join(tid, &result)); EXPECT_TRUE((bool)result); } // libpthread puts the thread descriptor at the end of stack space. void *thread_descriptor_size_test_func(void *arg) { uptr descr_addr = ThreadSelf(); pthread_attr_t attr; pthread_getattr_np(pthread_self(), &attr); void *stackaddr; size_t stacksize; pthread_attr_getstack(&attr, &stackaddr, &stacksize); return (void *)((uptr)stackaddr + stacksize - descr_addr); } TEST(SanitizerLinux, ThreadDescriptorSize) { pthread_t tid; void *result; ASSERT_EQ(0, pthread_create(&tid, 0, thread_descriptor_size_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