2 * sparse/check_locking.c
4 * Copyright (C) 2009 Dan Carpenter.
6 * Licensed under the Open Software License version 1.1
11 * This test checks that locks are held the same across all returns.
13 * Of course, some functions are designed to only hold the locks on success.
14 * Oh well... We can rewrite it later if we want.
19 #include "smatch_slist.h"
21 static const char *lock_funcs
[] = {
27 "_spin_lock_irqsave_nested",
34 "_write_lock_irqsave",
43 static const char *unlock_funcs
[] = {
45 "_spin_unlock_irqrestore",
50 "_read_unlock_irqrestore",
54 "_write_unlock_irqrestore",
62 /* These are return 1 if they aquire the lock */
63 static const char *conditional_funcs
[] = {
68 "generic__raw_read_trylock",
74 "__raw_write_trylock",
75 "__raw_write_trylock",
80 /* These functions return 0 on success */
81 static const char *reverse_cond_funcs
[] = {
84 "mutex_lock_interruptible",
85 "mutex_lock_interruptible_nested",
86 "mutex_lock_killable",
87 "mutex_lock_killable_nested",
91 /* todo still need to handle__raw_spin_is_locked */
98 static struct locked_call lock_needed
[] = {
99 {"tty_ldisc_ref_wait", "tty_ldisc_lock"},
104 static struct tracker_list
*starts_locked
;
105 static struct tracker_list
*starts_unlocked
;
107 struct locks_on_return
{
109 struct tracker_list
*locked
;
110 struct tracker_list
*unlocked
;
112 DECLARE_PTR_LIST(return_list
, struct locks_on_return
);
113 static struct return_list
*all_returns
;
119 static struct smatch_state
*get_start_state(struct sm_state
*sm
)
124 if (in_tracker_list(starts_locked
, sm
->name
, my_id
, sm
->sym
))
126 if (in_tracker_list(starts_unlocked
, sm
->name
, my_id
, sm
->sym
))
128 if (is_locked
&& is_unlocked
)
137 static struct smatch_state
*unmatched_state(struct sm_state
*sm
)
142 static char *match_func(const char *list
[], char *fn_name
,
143 struct expression_list
*args
)
145 struct expression
*lock_expr
;
148 for (i
= 0; list
[i
]; i
++) {
149 if (!strcmp(fn_name
, list
[i
])) {
150 lock_expr
= get_argument_from_call_expr(args
, 0);
151 return get_variable_from_expr(lock_expr
, NULL
);
157 static char *get_lock_name(struct expression
*expr
, void *data
)
159 struct expression
*lock_expr
;
162 return alloc_string((char *)data
);
164 lock_expr
= get_argument_from_call_expr(expr
->args
, 0);
165 return get_variable_from_expr(lock_expr
, NULL
);
169 static void match_lock_func(struct expression
*expr
, void *data
)
174 lock_name
= get_lock_name(expr
, data
);
177 sm
= get_sm_state(lock_name
, my_id
, NULL
);
179 add_tracker(&starts_unlocked
, lock_name
, my_id
, NULL
);
180 if (sm
&& slist_has_state(sm
->possible
, &locked
))
181 smatch_msg("error: double lock '%s'", lock_name
);
182 set_state(lock_name
, my_id
, NULL
, &locked
);
183 free_string(lock_name
);
186 static void match_unlock_func(struct expression
*expr
, void *data
)
191 lock_name
= get_lock_name(expr
, data
);
194 sm
= get_sm_state(lock_name
, my_id
, NULL
);
196 add_tracker(&starts_locked
, lock_name
, my_id
, NULL
);
197 if (sm
&& slist_has_state(sm
->possible
, &unlocked
))
198 smatch_msg("error: double unlock '%s'", lock_name
);
199 set_state(lock_name
, my_id
, NULL
, &unlocked
);
200 free_string(lock_name
);
203 static void match_lock_needed(struct expression
*expr
, void *data
)
205 struct smatch_state
*state
;
208 state
= get_state((char *)data
, my_id
, NULL
);
209 if (state
== &locked
)
211 fn_name
= get_variable_from_expr(expr
->fn
, NULL
);
213 smatch_msg("Internal error.");
216 smatch_msg("error: %s called without holding '%s' lock", fn_name
,
218 free_string(fn_name
);
221 static void match_condition(struct expression
*expr
)
226 if (expr
->type
!= EXPR_CALL
)
229 fn_name
= get_variable_from_expr(expr
->fn
, NULL
);
233 if ((lock_name
= match_func(conditional_funcs
, fn_name
, expr
->args
))) {
234 if (!get_state(lock_name
, my_id
, NULL
))
235 add_tracker(&starts_unlocked
, lock_name
, my_id
, NULL
);
236 set_true_false_states(lock_name
, my_id
, NULL
, &locked
, &unlocked
);
238 if ((lock_name
= match_func(reverse_cond_funcs
, fn_name
, expr
->args
))) {
239 if (!get_state(lock_name
, my_id
, NULL
))
240 add_tracker(&starts_unlocked
, lock_name
, my_id
, NULL
);
241 set_true_false_states(lock_name
, my_id
, NULL
, &unlocked
, &locked
);
243 free_string(lock_name
);
244 free_string(fn_name
);
248 static struct locks_on_return
*alloc_return(int line
)
250 struct locks_on_return
*ret
;
252 ret
= malloc(sizeof(*ret
));
255 ret
->unlocked
= NULL
;
259 static void check_possible(struct sm_state
*sm
)
261 struct sm_state
*tmp
;
266 FOR_EACH_PTR(sm
->possible
, tmp
) {
267 if (tmp
->state
== &locked
)
269 if (tmp
->state
== &unlocked
)
271 if (tmp
->state
== &start_state
) {
272 struct smatch_state
*s
;
274 s
= get_start_state(tmp
);
277 else if (s
== &unlocked
)
282 if (tmp
->state
== &undefined
)
283 undef
= 1; // i don't think this is possible any more.
284 } END_FOR_EACH_PTR(tmp
);
285 if ((islocked
&& isunlocked
) || undef
)
286 smatch_msg("warn: '%s' is sometimes locked here and "
287 "sometimes unlocked.", sm
->name
);
290 static void match_return(struct statement
*stmt
)
292 struct locks_on_return
*ret
;
293 struct state_list
*slist
;
294 struct sm_state
*tmp
;
296 ret
= alloc_return(get_lineno());
298 slist
= get_all_states(my_id
);
299 FOR_EACH_PTR(slist
, tmp
) {
300 if (tmp
->state
== &locked
) {
301 add_tracker(&ret
->locked
, tmp
->name
, tmp
->owner
,
303 } else if (tmp
->state
== &unlocked
) {
304 add_tracker(&ret
->unlocked
, tmp
->name
, tmp
->owner
,
306 } else if (tmp
->state
== &start_state
) {
307 struct smatch_state
*s
;
309 s
= get_start_state(tmp
);
311 add_tracker(&ret
->locked
, tmp
->name
, tmp
->owner
,
314 add_tracker(&ret
->unlocked
, tmp
->name
,
315 tmp
->owner
, tmp
->sym
);
319 } END_FOR_EACH_PTR(tmp
);
321 add_ptr_list(&all_returns
, ret
);
324 static void check_returns_consistently(struct tracker
*lock
,
325 struct smatch_state
*start
)
327 int returns_locked
= 0;
328 int returns_unlocked
= 0;
329 struct locks_on_return
*tmp
;
331 FOR_EACH_PTR(all_returns
, tmp
) {
332 if (in_tracker_list(tmp
->unlocked
, lock
->name
, lock
->owner
,
334 returns_unlocked
= tmp
->line
;
335 else if (in_tracker_list(tmp
->locked
, lock
->name
, lock
->owner
,
337 returns_locked
= tmp
->line
;
338 else if (start
== &locked
)
339 returns_locked
= tmp
->line
;
340 else if (start
== &unlocked
)
341 returns_unlocked
= tmp
->line
;
342 } END_FOR_EACH_PTR(tmp
);
344 if (returns_locked
&& returns_unlocked
)
345 smatch_msg("warn: lock '%s' held on line %d but not on %d.",
346 lock
->name
, returns_locked
, returns_unlocked
);
350 static void check_consistency(struct symbol
*sym
)
357 FOR_EACH_PTR(starts_locked
, tmp
) {
358 if (in_tracker_list(starts_unlocked
, tmp
->name
, tmp
->owner
,
360 smatch_msg("error: locking inconsistency. We assume "
361 "'%s' is both locked and unlocked at the "
364 } END_FOR_EACH_PTR(tmp
);
366 FOR_EACH_PTR(starts_locked
, tmp
) {
367 check_returns_consistently(tmp
, &locked
);
368 } END_FOR_EACH_PTR(tmp
);
370 FOR_EACH_PTR(starts_unlocked
, tmp
) {
371 check_returns_consistently(tmp
, &unlocked
);
372 } END_FOR_EACH_PTR(tmp
);
376 static void clear_lists(void)
378 struct locks_on_return
*tmp
;
380 free_trackers_and_list(&starts_locked
);
381 free_trackers_and_list(&starts_unlocked
);
383 FOR_EACH_PTR(all_returns
, tmp
) {
384 free_trackers_and_list(&tmp
->locked
);
385 free_trackers_and_list(&tmp
->unlocked
);
386 } END_FOR_EACH_PTR(tmp
);
387 __free_ptr_list((struct ptr_list
**)&all_returns
);
390 static void match_func_end(struct symbol
*sym
)
392 check_consistency(sym
);
396 void check_locking(int id
)
401 add_unmatched_state_hook(my_id
, &unmatched_state
);
402 add_hook(&match_condition
, CONDITION_HOOK
);
403 add_hook(&match_return
, RETURN_HOOK
);
404 add_hook(&match_func_end
, END_FUNC_HOOK
);
406 for (i
= 0; lock_funcs
[i
]; i
++) {
407 add_function_hook(lock_funcs
[i
], &match_lock_func
, NULL
);
409 add_function_hook("lock_kernel", &match_lock_func
, (void *)"kernel");
410 for (i
= 0; unlock_funcs
[i
]; i
++) {
411 add_function_hook(unlock_funcs
[i
], &match_unlock_func
, NULL
);
413 add_function_hook("unlock_kernel", &match_unlock_func
, (void *)"kernel");
414 for (i
= 0; i
< sizeof(lock_needed
)/sizeof(struct locked_call
); i
++) {
415 add_function_hook(lock_needed
[i
].function
, &match_lock_needed
,
416 (void *)lock_needed
[i
].lock
);