1 //===-- sanitizer_stoptheworld_test.cpp -----------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Tests for sanitizer_stoptheworld.h
10 //
11 //===----------------------------------------------------------------------===//
12
13 #include "sanitizer_common/sanitizer_stoptheworld.h"
14
15 #include "sanitizer_common/sanitizer_platform.h"
16 #if (SANITIZER_LINUX || SANITIZER_WINDOWS) && defined(__x86_64__)
17
18 # include <atomic>
19 # include <mutex>
20 # include <thread>
21
22 # include "gtest/gtest.h"
23 # include "sanitizer_common/sanitizer_common.h"
24 # include "sanitizer_common/sanitizer_libc.h"
25
26 namespace __sanitizer {
27
28 static std::mutex mutex;
29
30 struct CallbackArgument {
31 std::atomic_int counter = {};
32 std::atomic_bool threads_stopped = {};
33 std::atomic_bool callback_executed = {};
34 };
35
IncrementerThread(CallbackArgument & callback_argument)36 void IncrementerThread(CallbackArgument &callback_argument) {
37 while (true) {
38 callback_argument.counter++;
39
40 if (mutex.try_lock()) {
41 mutex.unlock();
42 return;
43 }
44
45 std::this_thread::yield();
46 }
47 }
48
49 // This callback checks that IncrementerThread is suspended at the time of its
50 // execution.
Callback(const SuspendedThreadsList & suspended_threads_list,void * argument)51 void Callback(const SuspendedThreadsList &suspended_threads_list,
52 void *argument) {
53 CallbackArgument *callback_argument = (CallbackArgument *)argument;
54 callback_argument->callback_executed = true;
55 int counter_at_init = callback_argument->counter;
56 for (uptr i = 0; i < 1000; i++) {
57 std::this_thread::yield();
58 if (callback_argument->counter != counter_at_init) {
59 callback_argument->threads_stopped = false;
60 return;
61 }
62 }
63 callback_argument->threads_stopped = true;
64 }
65
TEST(StopTheWorld,SuspendThreadsSimple)66 TEST(StopTheWorld, SuspendThreadsSimple) {
67 CallbackArgument argument;
68 std::thread thread;
69 {
70 std::lock_guard<std::mutex> lock(mutex);
71 thread = std::thread(IncrementerThread, std::ref(argument));
72 StopTheWorld(&Callback, &argument);
73 }
74 EXPECT_TRUE(argument.callback_executed);
75 EXPECT_TRUE(argument.threads_stopped);
76 // argument is on stack, so we have to wait for the incrementer thread to
77 // terminate before we can return from this function.
78 ASSERT_NO_THROW(thread.join());
79 }
80
81 // A more comprehensive test where we spawn a bunch of threads while executing
82 // StopTheWorld in parallel.
83 static const uptr kThreadCount = 50;
84 static const uptr kStopWorldAfter = 10; // let this many threads spawn first
85
86 struct AdvancedCallbackArgument {
87 std::atomic_uintptr_t thread_index = {};
88 std::atomic_int counters[kThreadCount] = {};
89 std::thread threads[kThreadCount];
90 std::atomic_bool threads_stopped = {};
91 std::atomic_bool callback_executed = {};
92 };
93
AdvancedIncrementerThread(AdvancedCallbackArgument & callback_argument)94 void AdvancedIncrementerThread(AdvancedCallbackArgument &callback_argument) {
95 uptr this_thread_index = callback_argument.thread_index++;
96 // Spawn the next thread.
97 if (this_thread_index + 1 < kThreadCount) {
98 callback_argument.threads[this_thread_index + 1] =
99 std::thread(AdvancedIncrementerThread, std::ref(callback_argument));
100 }
101 // Do the actual work.
102 while (true) {
103 callback_argument.counters[this_thread_index]++;
104 if (mutex.try_lock()) {
105 mutex.unlock();
106 return;
107 }
108
109 std::this_thread::yield();
110 }
111 }
112
AdvancedCallback(const SuspendedThreadsList & suspended_threads_list,void * argument)113 void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list,
114 void *argument) {
115 AdvancedCallbackArgument *callback_argument =
116 (AdvancedCallbackArgument *)argument;
117 callback_argument->callback_executed = true;
118
119 int counters_at_init[kThreadCount];
120 for (uptr j = 0; j < kThreadCount; j++)
121 counters_at_init[j] = callback_argument->counters[j];
122 for (uptr i = 0; i < 10; i++) {
123 std::this_thread::yield();
124 for (uptr j = 0; j < kThreadCount; j++)
125 if (callback_argument->counters[j] != counters_at_init[j]) {
126 callback_argument->threads_stopped = false;
127 return;
128 }
129 }
130 callback_argument->threads_stopped = true;
131 }
132
TEST(StopTheWorld,SuspendThreadsAdvanced)133 TEST(StopTheWorld, SuspendThreadsAdvanced) {
134 AdvancedCallbackArgument argument;
135
136 {
137 std::lock_guard<std::mutex> lock(mutex);
138 argument.threads[0] =
139 std::thread(AdvancedIncrementerThread, std::ref(argument));
140 // Wait for several threads to spawn before proceeding.
141 while (argument.thread_index < kStopWorldAfter) std::this_thread::yield();
142 StopTheWorld(&AdvancedCallback, &argument);
143 EXPECT_TRUE(argument.callback_executed);
144 EXPECT_TRUE(argument.threads_stopped);
145
146 // Wait for all threads to spawn before we start terminating them.
147 while (argument.thread_index < kThreadCount) std::this_thread::yield();
148 }
149 // Signal the threads to terminate.
150 for (auto &t : argument.threads) t.join();
151 }
152
SegvCallback(const SuspendedThreadsList & suspended_threads_list,void * argument)153 static void SegvCallback(const SuspendedThreadsList &suspended_threads_list,
154 void *argument) {
155 *(volatile int *)0x1234 = 0;
156 }
157
158 # if SANITIZER_WINDOWS
159 # define MAYBE_SegvInCallback DISABLED_SegvInCallback
160 # else
161 # define MAYBE_SegvInCallback SegvInCallback
162 # endif
163
TEST(StopTheWorld,MAYBE_SegvInCallback)164 TEST(StopTheWorld, MAYBE_SegvInCallback) {
165 // Test that tracer thread catches SIGSEGV.
166 StopTheWorld(&SegvCallback, NULL);
167 }
168
169 } // namespace __sanitizer
170
171 #endif // SANITIZER_LINUX && defined(__x86_64__)
172