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_DECLARE(lock_chk);
19
20LOCK_CHK_CLASS_DECLARE_LOCAL(death_abba_class1);
21LOCK_CHK_CLASS_DECLARE_LOCAL(death_abba_class2);
22LOCK_CHK_CLASS_DECLARE_LOCAL(death_recurse_class);
23LOCK_CHK_CLASS_DECLARE_LOCAL(death_foreign_class);
24LOCK_CHK_CLASS_DECLARE_LOCAL(death_unbalanced_class);
25LOCK_CHK_CLASS_DECLARE_LOCAL(death_spin_class1);
26LOCK_CHK_CLASS_DECLARE_LOCAL(death_spin_class2);
27LOCK_CHK_CLASS_DECLARE_LOCAL(death_nmi_class);
28LOCK_CHK_CLASS_DECLARE_LOCAL(death_sleep_spin_class);
29LOCK_CHK_CLASS_DECLARE_LOCAL(death_exit_class);
30LOCK_CHK_CLASS_DECLARE_LOCAL(death_cross_rw_class);
31LOCK_CHK_CLASS_DECLARE_LOCAL(death_upgrade_rw_class);
32LOCK_CHK_CLASS_DECLARE_LOCAL(death_exhaust_class);
33LOCK_CHK_CLASS_DECLARE_LOCAL(death_assert_held_spin_class);
34LOCK_CHK_CLASS_DECLARE_LOCAL(death_assert_not_held_mutex_class);
35LOCK_CHK_CLASS_DECLARE_LOCAL(death_assert_held_foreign_qspin_class);
36LOCK_CHK_CLASS_DECLARE_LOCAL(death_assert_held_rw_wrong_mode_class);
37LOCK_CHK_CLASS_DECLARE_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 unused(arg);
46 mutex_lock(&death_abba_m2);
47 atomic_store_explicit(&death_abba_ready, true, memory_order_release);
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_DECLARE_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(name: "death_abba_worker", entry: death_abba_worker, NULL);
60 mutex_lock(&death_abba_m1);
61 while (!atomic_load_explicit(&death_abba_ready, memory_order_acquire))
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 */
70TEST_DECLARE_UNIT(lock_chk, death_recursive_mutex,
71 .enabled = TEST_STATE_DISABLED) {
72 struct mutex m;
73 mutex_init_chk(&m, LOCK_CHK_CLASS(death_recurse_class), LOCK_CHKD_FULL);
74 mutex_lock(&m);
75 mutex_lock(&m);
76 mutex_unlock(&m);
77 return TEST_SUCCESS;
78}
79
80/* Foreign unlock */
81static struct mutex death_foreign_m;
82
83static void death_foreign_worker(void *arg) {
84 unused(arg);
85 mutex_unlock(&death_foreign_m);
86}
87
88TEST_DECLARE_UNIT(lock_chk, death_foreign_unlock,
89 .enabled = TEST_STATE_DISABLED) {
90 mutex_init_chk(&death_foreign_m, LOCK_CHK_CLASS(death_foreign_class),
91 LOCK_CHKD_FULL);
92 mutex_lock(&death_foreign_m);
93 struct thread *th =
94 thread_spawn_joinable(name: "death_foreign", entry: death_foreign_worker, NULL);
95 thread_join(t: th);
96 mutex_unlock(&death_foreign_m);
97 return TEST_SUCCESS;
98}
99
100/* Unbalanced unlock */
101TEST_DECLARE_UNIT(lock_chk, death_unbalanced_unlock,
102 .enabled = TEST_STATE_DISABLED) {
103 struct mutex m;
104 mutex_init_chk(&m, LOCK_CHK_CLASS(death_unbalanced_class), LOCK_CHKD_FULL);
105 mutex_unlock(&m);
106 return TEST_SUCCESS;
107}
108
109/* Non-LIFO spinlock unlock */
110TEST_DECLARE_UNIT(lock_chk, death_spin_order, .enabled = TEST_STATE_DISABLED) {
111 struct spinlock s1, s2;
112 spinlock_init_chk(&s1, LOCK_CHK_CLASS(death_spin_class1), LOCK_CHKD_FULL);
113 spinlock_init_chk(&s2, LOCK_CHK_CLASS(death_spin_class2), LOCK_CHKD_FULL);
114 enum irql i1 = spin_lock(&s1);
115 enum irql i2 = spin_lock(&s2);
116 spin_unlock(&s1, i1);
117 spin_unlock(&s2, i2);
118 return TEST_SUCCESS;
119}
120
121/* Inconsistent IRQ safety usage */
122TEST_DECLARE_UNIT(lock_chk, death_irq_unsafe_spin,
123 .enabled = TEST_STATE_DISABLED) {
124 struct spinlock s;
125 spinlock_init_chk(&s, LOCK_CHK_CLASS(death_spin_class1), LOCK_CHKD_FULL);
126 enum irql old = spin_lock(&s);
127 spin_unlock(&s, old);
128 old = spin_lock_irq_disable(&s);
129 spin_unlock(&s, old);
130 return TEST_SUCCESS;
131}
132
133/* lock acquire in NMI */
134TEST_DECLARE_UNIT(lock_chk, death_checked_in_nmi,
135 .enabled = TEST_STATE_DISABLED) {
136#ifdef DEBUG_LOCK_CHK
137 struct lock_chk_map map =
138 LOCK_CHK_MAP_VALUE_INIT(LOCK_CHK_CLASS(death_nmi_class));
139 struct lock_chk_acquire_token tok;
140 struct lock_chk_acquire_request req = {
141 .map = &map,
142 .instance = &map,
143 .flags = LOCK_CHKD_FULL,
144 .type = LOCK_CHK_TYPE_SPIN,
145 .mode = LOCK_CHK_MODE_EXCLUSIVE,
146 .wait_kind = LOCK_CHK_WAIT_BLOCKING,
147 .in_nmi = true,
148 .site = LOCK_CHK_SITE_HERE(),
149 };
150 lock_chk_before_acquire(&tok, &req);
151#endif
152 return TEST_SUCCESS;
153}
154
155/* Assert schedulable / sleep while holding a thread checked spinlock */
156TEST_DECLARE_UNIT(lock_chk, death_sleep_holding_spin,
157 .enabled = TEST_STATE_DISABLED) {
158 struct spinlock s;
159 spinlock_init_chk(&s, LOCK_CHK_CLASS(death_sleep_spin_class),
160 LOCK_CHKD_FULL);
161 enum irql old = spin_lock(&s);
162 lock_chk_assert_schedulable(LOCK_CHK_SITE_HERE());
163 spin_unlock(&s, old);
164 return TEST_SUCCESS;
165}
166
167/* Thread exit while holding a thread checked lock */
168static void death_exit_worker(void *arg) {
169 unused(arg);
170 static struct mutex exit_m;
171 mutex_init_chk(&exit_m, LOCK_CHK_CLASS(death_exit_class), LOCK_CHKD_FULL);
172 mutex_lock(&exit_m);
173 /* Worker function returns without unlocking */
174}
175
176TEST_DECLARE_UNIT(lock_chk, death_exit_holding_lock,
177 .enabled = TEST_STATE_DISABLED) {
178 struct thread *th =
179 thread_spawn_joinable(name: "death_exit_worker", entry: death_exit_worker, NULL);
180 thread_join(t: th);
181 return TEST_SUCCESS;
182}
183
184/* Cross thread release on checked reader lock */
185static struct rwlock death_cross_rw;
186
187static void death_cross_rw_worker(void *arg) {
188 unused(arg);
189 rw_unlock(&death_cross_rw);
190}
191
192TEST_DECLARE_UNIT(lock_chk, death_rw_cross_thread_release,
193 .enabled = TEST_STATE_DISABLED) {
194 rwlock_init_chk(&death_cross_rw, THREAD_PRIO_CLASS_TIMESHARE,
195 LOCK_CHK_CLASS(death_cross_rw_class), LOCK_CHKD_FULL);
196 rw_read_lock(&death_cross_rw);
197 struct thread *th =
198 thread_spawn_joinable(name: "death_cross_rw", entry: death_cross_rw_worker, NULL);
199 thread_join(t: th);
200 rw_unlock(&death_cross_rw);
201 return TEST_SUCCESS;
202}
203
204/* RW lock invalid upgrade / recursive acquire */
205TEST_DECLARE_UNIT(lock_chk, death_rw_invalid_upgrade,
206 .enabled = TEST_STATE_DISABLED) {
207 struct rwlock rw;
208 rwlock_init_chk(&rw, THREAD_PRIO_CLASS_TIMESHARE,
209 LOCK_CHK_CLASS(death_upgrade_rw_class), LOCK_CHKD_FULL);
210 rw_read_lock(&rw);
211 rw_write_lock(&rw);
212 rw_unlock(&rw);
213 rw_unlock(&rw);
214 return TEST_SUCCESS;
215}
216
217/* Uninitialized zero-filled lock usage */
218TEST_DECLARE_UNIT(lock_chk, death_uninitialized,
219 .enabled = TEST_STATE_DISABLED) {
220 struct mutex uninit_m = {0};
221 mutex_lock(&uninit_m);
222 return TEST_SUCCESS;
223}
224
225/* Exceed held capacity */
226TEST_DECLARE_UNIT(lock_chk, death_exhaust_held_capacity,
227 .enabled = TEST_STATE_DISABLED) {
228 struct mutex m[33];
229 for (int i = 0; i < 33; i++) {
230 mutex_init_chk(&m[i], LOCK_CHK_CLASS(death_exhaust_class),
231 LOCK_CHKD_THREAD);
232 mutex_lock(&m[i]);
233 }
234 for (int i = 32; i >= 0; i--)
235 mutex_unlock(&m[i]);
236 return TEST_SUCCESS;
237}
238
239TEST_DECLARE_UNIT(lock_chk, death_assert_held_spin_unheld,
240 .enabled = TEST_STATE_DISABLED) {
241 struct spinlock s;
242 spinlock_init_chk(&s, LOCK_CHK_CLASS(death_assert_held_spin_class),
243 LOCK_CHKD_FULL);
244 LOCK_CHK_ASSERT_HELD(&s);
245 return TEST_SUCCESS;
246}
247
248TEST_DECLARE_UNIT(lock_chk, death_assert_not_held_mutex_while_held,
249 .enabled = TEST_STATE_DISABLED) {
250 struct mutex m;
251 mutex_init_chk(&m, LOCK_CHK_CLASS(death_assert_not_held_mutex_class),
252 LOCK_CHKD_FULL);
253 mutex_lock(&m);
254 MUTEX_ASSERT_NOT_HELD(&m);
255 mutex_unlock(&m);
256 return TEST_SUCCESS;
257}
258
259static struct qspinlock death_foreign_qspin;
260static _Atomic bool death_foreign_qspin_held = false;
261
262static void death_foreign_qspin_worker(void *arg) {
263 unused(arg);
264 enum irql old = qspin_lock(&death_foreign_qspin);
265 atomic_store_explicit(&death_foreign_qspin_held, true,
266 memory_order_release);
267 sleep_spin_ms(msec: 2000);
268 qspin_unlock(&death_foreign_qspin, old);
269}
270
271TEST_DECLARE_UNIT(lock_chk, death_assert_held_qspin_foreign_owner,
272 .enabled = TEST_STATE_DISABLED) {
273 qspinlock_init_chk(&death_foreign_qspin,
274 LOCK_CHK_CLASS(death_assert_held_foreign_qspin_class),
275 LOCK_CHKD_FULL);
276 thread_spawn(name: "death_foreign_qspin_worker", entry: death_foreign_qspin_worker,
277 NULL);
278 while (
279 !atomic_load_explicit(&death_foreign_qspin_held, memory_order_acquire))
280 sleep_spin_ms(msec: 1);
281 QSPINLOCK_ASSERT_HELD(&death_foreign_qspin);
282 return TEST_SUCCESS;
283}
284
285TEST_DECLARE_UNIT(lock_chk, death_assert_held_rwlock_wrong_mode,
286 .enabled = TEST_STATE_DISABLED) {
287 struct rwlock rw;
288 rwlock_init_chk(&rw, THREAD_PRIO_CLASS_TIMESHARE,
289 LOCK_CHK_CLASS(death_assert_held_rw_wrong_mode_class),
290 LOCK_CHKD_FULL);
291 rw_read_lock(&rw);
292 LOCK_CHK_ASSERT_HELD(&rw, RWLOCK_ACQUIRE_WRITE);
293 rw_unlock(&rw);
294 return TEST_SUCCESS;
295}
296
297TEST_DECLARE_UNIT(lock_chk, death_assert_not_held_mutex_simple_while_held,
298 .enabled = TEST_STATE_DISABLED) {
299 struct mutex_simple m;
300 mutex_simple_init_chk(
301 &m, LOCK_CHK_CLASS(death_assert_not_held_mutex_simple_class),
302 LOCK_CHKD_FULL);
303 mutex_simple_lock(&m);
304 MUTEX_SIMPLE_ASSERT_NOT_HELD(&m);
305 mutex_simple_unlock(&m);
306 return TEST_SUCCESS;
307}
308