#include <ctype.h>
#include "parse.h"
#include "smatch.h"
#include "smatch_extra.h"
#include "smatch_slist.h"
static int my_id;
STATE(locked);
STATE(half_locked);
STATE(start_state);
STATE(unlocked);
STATE(impossible);
STATE(restore);
enum action {
LOCK,
UNLOCK,
RESTORE,
};
enum lock_type {
spin_lock,
read_lock,
write_lock,
mutex,
bottom_half,
irq,
sem,
prepare_lock,
enable_lock,
};
const char *get_lock_name(enum lock_type type)
{
static const char *names[] = {
[spin_lock] = "spin_lock",
[read_lock] = "read_lock",
[write_lock] = "write_lock",
[mutex] = "mutex",
[bottom_half] = "bottom_half",
[irq] = "irq",
[sem] = "sem",
[prepare_lock] = "prepare_lock",
[enable_lock] = "enable_lock",
};
return names[type];
}
enum return_type {
ret_any,
ret_zero,
ret_one,
ret_negative,
ret_positive,
ret_valid_ptr,
};
#define RETURN_VAL -1
#define NO_ARG -2
struct lock_info {
const char *function;
enum action action;
enum lock_type type;
int arg;
enum return_type return_type;
};
static struct lock_info lock_table[] = {
{"spin_lock", LOCK, spin_lock, 0, ret_any},
{"spin_unlock", UNLOCK, spin_lock, 0, ret_any},
{"spin_lock_nested", LOCK, spin_lock, 0, ret_any},
{"_spin_lock", LOCK, spin_lock, 0, ret_any},
{"_spin_unlock", UNLOCK, spin_lock, 0, ret_any},
{"_spin_lock_nested", LOCK, spin_lock, 0, ret_any},
{"__spin_lock", LOCK, spin_lock, 0, ret_any},
{"__spin_unlock", UNLOCK, spin_lock, 0, ret_any},
{"__spin_lock_nested", LOCK, spin_lock, 0, ret_any},
{"raw_spin_lock", LOCK, spin_lock, 0, ret_any},
{"raw_spin_unlock", UNLOCK, spin_lock, 0, ret_any},
{"_raw_spin_lock", LOCK, spin_lock, 0, ret_any},
{"_raw_spin_lock_nested", LOCK, spin_lock, 0, ret_any},
{"_raw_spin_unlock", UNLOCK, spin_lock, 0, ret_any},
{"__raw_spin_lock", LOCK, spin_lock, 0, ret_any},
{"__raw_spin_unlock", UNLOCK, spin_lock, 0, ret_any},
{"spin_lock_irq", LOCK, spin_lock, 0, ret_any},
{"spin_unlock_irq", UNLOCK, spin_lock, 0, ret_any},
{"_spin_lock_irq", LOCK, spin_lock, 0, ret_any},
{"_spin_unlock_irq", UNLOCK, spin_lock, 0, ret_any},
{"__spin_lock_irq", LOCK, spin_lock, 0, ret_any},
{"__spin_unlock_irq", UNLOCK, spin_lock, 0, ret_any},
{"_raw_spin_lock_irq", LOCK, spin_lock, 0, ret_any},
{"_raw_spin_unlock_irq", UNLOCK, spin_lock, 0, ret_any},
{"__raw_spin_unlock_irq", UNLOCK, spin_lock, 0, ret_any},
{"spin_lock_irqsave", LOCK, spin_lock, 0, ret_any},
{"spin_unlock_irqrestore", UNLOCK, spin_lock, 0, ret_any},
{"_spin_lock_irqsave", LOCK, spin_lock, 0, ret_any},
{"_spin_unlock_irqrestore", UNLOCK, spin_lock, 0, ret_any},
{"__spin_lock_irqsave", LOCK, spin_lock, 0, ret_any},
{"__spin_unlock_irqrestore", UNLOCK, spin_lock, 0, ret_any},
{"_raw_spin_lock_irqsave", LOCK, spin_lock, 0, ret_any},
{"_raw_spin_unlock_irqrestore", UNLOCK, spin_lock, 0, ret_any},
{"__raw_spin_lock_irqsave", LOCK, spin_lock, 0, ret_any},
{"__raw_spin_unlock_irqrestore", UNLOCK, spin_lock, 0, ret_any},
{"spin_lock_irqsave_nested", LOCK, spin_lock, 0, ret_any},
{"_spin_lock_irqsave_nested", LOCK, spin_lock, 0, ret_any},
{"__spin_lock_irqsave_nested", LOCK, spin_lock, 0, ret_any},
{"_raw_spin_lock_irqsave_nested", LOCK, spin_lock, 0, ret_any},
{"spin_lock_bh", LOCK, spin_lock, 0, ret_any},
{"spin_unlock_bh", UNLOCK, spin_lock, 0, ret_any},
{"_spin_lock_bh", LOCK, spin_lock, 0, ret_any},
{"_spin_unlock_bh", UNLOCK, spin_lock, 0, ret_any},
{"__spin_lock_bh", LOCK, spin_lock, 0, ret_any},
{"__spin_unlock_bh", UNLOCK, spin_lock, 0, ret_any},
{"spin_trylock", LOCK, spin_lock, 0, ret_one},
{"_spin_trylock", LOCK, spin_lock, 0, ret_one},
{"__spin_trylock", LOCK, spin_lock, 0, ret_one},
{"raw_spin_trylock", LOCK, spin_lock, 0, ret_one},
{"_raw_spin_trylock", LOCK, spin_lock, 0, ret_one},
{"spin_trylock_irq", LOCK, spin_lock, 0, ret_one},
{"spin_trylock_irqsave", LOCK, spin_lock, 0, ret_one},
{"spin_trylock_bh", LOCK, spin_lock, 0, ret_one},
{"_spin_trylock_bh", LOCK, spin_lock, 0, ret_one},
{"__spin_trylock_bh", LOCK, spin_lock, 0, ret_one},
{"__raw_spin_trylock", LOCK, spin_lock, 0, ret_one},
{"_atomic_dec_and_lock", LOCK, spin_lock, 1, ret_one},
{"read_lock", LOCK, read_lock, 0, ret_any},
{"down_read", LOCK, read_lock, 0, ret_any},
{"down_read_nested", LOCK, read_lock, 0, ret_any},
{"down_read_trylock", LOCK, read_lock, 0, ret_one},
{"up_read", UNLOCK, read_lock, 0, ret_any},
{"read_unlock", UNLOCK, read_lock, 0, ret_any},
{"_read_lock", LOCK, read_lock, 0, ret_any},
{"_read_unlock", UNLOCK, read_lock, 0, ret_any},
{"__read_lock", LOCK, read_lock, 0, ret_any},
{"__read_unlock", UNLOCK, read_lock, 0, ret_any},
{"_raw_read_lock", LOCK, read_lock, 0, ret_any},
{"_raw_read_unlock", UNLOCK, read_lock, 0, ret_any},
{"__raw_read_lock", LOCK, read_lock, 0, ret_any},
{"__raw_read_unlock", UNLOCK, read_lock, 0, ret_any},
{"read_lock_irq", LOCK, read_lock, 0, ret_any},
{"read_unlock_irq" , UNLOCK, read_lock, 0, ret_any},
{"_read_lock_irq", LOCK, read_lock, 0, ret_any},
{"_read_unlock_irq", UNLOCK, read_lock, 0, ret_any},
{"__read_lock_irq", LOCK, read_lock, 0, ret_any},
{"__read_unlock_irq", UNLOCK, read_lock, 0, ret_any},
{"_raw_read_unlock_irq", UNLOCK, read_lock, 0, ret_any},
{"_raw_read_lock_irq", LOCK, read_lock, 0, ret_any},
{"_raw_read_lock_bh", LOCK, read_lock, 0, ret_any},
{"_raw_read_unlock_bh", UNLOCK, read_lock, 0, ret_any},
{"read_lock_irqsave", LOCK, read_lock, 0, ret_any},
{"read_unlock_irqrestore", UNLOCK, read_lock, 0, ret_any},
{"_read_lock_irqsave", LOCK, read_lock, 0, ret_any},
{"_read_unlock_irqrestore", UNLOCK, read_lock, 0, ret_any},
{"__read_lock_irqsave", LOCK, read_lock, 0, ret_any},
{"__read_unlock_irqrestore", UNLOCK, read_lock, 0, ret_any},
{"read_lock_bh", LOCK, read_lock, 0, ret_any},
{"read_unlock_bh", UNLOCK, read_lock, 0, ret_any},
{"_read_lock_bh", LOCK, read_lock, 0, ret_any},
{"_read_unlock_bh", UNLOCK, read_lock, 0, ret_any},
{"__read_lock_bh", LOCK, read_lock, 0, ret_any},
{"__read_unlock_bh", UNLOCK, read_lock, 0, ret_any},
{"__raw_read_lock_bh", LOCK, read_lock, 0, ret_any},
{"__raw_read_unlock_bh", UNLOCK, read_lock, 0, ret_any},
{"_raw_read_lock_irqsave", LOCK, read_lock, 0, ret_any},
{"_raw_read_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"_raw_read_unlock_irqrestore", UNLOCK, read_lock, 0, ret_any},
{"_raw_read_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"_raw_spin_lock_bh", LOCK, read_lock, 0, ret_any},
{"_raw_spin_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"_raw_spin_lock_nest_lock", LOCK, read_lock, 0, ret_any},
{"_raw_spin_unlock_bh", UNLOCK, read_lock, 0, ret_any},
{"_raw_spin_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_raw_write_lock_irqsave", LOCK, write_lock, 0, ret_any},
{"_raw_write_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"_raw_write_unlock_irqrestore", UNLOCK, write_lock, 0, ret_any},
{"_raw_write_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"__raw_write_unlock_irqrestore", UNLOCK, write_lock, 0, ret_any},
{"__raw_write_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"generic__raw_read_trylock", LOCK, read_lock, 0, ret_one},
{"read_trylock", LOCK, read_lock, 0, ret_one},
{"_read_trylock", LOCK, read_lock, 0, ret_one},
{"raw_read_trylock", LOCK, read_lock, 0, ret_one},
{"_raw_read_trylock", LOCK, read_lock, 0, ret_one},
{"__raw_read_trylock", LOCK, read_lock, 0, ret_one},
{"__read_trylock", LOCK, read_lock, 0, ret_one},
{"write_lock", LOCK, write_lock, 0, ret_any},
{"down_write", LOCK, write_lock, 0, ret_any},
{"down_write_nested", LOCK, write_lock, 0, ret_any},
{"up_write", UNLOCK, write_lock, 0, ret_any},
{"write_unlock", UNLOCK, write_lock, 0, ret_any},
{"_write_lock", LOCK, write_lock, 0, ret_any},
{"_write_unlock", UNLOCK, write_lock, 0, ret_any},
{"__write_lock", LOCK, write_lock, 0, ret_any},
{"__write_unlock", UNLOCK, write_lock, 0, ret_any},
{"write_lock_irq", LOCK, write_lock, 0, ret_any},
{"write_unlock_irq", UNLOCK, write_lock, 0, ret_any},
{"_write_lock_irq", LOCK, write_lock, 0, ret_any},
{"_write_unlock_irq", UNLOCK, write_lock, 0, ret_any},
{"__write_lock_irq", LOCK, write_lock, 0, ret_any},
{"__write_unlock_irq", UNLOCK, write_lock, 0, ret_any},
{"_raw_write_unlock_irq", UNLOCK, write_lock, 0, ret_any},
{"write_lock_irqsave", LOCK, write_lock, 0, ret_any},
{"write_unlock_irqrestore", UNLOCK, write_lock, 0, ret_any},
{"_write_lock_irqsave", LOCK, write_lock, 0, ret_any},
{"_write_unlock_irqrestore", UNLOCK, write_lock, 0, ret_any},
{"__write_lock_irqsave", LOCK, write_lock, 0, ret_any},
{"__write_unlock_irqrestore", UNLOCK, write_lock, 0, ret_any},
{"write_lock_bh", LOCK, write_lock, 0, ret_any},
{"write_unlock_bh", UNLOCK, write_lock, 0, ret_any},
{"_write_lock_bh", LOCK, write_lock, 0, ret_any},
{"_write_unlock_bh", UNLOCK, write_lock, 0, ret_any},
{"__write_lock_bh", LOCK, write_lock, 0, ret_any},
{"__write_unlock_bh", UNLOCK, write_lock, 0, ret_any},
{"_raw_write_lock", LOCK, write_lock, 0, ret_any},
{"__raw_write_lock", LOCK, write_lock, 0, ret_any},
{"_raw_write_unlock", UNLOCK, write_lock, 0, ret_any},
{"__raw_write_unlock", UNLOCK, write_lock, 0, ret_any},
{"_raw_write_lock_bh", LOCK, write_lock, 0, ret_any},
{"_raw_write_unlock_bh", UNLOCK, write_lock, 0, ret_any},
{"_raw_write_lock_irq", LOCK, write_lock, 0, ret_any},
{"write_trylock", LOCK, write_lock, 0, ret_one},
{"_write_trylock", LOCK, write_lock, 0, ret_one},
{"raw_write_trylock", LOCK, write_lock, 0, ret_one},
{"_raw_write_trylock", LOCK, write_lock, 0, ret_one},
{"__write_trylock", LOCK, write_lock, 0, ret_one},
{"__raw_write_trylock", LOCK, write_lock, 0, ret_one},
{"down_write_trylock", LOCK, write_lock, 0, ret_one},
{"down_write_killable", LOCK, write_lock, 0, ret_zero},
{"down", LOCK, sem, 0, ret_any},
{"up", UNLOCK, sem, 0, ret_any},
{"down_trylock", LOCK, sem, 0, ret_zero},
{"down_timeout", LOCK, sem, 0, ret_zero},
{"down_interruptible", LOCK, sem, 0, ret_zero},
{"down_killable", LOCK, sem, 0, ret_zero},
{"mutex_lock", LOCK, mutex, 0, ret_any},
{"mutex_unlock", UNLOCK, mutex, 0, ret_any},
{"mutex_lock_nested", LOCK, mutex, 0, ret_any},
{"mutex_lock_io", LOCK, mutex, 0, ret_any},
{"mutex_lock_io_nested", LOCK, mutex, 0, ret_any},
{"mutex_lock_interruptible", LOCK, mutex, 0, ret_zero},
{"mutex_lock_interruptible_nested", LOCK, mutex, 0, ret_zero},
{"mutex_lock_killable", LOCK, mutex, 0, ret_zero},
{"mutex_lock_killable_nested", LOCK, mutex, 0, ret_zero},
{"mutex_trylock", LOCK, mutex, 0, ret_one},
{"ww_mutex_lock", LOCK, mutex, 0, ret_any},
{"__ww_mutex_lock", LOCK, mutex, 0, ret_any},
{"ww_mutex_lock_interruptible", LOCK, mutex, 0, ret_zero},
{"ww_mutex_unlock", UNLOCK, mutex, 0, ret_any},
{"raw_local_irq_disable", LOCK, irq, NO_ARG, ret_any},
{"raw_local_irq_enable", UNLOCK, irq, NO_ARG, ret_any},
{"spin_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"spin_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"_spin_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"_spin_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"__spin_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"__spin_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"_raw_spin_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"_raw_spin_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"__raw_spin_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"spin_trylock_irq", LOCK, irq, NO_ARG, ret_one},
{"read_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"read_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"_read_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"_read_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"__read_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"_raw_read_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"__read_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"_raw_read_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"write_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"write_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"_write_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"_write_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"__write_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"__write_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"_raw_write_lock_irq", LOCK, irq, NO_ARG, ret_any},
{"_raw_write_unlock_irq", UNLOCK, irq, NO_ARG, ret_any},
{"arch_local_irq_save", LOCK, irq, RETURN_VAL, ret_any},
{"arch_local_irq_restore", RESTORE, irq, 0, ret_any},
{"__raw_local_irq_save", LOCK, irq, RETURN_VAL, ret_any},
{"raw_local_irq_restore", RESTORE, irq, 0, ret_any},
{"spin_lock_irqsave_nested", LOCK, irq, RETURN_VAL, ret_any},
{"spin_lock_irqsave", LOCK, irq, 1, ret_any},
{"spin_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"_spin_lock_irqsave_nested", LOCK, irq, RETURN_VAL, ret_any},
{"_spin_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"_spin_lock_irqsave", LOCK, irq, 1, ret_any},
{"_spin_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"__spin_lock_irqsave_nested", LOCK, irq, 1, ret_any},
{"__spin_lock_irqsave", LOCK, irq, 1, ret_any},
{"__spin_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"_raw_spin_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"_raw_spin_lock_irqsave", LOCK, irq, 1, ret_any},
{"_raw_spin_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"__raw_spin_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"__raw_spin_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"_raw_spin_lock_irqsave_nested", LOCK, irq, RETURN_VAL, ret_any},
{"spin_trylock_irqsave", LOCK, irq, 1, ret_one},
{"read_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"read_lock_irqsave", LOCK, irq, 1, ret_any},
{"read_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"_read_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"_read_lock_irqsave", LOCK, irq, 1, ret_any},
{"_read_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"__read_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"__read_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"write_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"write_lock_irqsave", LOCK, irq, 1, ret_any},
{"write_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"_write_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"_write_lock_irqsave", LOCK, irq, 1, ret_any},
{"_write_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"__write_lock_irqsave", LOCK, irq, RETURN_VAL, ret_any},
{"__write_unlock_irqrestore", RESTORE, irq, 1, ret_any},
{"local_bh_disable", LOCK, bottom_half, NO_ARG, ret_any},
{"_local_bh_disable", LOCK, bottom_half, NO_ARG, ret_any},
{"__local_bh_disable", LOCK, bottom_half, NO_ARG, ret_any},
{"local_bh_enable", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_local_bh_enable", UNLOCK, bottom_half, NO_ARG, ret_any},
{"__local_bh_enable", UNLOCK, bottom_half, NO_ARG, ret_any},
{"spin_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"spin_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_spin_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"_spin_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"__spin_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"__spin_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"read_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"read_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_read_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"_read_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"__read_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"__read_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_raw_read_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"_raw_read_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"write_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"write_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_write_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"_write_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"__write_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"__write_unlock_bh", UNLOCK, bottom_half, NO_ARG, ret_any},
{"_raw_write_lock_bh", LOCK, bottom_half, NO_ARG, ret_any},
{"_raw_write_unlock_bh",UNLOCK, bottom_half, NO_ARG, ret_any},
{"spin_trylock_bh", LOCK, bottom_half, NO_ARG, ret_one},
{"_spin_trylock_bh", LOCK, bottom_half, NO_ARG, ret_one},
{"__spin_trylock_bh", LOCK, bottom_half, NO_ARG, ret_one},
{"ffs_mutex_lock", LOCK, mutex, 0, ret_zero},
{"clk_prepare_lock", LOCK, prepare_lock, NO_ARG, ret_any},
{"clk_prepare_unlock", UNLOCK, prepare_lock, NO_ARG, ret_any},
{"clk_enable_lock", LOCK, enable_lock, -1, ret_any},
{"clk_enable_unlock", UNLOCK, enable_lock, 0, ret_any},
{"dma_resv_lock", LOCK, mutex, 0, ret_zero},
{"dma_resv_trylock", LOCK, mutex, 0, ret_one},
{"dma_resv_lock_interruptible", LOCK, mutex, 0, ret_zero},
{"dma_resv_unlock", UNLOCK, mutex, 0, ret_any},
{"modeset_lock", LOCK, mutex, 0, ret_zero},
{"drm_ modeset_lock", LOCK, mutex, 0, ret_zero},
{"drm_modeset_lock_single_interruptible", LOCK, mutex, 0, ret_zero},
{"modeset_unlock", UNLOCK, mutex, 0, ret_any},
{"reiserfs_write_lock_nested", LOCK, mutex, 0, ret_any},
{"reiserfs_write_unlock_nested", UNLOCK, mutex, 0, ret_any},
{"rw_lock", LOCK, write_lock, 1, ret_any},
{"rw_unlock", UNLOCK, write_lock, 1, ret_any},
{"sem_lock", LOCK, mutex, 0, ret_any},
{"sem_unlock", UNLOCK, mutex, 0, ret_any},
{},
};
struct macro_info {
const char *macro;
enum action action;
int param;
};
static struct macro_info macro_table[] = {
{"genpd_lock", LOCK, 0},
{"genpd_lock_nested", LOCK, 0},
{"genpd_lock_interruptible", LOCK, 0},
{"genpd_unlock", UNLOCK, 0},
};
static const char *false_positives[][2] = {
{"fs/jffs2/", "->alloc_sem"},
{"fs/xfs/", "->b_sema"},
{"mm/", "pvmw->ptl"},
};
static struct stree *start_states;
static struct stree_stack *saved_stack;
static struct tracker_list *locks;
static void reset(struct sm_state *sm, struct expression *mod_expr)
{
set_state(my_id, sm->name, sm->sym, &start_state);
}
static struct expression *remove_spinlock_check(struct expression *expr)
{
if (expr->type != EXPR_CALL)
return expr;
if (expr->fn->type != EXPR_SYMBOL)
return expr;
if (strcmp(expr->fn->symbol_name->name, "spinlock_check"))
return expr;
expr = get_argument_from_call_expr(expr->args, 0);
return expr;
}
static struct expression *filter_kernel_args(struct expression *arg)
{
if (arg->type == EXPR_PREOP && arg->op == '&')
return strip_expr(arg->unop);
if (!is_pointer(arg))
return arg;
return deref_expression(strip_expr(arg));
}
static char *lock_to_name_sym(struct expression *expr, struct symbol **sym)
{
expr = remove_spinlock_check(expr);
expr = filter_kernel_args(expr);
return expr_to_str_sym(expr, sym);
}
static char *get_full_name(struct expression *expr, int index, struct symbol **sym)
{
struct lock_info *lock = &lock_table[index];
struct expression *arg;
*sym = NULL;
if (lock->arg == RETURN_VAL) {
return expr_to_var_sym(strip_expr(expr->left), sym);
} else if (lock->arg == NO_ARG) {
return alloc_string(get_lock_name(lock->type));
} else {
arg = get_argument_from_call_expr(expr->args, lock->arg);
if (!arg)
return NULL;
return lock_to_name_sym(arg, sym);
}
}
static struct smatch_state *unmatched_state(struct sm_state *sm)
{
return &start_state;
}
static void pre_merge_hook(struct sm_state *cur, struct sm_state *other)
{
if (is_impossible_path())
set_state(my_id, cur->name, cur->sym, &impossible);
}
static struct smatch_state *merge_func(struct smatch_state *s1, struct smatch_state *s2)
{
if (s1 == &impossible)
return s2;
if (s2 == &impossible)
return s1;
return &merged;
}
static struct smatch_state *action_to_state(enum action lock_unlock)
{
switch (lock_unlock) {
case LOCK:
return &locked;
case UNLOCK:
return &unlocked;
case RESTORE:
return &restore;
}
return NULL;
}
static struct sm_state *get_best_match(const char *key, enum action lock_unlock)
{
struct sm_state *sm;
struct sm_state *match;
int cnt = 0;
int start_pos, state_len, key_len, chunks, i;
if (strncmp(key, "$->", 3) == 0)
key += 3;
key_len = strlen(key);
chunks = 0;
for (i = key_len - 1; i > 0; i--) {
if (key[i] == '>' || key[i] == '.')
chunks++;
if (chunks == 2) {
key += (i + 1);
key_len = strlen(key);
break;
}
}
FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
if (((lock_unlock == UNLOCK || lock_unlock == RESTORE) &&
sm->state != &locked) ||
(lock_unlock == LOCK && sm->state != &unlocked))
continue;
state_len = strlen(sm->name);
if (state_len < key_len)
continue;
start_pos = state_len - key_len;
if ((start_pos == 0 || !isalnum(sm->name[start_pos - 1])) &&
strcmp(sm->name + start_pos, key) == 0) {
cnt++;
match = sm;
}
} END_FOR_EACH_SM(sm);
if (cnt == 1)
return match;
return NULL;
}
static void use_best_match(char *key, enum action lock_unlock)
{
struct sm_state *match;
match = get_best_match(key, lock_unlock);
if (match)
set_state(my_id, match->name, match->sym, action_to_state(lock_unlock));
else
set_state(my_id, key, NULL, action_to_state(lock_unlock));
}
static void set_start_state(const char *name, struct symbol *sym, struct smatch_state *start)
{
struct smatch_state *orig;
orig = get_state_stree(start_states, my_id, name, sym);
if (!orig)
set_state_stree(&start_states, my_id, name, sym, start);
else if (orig != start)
set_state_stree(&start_states, my_id, name, sym, &undefined);
}
static bool common_false_positive(const char *name)
{
const char *path, *lname;
int i, len_total, len_path, len_name, skip;
if (!get_filename())
return false;
len_total = strlen(name);
for (i = 0; i < ARRAY_SIZE(false_positives); i++) {
path = false_positives[i][0];
lname = false_positives[i][1];
len_path = strlen(path);
len_name = strlen(lname);
if (len_name > len_total)
continue;
skip = len_total - len_name;
if (strncmp(get_filename(), path, len_path) == 0 &&
strcmp(name + skip, lname) == 0)
return true;
}
return false;
}
static void warn_on_double(struct sm_state *sm, struct smatch_state *state)
{
struct sm_state *tmp;
if (!sm)
return;
FOR_EACH_PTR(sm->possible, tmp) {
if (tmp->state == state)
goto found;
} END_FOR_EACH_PTR(tmp);
return;
found:
if (strcmp(sm->name, "bottom_half") == 0)
return;
if (common_false_positive(sm->name))
return;
sm_msg("error: double %s '%s' (orig line %u)",
state->name, sm->name, tmp->line);
}
static bool handle_macro_lock_unlock(void)
{
struct expression *expr, *arg;
struct macro_info *info;
struct sm_state *sm;
struct symbol *sym;
const char *macro;
char *name;
bool ret = false;
int i;
expr = last_ptr_list((struct ptr_list *)big_expression_stack);
while (expr && expr->type == EXPR_ASSIGNMENT)
expr = strip_expr(expr->right);
if (!expr || expr->type != EXPR_CALL)
return false;
macro = get_macro_name(expr->pos);
if (!macro)
return false;
for (i = 0; i < ARRAY_SIZE(macro_table); i++) {
info = ¯o_table[i];
if (strcmp(macro, info->macro) != 0)
continue;
arg = get_argument_from_call_expr(expr->args, info->param);
name = expr_to_str_sym(arg, &sym);
if (!name || !sym)
goto free;
sm = get_sm_state(my_id, name, sym);
if (info->action == LOCK) {
if (!sm)
set_start_state(name, sym, &unlocked);
if (sm && sm->line != expr->pos.line)
warn_on_double(sm, &locked);
set_state(my_id, name, sym, &locked);
} else {
if (!sm)
set_start_state(name, sym, &locked);
if (sm && sm->line != expr->pos.line)
warn_on_double(sm, &unlocked);
set_state(my_id, name, sym, &unlocked);
}
ret = true;
free:
free_string(name);
return ret;
}
return false;
}
static void do_lock(const char *name, struct symbol *sym, struct lock_info *info)
{
struct sm_state *sm;
if (handle_macro_lock_unlock())
return;
add_tracker(&locks, my_id, name, sym);
sm = get_sm_state(my_id, name, sym);
if (!sm)
set_start_state(name, sym, &unlocked);
warn_on_double(sm, &locked);
set_state(my_id, name, sym, &locked);
}
static void do_lock_failed(const char *name, struct symbol *sym)
{
add_tracker(&locks, my_id, name, sym);
set_state(my_id, name, sym, &unlocked);
}
static void do_unlock(const char *name, struct symbol *sym, struct lock_info *info)
{
struct sm_state *sm;
if (__path_is_null())
return;
if (handle_macro_lock_unlock())
return;
add_tracker(&locks, my_id, name, sym);
sm = get_sm_state(my_id, name, sym);
if (!sm) {
sm = get_best_match(name, UNLOCK);
if (sm) {
name = sm->name;
sym = sm->sym;
}
}
if (!sm)
set_start_state(name, sym, &locked);
warn_on_double(sm, &unlocked);
set_state(my_id, name, sym, &unlocked);
}
static void do_restore(const char *name, struct symbol *sym, struct lock_info *info)
{
if (__path_is_null())
return;
if (!get_state(my_id, name, sym))
set_start_state(name, sym, &locked);
add_tracker(&locks, my_id, name, sym);
set_state(my_id, name, sym, &restore);
}
static void match_lock_held(const char *fn, struct expression *call_expr,
struct expression *assign_expr, void *_index)
{
int index = PTR_INT(_index);
struct lock_info *lock = &lock_table[index];
char *lock_name;
struct symbol *sym;
if (lock->arg == NO_ARG) {
lock_name = get_full_name(NULL, index, &sym);
} else if (lock->arg == RETURN_VAL) {
if (!assign_expr)
return;
lock_name = get_full_name(assign_expr, index, &sym);
} else {
lock_name = get_full_name(call_expr, index, &sym);
}
if (!lock_name)
return;
do_lock(lock_name, sym, lock);
free_string(lock_name);
}
static void match_lock_failed(const char *fn, struct expression *call_expr,
struct expression *assign_expr, void *_index)
{
int index = PTR_INT(_index);
struct lock_info *lock = &lock_table[index];
char *lock_name;
struct symbol *sym;
if (lock->arg == NO_ARG) {
lock_name = get_full_name(NULL, index, &sym);
} else if (lock->arg == RETURN_VAL) {
if (!assign_expr)
return;
lock_name = get_full_name(assign_expr, index, &sym);
} else {
lock_name = get_full_name(call_expr, index, &sym);
}
if (!lock_name)
return;
do_lock_failed(lock_name, sym);
free_string(lock_name);
}
static void match_returns_locked(const char *fn, struct expression *expr,
void *_index)
{
int index = PTR_INT(_index);
struct lock_info *lock = &lock_table[index];
char *full_name;
struct symbol *sym;
if (lock->arg != RETURN_VAL)
return;
full_name = get_full_name(expr, index, &sym);
if (!full_name)
return;
do_lock(full_name, sym, lock);
}
static void match_lock_unlock(const char *fn, struct expression *expr, void *_index)
{
int index = PTR_INT(_index);
struct lock_info *lock = &lock_table[index];
char *full_name;
struct symbol *sym;
full_name = get_full_name(expr, index, &sym);
if (!full_name)
return;
switch (lock->action) {
case LOCK:
do_lock(full_name, sym, lock);
break;
case UNLOCK:
do_unlock(full_name, sym, lock);
break;
case RESTORE:
do_restore(full_name, sym, lock);
break;
}
free_string(full_name);
}
static struct smatch_state *get_start_state(struct sm_state *sm)
{
struct smatch_state *orig;
orig = get_state_stree(start_states, my_id, sm->name, sm->sym);
if (orig)
return orig;
return &undefined;
}
static int get_param_lock_name(struct sm_state *sm, struct expression *expr,
const char **name)
{
char *other_name;
struct symbol *other_sym;
const char *param_name;
int param;
*name = sm->name;
param = get_param_num_from_sym(sm->sym);
if (param >= 0) {
param_name = get_param_name(sm);
if (param_name)
*name = param_name;
return param;
}
if (expr) {
struct symbol *ret_sym;
char *ret_str;
ret_str = expr_to_str_sym(expr, &ret_sym);
if (ret_str && ret_sym == sm->sym) {
param_name = state_name_to_param_name(sm->name, ret_str);
if (param_name) {
free_string(ret_str);
*name = param_name;
return -1;
}
}
free_string(ret_str);
}
other_name = get_other_name_sym(sm->name, sm->sym, &other_sym);
if (!other_name)
return -2;
param = get_param_num_from_sym(other_sym);
if (param < 0)
return -2;
param_name = get_param_name_var_sym(other_name, other_sym);
free_string(other_name);
if (param_name)
*name = param_name;
return param;
}
static int get_db_type(struct sm_state *sm)
{
if (sm->state == get_start_state(sm)) {
if (sm->state == &locked)
return KNOWN_LOCKED;
if (sm->state == &unlocked)
return KNOWN_UNLOCKED;
}
if (sm->state == &locked)
return LOCKED;
if (sm->state == &unlocked)
return UNLOCKED;
if (sm->state == &restore)
return LOCK_RESTORED;
return LOCKED;
}
static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
{
struct sm_state *sm;
const char *param_name;
int param;
FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
if (sm->state != &locked &&
sm->state != &unlocked &&
sm->state != &restore)
continue;
param = get_param_lock_name(sm, expr, ¶m_name);
sql_insert_return_states(return_id, return_ranges,
get_db_type(sm),
param, param_name, "");
} END_FOR_EACH_SM(sm);
}
enum {
ERR_PTR, VALID_PTR, NEGATIVE, ZERO, POSITIVE, NUM_BUCKETS,
};
static bool is_EINTR(struct range_list *rl)
{
sval_t sval;
if (!rl_to_sval(rl, &sval))
return false;
return sval.value == -4;
}
static int success_fail_positive(struct range_list *rl)
{
if (!rl)
return ZERO;
if (rl_type(rl)->type != SYM_PTR && sval_is_negative(rl_min(rl)))
return NEGATIVE;
if (rl_min(rl).value == 0 && rl_max(rl).value == 0)
return ZERO;
if (is_err_ptr(rl_min(rl)) &&
is_err_ptr(rl_max(rl)))
return ERR_PTR;
if (type_bits(&long_ctype) == 64 &&
rl_type(rl)->type == SYM_PTR &&
rl_min(rl).value == INT_MIN)
return ERR_PTR;
return POSITIVE;
}
static bool sym_in_lock_table(struct symbol *sym)
{
int i;
if (!sym || !sym->ident)
return false;
for (i = 0; lock_table[i].function != NULL; i++) {
if (strcmp(lock_table[i].function, sym->ident->name) == 0)
return true;
}
return false;
}
static bool func_in_lock_table(struct expression *expr)
{
if (expr->type != EXPR_SYMBOL)
return false;
return sym_in_lock_table(expr->symbol);
}
static void check_lock(char *name, struct symbol *sym)
{
struct range_list *locked_lines = NULL;
struct range_list *unlocked_lines = NULL;
int locked_buckets[NUM_BUCKETS] = {};
int unlocked_buckets[NUM_BUCKETS] = {};
struct stree *stree, *orig;
struct sm_state *return_sm;
struct sm_state *sm;
sval_t line = sval_type_val(&int_ctype, 0);
int bucket;
int i;
if (sym_in_lock_table(cur_func_sym))
return;
FOR_EACH_PTR(get_all_return_strees(), stree) {
orig = __swap_cur_stree(stree);
if (is_impossible_path())
goto swap_stree;
return_sm = get_sm_state(RETURN_ID, "return_ranges", NULL);
if (!return_sm)
goto swap_stree;
line.value = return_sm->line;
sm = get_sm_state(my_id, name, sym);
if (!sm)
goto swap_stree;
if (parent_is_gone_var_sym(sm->name, sm->sym))
goto swap_stree;
if (sm->state != &locked &&
sm->state != &unlocked &&
sm->state != &restore)
goto swap_stree;
if ((sm->state == &unlocked || sm->state == &restore) &&
is_EINTR(estate_rl(return_sm->state)))
goto swap_stree;
bucket = success_fail_positive(estate_rl(return_sm->state));
if (sm->state == &locked) {
add_range(&locked_lines, line, line);
locked_buckets[bucket] = true;
}
if (sm->state == &unlocked || sm->state == &restore) {
add_range(&unlocked_lines, line, line);
unlocked_buckets[bucket] = true;
}
swap_stree:
__swap_cur_stree(orig);
} END_FOR_EACH_PTR(stree);
if (!locked_lines || !unlocked_lines)
return;
for (i = 0; i < NUM_BUCKETS; i++) {
if (locked_buckets[i] && unlocked_buckets[i])
goto complain;
}
if (locked_buckets[NEGATIVE] &&
(unlocked_buckets[ZERO] || unlocked_buckets[POSITIVE]))
goto complain;
if (locked_buckets[ERR_PTR])
goto complain;
return;
complain:
sm_msg("warn: inconsistent returns '%s'.", name);
sm_printf(" Locked on : %s\n", show_rl(locked_lines));
sm_printf(" Unlocked on: %s\n", show_rl(unlocked_lines));
}
static void match_func_end(struct symbol *sym)
{
struct tracker *tracker;
FOR_EACH_PTR(locks, tracker) {
check_lock(tracker->name, tracker->sym);
} END_FOR_EACH_PTR(tracker);
}
static void register_lock(int index)
{
struct lock_info *lock = &lock_table[index];
void *idx = INT_PTR(index);
if (lock->return_type == ret_one) {
return_implies_state(lock->function, 1, 1, &match_lock_held, idx);
return_implies_state(lock->function, 0, 0, &match_lock_failed, idx);
} else if (lock->return_type == ret_any && lock->arg == RETURN_VAL) {
add_function_assign_hook(lock->function, &match_returns_locked, idx);
} else if (lock->return_type == ret_any) {
add_function_hook(lock->function, &match_lock_unlock, idx);
} else if (lock->return_type == ret_zero) {
return_implies_state(lock->function, 0, 0, &match_lock_held, idx);
return_implies_state(lock->function, -4095, -1, &match_lock_failed, idx);
} else if (lock->return_type == ret_valid_ptr) {
return_implies_state_sval(lock->function, valid_ptr_min_sval, valid_ptr_max_sval, &match_lock_held, idx);
}
}
static void load_table(struct lock_info *lock_table)
{
int i;
for (i = 0; lock_table[i].function != NULL; i++) {
if (lock_table[i].action == LOCK)
register_lock(i);
else
add_function_hook(lock_table[i].function, &match_lock_unlock, INT_PTR(i));
}
}
static void db_param_locked_unlocked(struct expression *expr, int param, char *key, char *value, enum action lock_unlock)
{
struct expression *call, *arg;
char *name;
struct symbol *sym;
call = expr;
while (call->type == EXPR_ASSIGNMENT)
call = strip_expr(call->right);
if (call->type != EXPR_CALL)
return;
if (func_in_lock_table(call->fn))
return;
if (param == -2) {
use_best_match(key, lock_unlock);
return;
}
if (param == -1) {
if (expr->type != EXPR_ASSIGNMENT)
return;
name = get_variable_from_key(expr->left, key, &sym);
} else {
arg = get_argument_from_call_expr(call->args, param);
if (!arg)
return;
name = get_variable_from_key(arg, key, &sym);
}
if (!name || !sym)
goto free;
if (lock_unlock == LOCK)
do_lock(name, sym, NULL);
else if (lock_unlock == UNLOCK)
do_unlock(name, sym, NULL);
else if (lock_unlock == RESTORE)
do_restore(name, sym, NULL);
free:
free_string(name);
}
static void db_param_locked(struct expression *expr, int param, char *key, char *value)
{
db_param_locked_unlocked(expr, param, key, value, LOCK);
}
static void db_param_unlocked(struct expression *expr, int param, char *key, char *value)
{
db_param_locked_unlocked(expr, param, key, value, UNLOCK);
}
static void db_param_restore(struct expression *expr, int param, char *key, char *value)
{
db_param_locked_unlocked(expr, param, key, value, RESTORE);
}
static int get_caller_param_lock_name(struct expression *call, struct sm_state *sm, const char **name)
{
struct expression *arg;
char *arg_name;
int param;
param = 0;
FOR_EACH_PTR(call->args, arg) {
arg_name = sm_to_arg_name(arg, sm);
if (arg_name) {
*name = arg_name;
return param;
}
param++;
} END_FOR_EACH_PTR(arg);
*name = sm->name;
return -2;
}
static void match_call_info(struct expression *expr)
{
struct sm_state *sm;
const char *param_name;
int locked_type;
int param;
FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
param = get_caller_param_lock_name(expr, sm, ¶m_name);
if (sm->state == &locked)
locked_type = LOCKED;
else if (sm->state == &half_locked ||
slist_has_state(sm->possible, &locked))
locked_type = HALF_LOCKED;
else
continue;
sql_insert_caller_info(expr, locked_type, param, param_name, "xxx type");
} END_FOR_EACH_SM(sm);
}
static void match_save_states(struct expression *expr)
{
push_stree(&saved_stack, start_states);
start_states = NULL;
}
static void match_restore_states(struct expression *expr)
{
start_states = pop_stree(&saved_stack);
}
static void match_after_func(struct symbol *sym)
{
free_stree(&start_states);
}
static void match_dma_resv_lock_NULL(const char *fn, struct expression *call_expr,
struct expression *assign_expr, void *_index)
{
struct expression *lock, *ctx;
char *lock_name;
struct symbol *sym;
lock = get_argument_from_call_expr(call_expr->args, 0);
ctx = get_argument_from_call_expr(call_expr->args, 1);
if (!expr_is_zero(ctx))
return;
lock_name = lock_to_name_sym(lock, &sym);
if (!lock_name || !sym)
goto free;
do_lock(lock_name, sym, NULL);
free:
free_string(lock_name);
}
void print_held_locks(void)
{
struct stree *stree;
struct sm_state *sm;
int i = 0;
stree = __get_cur_stree();
FOR_EACH_MY_SM(my_id, stree, sm) {
if (sm->state != &locked)
continue;
if (i++)
sm_printf(" ");
sm_printf("'%s'", sm->name);
} END_FOR_EACH_SM(sm);
}
void check_locking(int id)
{
my_id = id;
if (option_project != PROJ_KERNEL)
return;
load_table(lock_table);
set_dynamic_states(my_id);
add_unmatched_state_hook(my_id, &unmatched_state);
add_pre_merge_hook(my_id, &pre_merge_hook);
add_merge_hook(my_id, &merge_func);
add_modification_hook(my_id, &reset);
add_hook(&match_func_end, END_FUNC_HOOK);
add_hook(&match_after_func, AFTER_FUNC_HOOK);
add_hook(&match_save_states, INLINE_FN_START);
add_hook(&match_restore_states, INLINE_FN_END);
add_hook(&match_call_info, FUNCTION_CALL_HOOK);
add_split_return_callback(match_return_info);
select_return_states_hook(LOCKED, &db_param_locked);
select_return_states_hook(UNLOCKED, &db_param_unlocked);
select_return_states_hook(LOCK_RESTORED, &db_param_restore);
return_implies_state("dma_resv_lock", -4095, -1, &match_dma_resv_lock_NULL, 0);
}