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
26namespace __sanitizer {
27
28static std::mutex mutex;
29
30struct CallbackArgument {
31 std::atomic_int counter = {};
32 std::atomic_bool threads_stopped = {};
33 std::atomic_bool callback_executed = {};
34};
35
36void 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.
51void 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
66TEST(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.
83static const uptr kThreadCount = 50;
84static const uptr kStopWorldAfter = 10; // let this many threads spawn first
85
86struct 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
94void 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
113void 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
133TEST(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
153static 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
164TEST(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

source code of compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cpp