| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Compile-only tests for common patterns that should not generate false |
| * positive errors when compiled with Clang's context analysis. |
| */ |
| |
| #include <linux/bit_spinlock.h> |
| #include <linux/build_bug.h> |
| #include <linux/local_lock.h> |
| #include <linux/mutex.h> |
| #include <linux/percpu.h> |
| #include <linux/rcupdate.h> |
| #include <linux/rwsem.h> |
| #include <linux/seqlock.h> |
| #include <linux/spinlock.h> |
| #include <linux/srcu.h> |
| #include <linux/ww_mutex.h> |
| |
| /* |
| * Test that helper macros work as expected. |
| */ |
| static void __used test_common_helpers(void) |
| { |
| BUILD_BUG_ON(context_unsafe(3) != 3); /* plain expression */ |
| BUILD_BUG_ON(context_unsafe((void)2; 3) != 3); /* does not swallow semi-colon */ |
| BUILD_BUG_ON(context_unsafe((void)2, 3) != 3); /* does not swallow commas */ |
| context_unsafe(do { } while (0)); /* works with void statements */ |
| } |
| |
| #define TEST_SPINLOCK_COMMON(class, type, type_init, type_lock, type_unlock, type_trylock, op) \ |
| struct test_##class##_data { \ |
| type lock; \ |
| int counter __guarded_by(&lock); \ |
| int *pointer __pt_guarded_by(&lock); \ |
| }; \ |
| static void __used test_##class##_init(struct test_##class##_data *d) \ |
| { \ |
| guard(type_init)(&d->lock); \ |
| d->counter = 0; \ |
| } \ |
| static void __used test_##class(struct test_##class##_data *d) \ |
| { \ |
| unsigned long flags; \ |
| d->pointer++; \ |
| type_lock(&d->lock); \ |
| op(d->counter); \ |
| op(*d->pointer); \ |
| type_unlock(&d->lock); \ |
| type_lock##_irq(&d->lock); \ |
| op(d->counter); \ |
| op(*d->pointer); \ |
| type_unlock##_irq(&d->lock); \ |
| type_lock##_bh(&d->lock); \ |
| op(d->counter); \ |
| op(*d->pointer); \ |
| type_unlock##_bh(&d->lock); \ |
| type_lock##_irqsave(&d->lock, flags); \ |
| op(d->counter); \ |
| op(*d->pointer); \ |
| type_unlock##_irqrestore(&d->lock, flags); \ |
| } \ |
| static void __used test_##class##_trylock(struct test_##class##_data *d) \ |
| { \ |
| if (type_trylock(&d->lock)) { \ |
| op(d->counter); \ |
| type_unlock(&d->lock); \ |
| } \ |
| } \ |
| static void __used test_##class##_assert(struct test_##class##_data *d) \ |
| { \ |
| lockdep_assert_held(&d->lock); \ |
| op(d->counter); \ |
| } \ |
| static void __used test_##class##_guard(struct test_##class##_data *d) \ |
| { \ |
| { guard(class)(&d->lock); op(d->counter); } \ |
| { guard(class##_irq)(&d->lock); op(d->counter); } \ |
| { guard(class##_irqsave)(&d->lock); op(d->counter); } \ |
| } |
| |
| #define TEST_OP_RW(x) (x)++ |
| #define TEST_OP_RO(x) ((void)(x)) |
| |
| TEST_SPINLOCK_COMMON(raw_spinlock, |
| raw_spinlock_t, |
| raw_spinlock_init, |
| raw_spin_lock, |
| raw_spin_unlock, |
| raw_spin_trylock, |
| TEST_OP_RW); |
| static void __used test_raw_spinlock_trylock_extra(struct test_raw_spinlock_data *d) |
| { |
| unsigned long flags; |
| |
| data_race(d->counter++); /* no warning */ |
| |
| if (raw_spin_trylock_irq(&d->lock)) { |
| d->counter++; |
| raw_spin_unlock_irq(&d->lock); |
| } |
| if (raw_spin_trylock_irqsave(&d->lock, flags)) { |
| d->counter++; |
| raw_spin_unlock_irqrestore(&d->lock, flags); |
| } |
| scoped_cond_guard(raw_spinlock_try, return, &d->lock) { |
| d->counter++; |
| } |
| } |
| |
| TEST_SPINLOCK_COMMON(spinlock, |
| spinlock_t, |
| spinlock_init, |
| spin_lock, |
| spin_unlock, |
| spin_trylock, |
| TEST_OP_RW); |
| static void __used test_spinlock_trylock_extra(struct test_spinlock_data *d) |
| { |
| unsigned long flags; |
| |
| if (spin_trylock_irq(&d->lock)) { |
| d->counter++; |
| spin_unlock_irq(&d->lock); |
| } |
| if (spin_trylock_irqsave(&d->lock, flags)) { |
| d->counter++; |
| spin_unlock_irqrestore(&d->lock, flags); |
| } |
| scoped_cond_guard(spinlock_try, return, &d->lock) { |
| d->counter++; |
| } |
| } |
| |
| TEST_SPINLOCK_COMMON(write_lock, |
| rwlock_t, |
| rwlock_init, |
| write_lock, |
| write_unlock, |
| write_trylock, |
| TEST_OP_RW); |
| static void __used test_write_trylock_extra(struct test_write_lock_data *d) |
| { |
| unsigned long flags; |
| |
| if (write_trylock_irqsave(&d->lock, flags)) { |
| d->counter++; |
| write_unlock_irqrestore(&d->lock, flags); |
| } |
| } |
| |
| TEST_SPINLOCK_COMMON(read_lock, |
| rwlock_t, |
| rwlock_init, |
| read_lock, |
| read_unlock, |
| read_trylock, |
| TEST_OP_RO); |
| |
| struct test_mutex_data { |
| struct mutex mtx; |
| int counter __guarded_by(&mtx); |
| }; |
| |
| static void __used test_mutex_init(struct test_mutex_data *d) |
| { |
| guard(mutex_init)(&d->mtx); |
| d->counter = 0; |
| } |
| |
| static void __used test_mutex_lock(struct test_mutex_data *d) |
| { |
| mutex_lock(&d->mtx); |
| d->counter++; |
| mutex_unlock(&d->mtx); |
| mutex_lock_io(&d->mtx); |
| d->counter++; |
| mutex_unlock(&d->mtx); |
| } |
| |
| static void __used test_mutex_trylock(struct test_mutex_data *d, atomic_t *a) |
| { |
| if (!mutex_lock_interruptible(&d->mtx)) { |
| d->counter++; |
| mutex_unlock(&d->mtx); |
| } |
| if (!mutex_lock_killable(&d->mtx)) { |
| d->counter++; |
| mutex_unlock(&d->mtx); |
| } |
| if (mutex_trylock(&d->mtx)) { |
| d->counter++; |
| mutex_unlock(&d->mtx); |
| } |
| if (atomic_dec_and_mutex_lock(a, &d->mtx)) { |
| d->counter++; |
| mutex_unlock(&d->mtx); |
| } |
| } |
| |
| static void __used test_mutex_assert(struct test_mutex_data *d) |
| { |
| lockdep_assert_held(&d->mtx); |
| d->counter++; |
| } |
| |
| static void __used test_mutex_guard(struct test_mutex_data *d) |
| { |
| guard(mutex)(&d->mtx); |
| d->counter++; |
| } |
| |
| static void __used test_mutex_cond_guard(struct test_mutex_data *d) |
| { |
| scoped_cond_guard(mutex_try, return, &d->mtx) { |
| d->counter++; |
| } |
| scoped_cond_guard(mutex_intr, return, &d->mtx) { |
| d->counter++; |
| } |
| } |
| |
| struct test_seqlock_data { |
| seqlock_t sl; |
| int counter __guarded_by(&sl); |
| }; |
| |
| static void __used test_seqlock_init(struct test_seqlock_data *d) |
| { |
| guard(seqlock_init)(&d->sl); |
| d->counter = 0; |
| } |
| |
| static void __used test_seqlock_reader(struct test_seqlock_data *d) |
| { |
| unsigned int seq; |
| |
| do { |
| seq = read_seqbegin(&d->sl); |
| (void)d->counter; |
| } while (read_seqretry(&d->sl, seq)); |
| } |
| |
| static void __used test_seqlock_writer(struct test_seqlock_data *d) |
| { |
| unsigned long flags; |
| |
| write_seqlock(&d->sl); |
| d->counter++; |
| write_sequnlock(&d->sl); |
| |
| write_seqlock_irq(&d->sl); |
| d->counter++; |
| write_sequnlock_irq(&d->sl); |
| |
| write_seqlock_bh(&d->sl); |
| d->counter++; |
| write_sequnlock_bh(&d->sl); |
| |
| write_seqlock_irqsave(&d->sl, flags); |
| d->counter++; |
| write_sequnlock_irqrestore(&d->sl, flags); |
| } |
| |
| static void __used test_seqlock_scoped(struct test_seqlock_data *d) |
| { |
| scoped_seqlock_read (&d->sl, ss_lockless) { |
| (void)d->counter; |
| } |
| } |
| |
| struct test_rwsem_data { |
| struct rw_semaphore sem; |
| int counter __guarded_by(&sem); |
| }; |
| |
| static void __used test_rwsem_init(struct test_rwsem_data *d) |
| { |
| guard(rwsem_init)(&d->sem); |
| d->counter = 0; |
| } |
| |
| static void __used test_rwsem_reader(struct test_rwsem_data *d) |
| { |
| down_read(&d->sem); |
| (void)d->counter; |
| up_read(&d->sem); |
| |
| if (down_read_trylock(&d->sem)) { |
| (void)d->counter; |
| up_read(&d->sem); |
| } |
| } |
| |
| static void __used test_rwsem_writer(struct test_rwsem_data *d) |
| { |
| down_write(&d->sem); |
| d->counter++; |
| up_write(&d->sem); |
| |
| down_write(&d->sem); |
| d->counter++; |
| downgrade_write(&d->sem); |
| (void)d->counter; |
| up_read(&d->sem); |
| |
| if (down_write_trylock(&d->sem)) { |
| d->counter++; |
| up_write(&d->sem); |
| } |
| } |
| |
| static void __used test_rwsem_assert(struct test_rwsem_data *d) |
| { |
| rwsem_assert_held_nolockdep(&d->sem); |
| d->counter++; |
| } |
| |
| static void __used test_rwsem_guard(struct test_rwsem_data *d) |
| { |
| { guard(rwsem_read)(&d->sem); (void)d->counter; } |
| { guard(rwsem_write)(&d->sem); d->counter++; } |
| } |
| |
| static void __used test_rwsem_cond_guard(struct test_rwsem_data *d) |
| { |
| scoped_cond_guard(rwsem_read_try, return, &d->sem) { |
| (void)d->counter; |
| } |
| scoped_cond_guard(rwsem_write_try, return, &d->sem) { |
| d->counter++; |
| } |
| } |
| |
| struct test_bit_spinlock_data { |
| unsigned long bits; |
| int counter __guarded_by(__bitlock(3, &bits)); |
| }; |
| |
| static void __used test_bit_spin_lock(struct test_bit_spinlock_data *d) |
| { |
| /* |
| * Note, the analysis seems to have false negatives, because it won't |
| * precisely recognize the bit of the fake __bitlock() token. |
| */ |
| bit_spin_lock(3, &d->bits); |
| d->counter++; |
| bit_spin_unlock(3, &d->bits); |
| |
| bit_spin_lock(3, &d->bits); |
| d->counter++; |
| __bit_spin_unlock(3, &d->bits); |
| |
| if (bit_spin_trylock(3, &d->bits)) { |
| d->counter++; |
| bit_spin_unlock(3, &d->bits); |
| } |
| } |
| |
| /* |
| * Test that we can mark a variable guarded by RCU, and we can dereference and |
| * write to the pointer with RCU's primitives. |
| */ |
| struct test_rcu_data { |
| long __rcu_guarded *data; |
| }; |
| |
| static void __used test_rcu_guarded_reader(struct test_rcu_data *d) |
| { |
| rcu_read_lock(); |
| (void)rcu_dereference(d->data); |
| rcu_read_unlock(); |
| |
| rcu_read_lock_bh(); |
| (void)rcu_dereference(d->data); |
| rcu_read_unlock_bh(); |
| |
| rcu_read_lock_sched(); |
| (void)rcu_dereference(d->data); |
| rcu_read_unlock_sched(); |
| } |
| |
| static void __used test_rcu_guard(struct test_rcu_data *d) |
| { |
| guard(rcu)(); |
| (void)rcu_dereference(d->data); |
| } |
| |
| static void __used test_rcu_guarded_updater(struct test_rcu_data *d) |
| { |
| rcu_assign_pointer(d->data, NULL); |
| RCU_INIT_POINTER(d->data, NULL); |
| (void)unrcu_pointer(d->data); |
| } |
| |
| static void wants_rcu_held(void) __must_hold_shared(RCU) { } |
| static void wants_rcu_held_bh(void) __must_hold_shared(RCU_BH) { } |
| static void wants_rcu_held_sched(void) __must_hold_shared(RCU_SCHED) { } |
| |
| static void __used test_rcu_lock_variants(void) |
| { |
| rcu_read_lock(); |
| wants_rcu_held(); |
| rcu_read_unlock(); |
| |
| rcu_read_lock_bh(); |
| wants_rcu_held_bh(); |
| rcu_read_unlock_bh(); |
| |
| rcu_read_lock_sched(); |
| wants_rcu_held_sched(); |
| rcu_read_unlock_sched(); |
| } |
| |
| static void __used test_rcu_lock_reentrant(void) |
| { |
| rcu_read_lock(); |
| rcu_read_lock(); |
| rcu_read_lock_bh(); |
| rcu_read_lock_bh(); |
| rcu_read_lock_sched(); |
| rcu_read_lock_sched(); |
| |
| rcu_read_unlock_sched(); |
| rcu_read_unlock_sched(); |
| rcu_read_unlock_bh(); |
| rcu_read_unlock_bh(); |
| rcu_read_unlock(); |
| rcu_read_unlock(); |
| } |
| |
| static void __used test_rcu_assert_variants(void) |
| { |
| lockdep_assert_in_rcu_read_lock(); |
| wants_rcu_held(); |
| |
| lockdep_assert_in_rcu_read_lock_bh(); |
| wants_rcu_held_bh(); |
| |
| lockdep_assert_in_rcu_read_lock_sched(); |
| wants_rcu_held_sched(); |
| } |
| |
| struct test_srcu_data { |
| struct srcu_struct srcu; |
| long __rcu_guarded *data; |
| }; |
| |
| static void __used test_srcu(struct test_srcu_data *d) |
| { |
| init_srcu_struct(&d->srcu); |
| |
| int idx = srcu_read_lock(&d->srcu); |
| long *data = srcu_dereference(d->data, &d->srcu); |
| (void)data; |
| srcu_read_unlock(&d->srcu, idx); |
| |
| rcu_assign_pointer(d->data, NULL); |
| } |
| |
| static void __used test_srcu_guard(struct test_srcu_data *d) |
| { |
| { guard(srcu)(&d->srcu); (void)srcu_dereference(d->data, &d->srcu); } |
| { guard(srcu_fast)(&d->srcu); (void)srcu_dereference(d->data, &d->srcu); } |
| { guard(srcu_fast_notrace)(&d->srcu); (void)srcu_dereference(d->data, &d->srcu); } |
| } |
| |
| struct test_local_lock_data { |
| local_lock_t lock; |
| int counter __guarded_by(&lock); |
| }; |
| |
| static DEFINE_PER_CPU(struct test_local_lock_data, test_local_lock_data) = { |
| .lock = INIT_LOCAL_LOCK(lock), |
| }; |
| |
| static void __used test_local_lock_init(struct test_local_lock_data *d) |
| { |
| guard(local_lock_init)(&d->lock); |
| d->counter = 0; |
| } |
| |
| static void __used test_local_lock(void) |
| { |
| unsigned long flags; |
| |
| local_lock(&test_local_lock_data.lock); |
| this_cpu_add(test_local_lock_data.counter, 1); |
| local_unlock(&test_local_lock_data.lock); |
| |
| local_lock_irq(&test_local_lock_data.lock); |
| this_cpu_add(test_local_lock_data.counter, 1); |
| local_unlock_irq(&test_local_lock_data.lock); |
| |
| local_lock_irqsave(&test_local_lock_data.lock, flags); |
| this_cpu_add(test_local_lock_data.counter, 1); |
| local_unlock_irqrestore(&test_local_lock_data.lock, flags); |
| |
| local_lock_nested_bh(&test_local_lock_data.lock); |
| this_cpu_add(test_local_lock_data.counter, 1); |
| local_unlock_nested_bh(&test_local_lock_data.lock); |
| } |
| |
| static void __used test_local_lock_guard(void) |
| { |
| { guard(local_lock)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); } |
| { guard(local_lock_irq)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); } |
| { guard(local_lock_irqsave)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); } |
| { guard(local_lock_nested_bh)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); } |
| } |
| |
| struct test_local_trylock_data { |
| local_trylock_t lock; |
| int counter __guarded_by(&lock); |
| }; |
| |
| static DEFINE_PER_CPU(struct test_local_trylock_data, test_local_trylock_data) = { |
| .lock = INIT_LOCAL_TRYLOCK(lock), |
| }; |
| |
| static void __used test_local_trylock_init(struct test_local_trylock_data *d) |
| { |
| guard(local_trylock_init)(&d->lock); |
| d->counter = 0; |
| } |
| |
| static void __used test_local_trylock(void) |
| { |
| local_lock(&test_local_trylock_data.lock); |
| this_cpu_add(test_local_trylock_data.counter, 1); |
| local_unlock(&test_local_trylock_data.lock); |
| |
| if (local_trylock(&test_local_trylock_data.lock)) { |
| this_cpu_add(test_local_trylock_data.counter, 1); |
| local_unlock(&test_local_trylock_data.lock); |
| } |
| } |
| |
| static DEFINE_WD_CLASS(ww_class); |
| |
| struct test_ww_mutex_data { |
| struct ww_mutex mtx; |
| int counter __guarded_by(&mtx); |
| }; |
| |
| static void __used test_ww_mutex_lock_noctx(struct test_ww_mutex_data *d) |
| { |
| if (!ww_mutex_lock(&d->mtx, NULL)) { |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| } |
| |
| if (!ww_mutex_lock_interruptible(&d->mtx, NULL)) { |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| } |
| |
| if (ww_mutex_trylock(&d->mtx, NULL)) { |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| } |
| |
| ww_mutex_lock_slow(&d->mtx, NULL); |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| |
| ww_mutex_destroy(&d->mtx); |
| } |
| |
| static void __used test_ww_mutex_lock_ctx(struct test_ww_mutex_data *d) |
| { |
| struct ww_acquire_ctx ctx; |
| |
| ww_acquire_init(&ctx, &ww_class); |
| |
| if (!ww_mutex_lock(&d->mtx, &ctx)) { |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| } |
| |
| if (!ww_mutex_lock_interruptible(&d->mtx, &ctx)) { |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| } |
| |
| if (ww_mutex_trylock(&d->mtx, &ctx)) { |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| } |
| |
| ww_mutex_lock_slow(&d->mtx, &ctx); |
| d->counter++; |
| ww_mutex_unlock(&d->mtx); |
| |
| ww_acquire_done(&ctx); |
| ww_acquire_fini(&ctx); |
| |
| ww_mutex_destroy(&d->mtx); |
| } |