1#include "sync/tests/test_internal.h"
2#include <asm.h>
3#include <sync/lock_chk.h>
4#include <sync/lock_chk_assert.h>
5#include <sync/mutex.h>
6#include <sync/mutex_simple.h>
7#include <sync/qspinlock.h>
8#include <sync/rwlock.h>
9#include <sync/spinlock.h>
10#include <test/test.h>
11#include <thread/thread.h>
12#include <time/spin_sleep.h>
13
14#ifdef DEBUG_LOCK_CHK
15#include "sync/lock_chk/internal.h"
16#endif /* DEBUG_LOCK_CHK */
17
18TEST_GROUP_DEFINE(lock_chk);
19
20LOCK_CHK_CLASS_DEFINE_LOCAL(death_abba_class1);
21LOCK_CHK_CLASS_DEFINE_LOCAL(death_abba_class2);
22LOCK_CHK_CLASS_DEFINE_LOCAL(death_recurse_class);
23LOCK_CHK_CLASS_DEFINE_LOCAL(death_foreign_class);
24LOCK_CHK_CLASS_DEFINE_LOCAL(death_unbalanced_class);
25LOCK_CHK_CLASS_DEFINE_LOCAL(death_spin_class1);
26LOCK_CHK_CLASS_DEFINE_LOCAL(death_spin_class2);
27LOCK_CHK_CLASS_DEFINE_LOCAL(death_nmi_class);
28LOCK_CHK_CLASS_DEFINE_LOCAL(death_sleep_spin_class);
29LOCK_CHK_CLASS_DEFINE_LOCAL(death_exit_class);
30LOCK_CHK_CLASS_DEFINE_LOCAL(death_cross_rw_class);
31LOCK_CHK_CLASS_DEFINE_LOCAL(death_upgrade_rw_class);
32LOCK_CHK_CLASS_DEFINE_LOCAL(death_exhaust_class);
33LOCK_CHK_CLASS_DEFINE_LOCAL(death_assert_held_spin_class);
34LOCK_CHK_CLASS_DEFINE_LOCAL(death_assert_not_held_mutex_class);
35LOCK_CHK_CLASS_DEFINE_LOCAL(death_assert_held_foreign_qspin_class);
36LOCK_CHK_CLASS_DEFINE_LOCAL(death_assert_held_rw_wrong_mode_class);
37LOCK_CHK_CLASS_DEFINE_LOCAL(death_assert_not_held_mutex_simple_class);
38
39/* ABBA cycle between two threads */
40static struct mutex death_abba_m1;
41static struct mutex death_abba_m2;
42static atomic_bool death_abba_ready = false;
43
44static void death_abba_worker(void *arg) {
45 cc_ignore(arg);
46 mutex_lock(&death_abba_m2);
47 atomic_store_release(&death_abba_ready, true);
48 sleep_spin_ms(msec: 10);
49 mutex_lock(&death_abba_m1);
50 mutex_unlock(&death_abba_m1);
51 mutex_unlock(&death_abba_m2);
52}
53
54TEST_DEFINE_UNIT(lock_chk, death_abba_mutex, .enabled = TEST_STATE_DISABLED) {
55 mutex_init_chk(&death_abba_m1, LOCK_CHK_CLASS(death_abba_class1),
56 LOCK_CHKD_FULL);
57 mutex_init_chk(&death_abba_m2, LOCK_CHK_CLASS(death_abba_class2),
58 LOCK_CHKD_FULL);
59 thread_spawn("death_abba_worker", death_abba_worker);
60 mutex_lock(&death_abba_m1);
61 while (!atomic_load_acq(&death_abba_ready))
62 sleep_spin_ms(msec: 1);
63 mutex_lock(&death_abba_m2);
64 mutex_unlock(&death_abba_m2);
65 mutex_unlock(&death_abba_m1);
66 return TEST_SUCCESS;
67}
68
69/* Recursive acquire */
70static void death_recursive_mutex_body(void) TSA_NO_ANALYSIS {
71 struct mutex m;
72 mutex_init_chk(&m, LOCK_CHK_CLASS(death_recurse_class), LOCK_CHKD_FULL);
73 mutex_lock(&m);
74 mutex_lock(&m);
75 mutex_unlock(&m);
76}
77
78TEST_DEFINE_UNIT(lock_chk, death_recursive_mutex,
79 .enabled = TEST_STATE_DISABLED) {
80 death_recursive_mutex_body();
81 return TEST_SUCCESS;
82}
83
84/* Foreign unlock */
85static struct mutex death_foreign_m;
86
87static void death_foreign_worker(void *arg) TSA_NO_ANALYSIS {
88 cc_ignore(arg);
89 mutex_unlock(&death_foreign_m);
90}
91
92TEST_DEFINE_UNIT(lock_chk, death_foreign_unlock,
93 .enabled = TEST_STATE_DISABLED) {
94 mutex_init_chk(&death_foreign_m, LOCK_CHK_CLASS(death_foreign_class),
95 LOCK_CHKD_FULL);
96 mutex_lock(&death_foreign_m);
97 struct thread *th =
98 thread_spawn("death_foreign", death_foreign_worker, .joinable = true);
99 thread_join(t: th);
100 mutex_unlock(&death_foreign_m);
101 return TEST_SUCCESS;
102}
103
104/* Unbalanced unlock */
105static void death_unbalanced_unlock_body(void) TSA_NO_ANALYSIS {
106 struct mutex m;
107 mutex_init_chk(&m, LOCK_CHK_CLASS(death_unbalanced_class), LOCK_CHKD_FULL);
108 mutex_unlock(&m);
109}
110
111TEST_DEFINE_UNIT(lock_chk, death_unbalanced_unlock,
112 .enabled = TEST_STATE_DISABLED) {
113 death_unbalanced_unlock_body();
114 return TEST_SUCCESS;
115}
116
117/* Non-LIFO spinlock unlock */
118TEST_DEFINE_UNIT(lock_chk, death_spin_order, .enabled = TEST_STATE_DISABLED) {
119 struct spinlock s1, s2;
120 spinlock_init_chk(&s1, LOCK_CHK_CLASS(death_spin_class1), LOCK_CHKD_FULL);
121 spinlock_init_chk(&s2, LOCK_CHK_CLASS(death_spin_class2), LOCK_CHKD_FULL);
122 enum irql i1 = spin_lock(&s1);
123 enum irql i2 = spin_lock(&s2);
124 spin_unlock(&s1, i1);
125 spin_unlock(&s2, i2);
126 return TEST_SUCCESS;
127}
128
129/* Inconsistent IRQ safety usage */
130TEST_DEFINE_UNIT(lock_chk, death_irq_unsafe_spin,
131 .enabled = TEST_STATE_DISABLED) {
132 struct spinlock s;
133 spinlock_init_chk(&s, LOCK_CHK_CLASS(death_spin_class1), LOCK_CHKD_FULL);
134 enum irql old = spin_lock(&s);
135 spin_unlock(&s, old);
136 old = spin_lock_high(&s);
137 spin_unlock(&s, old);
138 return TEST_SUCCESS;
139}
140
141/* lock acquire in NMI */
142TEST_DEFINE_UNIT(lock_chk, death_checked_in_nmi,
143 .enabled = TEST_STATE_DISABLED) {
144#ifdef DEBUG_LOCK_CHK
145 struct lock_chk_map map =
146 LOCK_CHK_MAP_VALUE_INIT(LOCK_CHK_CLASS(death_nmi_class));
147 struct lock_chk_acq_token tok;
148 struct lock_chk_acq_req req = {
149 .map = &map,
150 .lock = {.instance = &map,
151 .flags = LOCK_CHKD_FULL,
152 .type = LOCK_CHK_TYPE_SPIN},
153 .mode = LOCK_CHK_MODE_EXCLUSIVE,
154 .op_flags = LOCK_OP_KIND_BLOCKING,
155 .in_nmi = true,
156 .site = LOCK_CHK_SITE_HERE(),
157 };
158 lock_chk_before_acq(&tok, &req);
159#endif
160
161 return TEST_SUCCESS;
162}
163
164/* Assert schedulable / sleep while holding a thread checked spinlock */
165TEST_DEFINE_UNIT(lock_chk, death_sleep_holding_spin,
166 .enabled = TEST_STATE_DISABLED) {
167 struct spinlock s;
168 spinlock_init_chk(&s, LOCK_CHK_CLASS(death_sleep_spin_class),
169 LOCK_CHKD_FULL);
170 enum irql old = spin_lock(&s);
171 lock_chk_assert_schedulable(LOCK_CHK_SITE_HERE());
172 spin_unlock(&s, old);
173 return TEST_SUCCESS;
174}
175
176/* Thread exit while holding a thread checked lock */
177static void death_exit_worker(void *arg) TSA_NO_ANALYSIS {
178 cc_ignore(arg);
179 static struct mutex exit_m;
180 mutex_init_chk(&exit_m, LOCK_CHK_CLASS(death_exit_class), LOCK_CHKD_FULL);
181 mutex_lock(&exit_m);
182 /* Worker function returns without unlocking */
183}
184
185TEST_DEFINE_UNIT(lock_chk, death_exit_holding_lock,
186 .enabled = TEST_STATE_DISABLED) {
187 struct thread *th =
188 thread_spawn("death_exit_worker", death_exit_worker, .joinable = true);
189 thread_join(t: th);
190 return TEST_SUCCESS;
191}
192
193/* Cross thread release on checked reader lock */
194static struct rwlock death_cross_rw;
195
196static void death_cross_rw_worker(void *arg) TSA_NO_ANALYSIS {
197 cc_ignore(arg);
198 rw_unlock(&death_cross_rw);
199}
200
201TEST_DEFINE_UNIT(lock_chk, death_rw_cross_thread_release,
202 .enabled = TEST_STATE_DISABLED) {
203 rwlock_init_chk(&death_cross_rw, THREAD_PRIO_CLASS_TIMESHARE,
204 LOCK_CHK_CLASS(death_cross_rw_class), LOCK_CHKD_FULL);
205 rw_read_lock(&death_cross_rw);
206 struct thread *th =
207 thread_spawn("death_cross_rw", death_cross_rw_worker, .joinable = true);
208 thread_join(t: th);
209 rw_unlock(&death_cross_rw);
210 return TEST_SUCCESS;
211}
212
213/* RW lock invalid upgrade / recursive acquire */
214static void death_rw_invalid_upgrade_body(void) TSA_NO_ANALYSIS {
215 struct rwlock rw;
216 rwlock_init_chk(&rw, THREAD_PRIO_CLASS_TIMESHARE,
217 LOCK_CHK_CLASS(death_upgrade_rw_class), LOCK_CHKD_FULL);
218 rw_read_lock(&rw);
219 rw_write_lock(&rw);
220 rw_unlock(&rw);
221 rw_unlock(&rw);
222}
223
224TEST_DEFINE_UNIT(lock_chk, death_rw_invalid_upgrade,
225 .enabled = TEST_STATE_DISABLED) {
226 death_rw_invalid_upgrade_body();
227 return TEST_SUCCESS;
228}
229
230/* Uninitialized zero-filled lock usage */
231static void death_uninitialized_body(void) TSA_NO_ANALYSIS {
232 struct mutex uninit_m = {0};
233 mutex_lock(&uninit_m);
234}
235
236TEST_DEFINE_UNIT(lock_chk, death_uninitialized,
237 .enabled = TEST_STATE_DISABLED) {
238 death_uninitialized_body();
239 return TEST_SUCCESS;
240}
241
242/* Exceed held capacity */
243static void death_exhaust_held_capacity_body(void) TSA_NO_ANALYSIS {
244 struct mutex m[33];
245 for (int i = 0; i < 33; i++) {
246 mutex_init_chk(&m[i], LOCK_CHK_CLASS(death_exhaust_class),
247 LOCK_CHKD_THREAD);
248 mutex_lock(&m[i]);
249 }
250 for (int i = 32; i >= 0; i--)
251 mutex_unlock(&m[i]);
252}
253
254TEST_DEFINE_UNIT(lock_chk, death_exhaust_held_capacity,
255 .enabled = TEST_STATE_DISABLED) {
256 death_exhaust_held_capacity_body();
257 return TEST_SUCCESS;
258}
259
260TEST_DEFINE_UNIT(lock_chk, death_assert_held_spin_unheld,
261 .enabled = TEST_STATE_DISABLED) {
262 struct spinlock s;
263 spinlock_init_chk(&s, LOCK_CHK_CLASS(death_assert_held_spin_class),
264 LOCK_CHKD_FULL);
265 LOCK_CHK_ASSERT_HELD(&s);
266 return TEST_SUCCESS;
267}
268
269TEST_DEFINE_UNIT(lock_chk, death_assert_not_held_mutex_while_held,
270 .enabled = TEST_STATE_DISABLED) {
271 struct mutex m;
272 mutex_init_chk(&m, LOCK_CHK_CLASS(death_assert_not_held_mutex_class),
273 LOCK_CHKD_FULL);
274 mutex_lock(&m);
275 MUTEX_ASSERT_NOT_HELD(&m);
276 mutex_unlock(&m);
277 return TEST_SUCCESS;
278}
279
280static struct qspinlock death_foreign_qspin;
281static atomic_bool death_foreign_qspin_held = false;
282
283static void death_foreign_qspin_worker(void *arg) {
284 cc_ignore(arg);
285 enum irql old = qspin_lock(&death_foreign_qspin);
286 atomic_store_release(&death_foreign_qspin_held, true);
287 sleep_spin_ms(msec: 2000);
288 qspin_unlock(&death_foreign_qspin, old);
289}
290
291TEST_DEFINE_UNIT(lock_chk, death_assert_held_qspin_foreign_owner,
292 .enabled = TEST_STATE_DISABLED) {
293 qspinlock_init_chk(&death_foreign_qspin,
294 LOCK_CHK_CLASS(death_assert_held_foreign_qspin_class),
295 LOCK_CHKD_FULL);
296 thread_spawn("death_foreign_qspin_worker", death_foreign_qspin_worker);
297 while (!atomic_load_acq(&death_foreign_qspin_held))
298 sleep_spin_ms(msec: 1);
299 QSPINLOCK_ASSERT_HELD(&death_foreign_qspin);
300 return TEST_SUCCESS;
301}
302
303TEST_DEFINE_UNIT(lock_chk, death_assert_held_rwlock_wrong_mode,
304 .enabled = TEST_STATE_DISABLED) {
305 struct rwlock rw;
306 rwlock_init_chk(&rw, THREAD_PRIO_CLASS_TIMESHARE,
307 LOCK_CHK_CLASS(death_assert_held_rw_wrong_mode_class),
308 LOCK_CHKD_FULL);
309 rw_read_lock(&rw);
310 LOCK_CHK_ASSERT_HELD(&rw, RWLOCK_WRITE);
311 rw_unlock(&rw);
312 return TEST_SUCCESS;
313}
314
315TEST_DEFINE_UNIT(lock_chk, death_assert_not_held_mutex_simple_while_held,
316 .enabled = TEST_STATE_DISABLED) {
317 struct mutex_simple m;
318 mutex_simple_init_chk(
319 &m, LOCK_CHK_CLASS(death_assert_not_held_mutex_simple_class),
320 LOCK_CHKD_FULL);
321 mutex_simple_lock(&m);
322 MUTEX_SIMPLE_ASSERT_NOT_HELD(&m);
323 mutex_simple_unlock(&m);
324 return TEST_SUCCESS;
325}
326