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