Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Marco Elver 2538 98.95% 12 70.59%
Thomas Gleixner 12 0.47% 1 5.88%
Heiko Carstens 6 0.23% 1 5.88%
Ingo Molnar 4 0.16% 1 5.88%
Steven Whitehouse 3 0.12% 1 5.88%
Zwane Mwaikambo 2 0.08% 1 5.88%
Total 2565 17


// 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);
}