| // 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); | |
| struct mutex mtx2; | |
| int anyread __guarded_by(&mtx, &mtx2); | |
| int *anyptr __pt_guarded_by(&mtx, &mtx2); | |
| }; | |
| 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++; | |
| } | |
| } | |
| static void __used test_mutex_multiguard(struct test_mutex_data *d) | |
| { | |
| mutex_lock(&d->mtx); | |
| (void)d->anyread; | |
| (void)*d->anyptr; | |
| mutex_unlock(&d->mtx); | |
| mutex_lock(&d->mtx2); | |
| (void)d->anyread; | |
| (void)*d->anyptr; | |
| mutex_unlock(&d->mtx2); | |
| mutex_lock(&d->mtx); | |
| mutex_lock(&d->mtx2); | |
| d->anyread++; | |
| (*d->anyptr)++; | |
| mutex_unlock(&d->mtx2); | |
| mutex_unlock(&d->mtx); | |
| } | |
| 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); | |
| } | |
| static DEFINE_PER_CPU(raw_spinlock_t, test_per_cpu_lock); | |
| static void __used test_per_cpu(int cpu) | |
| { | |
| raw_spin_lock(&per_cpu(test_per_cpu_lock, cpu)); | |
| raw_spin_unlock(&per_cpu(test_per_cpu_lock, cpu)); | |
| raw_spin_lock(per_cpu_ptr(&test_per_cpu_lock, cpu)); | |
| raw_spin_unlock(per_cpu_ptr(&test_per_cpu_lock, cpu)); | |
| } |