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",
41 static const char *unlock_funcs
[] = {
43 "_spin_unlock_irqrestore",
48 "_read_unlock_irqrestore",
52 "_write_unlock_irqrestore",
64 static struct locked_call lock_needed
[] = {
65 {"tty_ldisc_ref_wait", "tty_ldisc_lock"},
70 static struct tracker_list
*starts_locked
;
71 static struct tracker_list
*starts_unlocked
;
73 struct locks_on_return
{
75 struct tracker_list
*locked
;
76 struct tracker_list
*unlocked
;
78 DECLARE_PTR_LIST(return_list
, struct locks_on_return
);
79 static struct return_list
*all_returns
;
84 static char kernel
[] = "kernel";
85 static char *match_lock_func(char *fn_name
, struct expression_list
*args
)
87 struct expression
*lock_expr
;
90 for (i
= 0; lock_funcs
[i
]; i
++) {
91 if (!strcmp(fn_name
, lock_funcs
[i
])) {
92 lock_expr
= get_argument_from_call_expr(args
, 0);
93 return get_variable_from_expr(lock_expr
, NULL
);
96 if (!strcmp(fn_name
, "lock_kernel"))
101 static char *match_unlock_func(char *fn_name
, struct expression_list
*args
)
103 struct expression
*lock_expr
;
106 for (i
= 0; unlock_funcs
[i
]; i
++) {
107 if (!strcmp(fn_name
, unlock_funcs
[i
])) {
108 lock_expr
= get_argument_from_call_expr(args
, 0);
109 return get_variable_from_expr(lock_expr
, NULL
);
112 if (!strcmp(fn_name
, "unlock_kernel"))
117 static void check_locks_needed(const char *fn_name
)
119 struct smatch_state
*state
;
122 for (i
= 0; i
< sizeof(lock_needed
)/sizeof(struct locked_call
); i
++) {
123 if (!strcmp(fn_name
, lock_needed
[i
].function
)) {
124 state
= get_state(lock_needed
[i
].lock
, my_id
, NULL
);
125 if (state
!= &locked
) {
126 smatch_msg("%s called without holding '%s' lock",
127 lock_needed
[i
].function
,
128 lock_needed
[i
].lock
);
134 static void match_call(struct expression
*expr
)
139 fn_name
= get_variable_from_expr(expr
->fn
, NULL
);
143 if ((lock_name
= match_lock_func(fn_name
, expr
->args
))) {
144 if (!get_state(lock_name
, my_id
, NULL
))
145 add_tracker(&starts_unlocked
, lock_name
, my_id
, NULL
);
146 set_state(lock_name
, my_id
, NULL
, &locked
);
147 } else if ((lock_name
= match_unlock_func(fn_name
, expr
->args
))) {
148 if (!get_state(lock_name
, my_id
, NULL
))
149 add_tracker(&starts_locked
, lock_name
, my_id
, NULL
);
150 set_state(lock_name
, my_id
, NULL
, &unlocked
);
152 check_locks_needed(fn_name
);
153 free_string(fn_name
);
157 static void match_condition(struct expression
*expr
)
159 /* __raw_spin_is_locked */
162 static struct locks_on_return
*alloc_return(int line
)
164 struct locks_on_return
*ret
;
166 ret
= malloc(sizeof(*ret
));
169 ret
->unlocked
= NULL
;
173 static void match_return(struct statement
*stmt
)
175 struct locks_on_return
*ret
;
176 struct state_list
*slist
;
177 struct sm_state
*tmp
;
179 ret
= alloc_return(get_lineno());
181 slist
= get_all_states(my_id
);
182 FOR_EACH_PTR(slist
, tmp
) {
183 if (tmp
->state
== &locked
) {
184 add_tracker(&ret
->locked
, tmp
->name
, tmp
->owner
,
186 } else if (tmp
->state
== &unlocked
) {
187 add_tracker(&ret
->unlocked
, tmp
->name
, tmp
->owner
,
190 smatch_msg("Unclear if '%s' is locked or unlocked.",
193 } END_FOR_EACH_PTR(tmp
);
194 add_ptr_list(&all_returns
, ret
);
197 static void check_returns_consistently(struct tracker
*lock
,
198 struct smatch_state
*start
)
200 int returns_locked
= 0;
201 int returns_unlocked
= 0;
202 struct locks_on_return
*tmp
;
204 FOR_EACH_PTR(all_returns
, tmp
) {
205 if (in_tracker_list(tmp
->unlocked
, lock
->name
, lock
->owner
,
207 returns_unlocked
= tmp
->line
;
208 else if (in_tracker_list(tmp
->locked
, lock
->name
, lock
->owner
,
210 returns_locked
= tmp
->line
;
211 else if (start
== &locked
)
212 returns_locked
= tmp
->line
;
213 else if (start
== &unlocked
)
214 returns_unlocked
= tmp
->line
;
215 } END_FOR_EACH_PTR(tmp
);
217 if (returns_locked
&& returns_unlocked
)
218 smatch_msg("Lock '%s' held on line %d but not on %d.",
219 lock
->name
, returns_locked
, returns_unlocked
);
223 static void clear_lists()
225 struct locks_on_return
*tmp
;
227 __free_ptr_list((struct ptr_list
**)&starts_locked
);
228 __free_ptr_list((struct ptr_list
**)&starts_unlocked
);
230 FOR_EACH_PTR(all_returns
, tmp
) {
231 __free_ptr_list((struct ptr_list
**)&tmp
->locked
);
232 __free_ptr_list((struct ptr_list
**)&tmp
->unlocked
);
233 } END_FOR_EACH_PTR(tmp
);
234 __free_ptr_list((struct ptr_list
**)&all_returns
);
237 static void check_consistency(struct symbol
*sym
)
241 FOR_EACH_PTR(starts_locked
, tmp
) {
242 if (in_tracker_list(starts_unlocked
, tmp
->name
, tmp
->owner
,
244 smatch_msg("Locking inconsistency. We assume '%s' is "
245 "both locked and unlocked at the start.",
247 } END_FOR_EACH_PTR(tmp
);
249 FOR_EACH_PTR(starts_locked
, tmp
) {
250 check_returns_consistently(tmp
, &locked
);
251 } END_FOR_EACH_PTR(tmp
);
253 FOR_EACH_PTR(starts_unlocked
, tmp
) {
254 check_returns_consistently(tmp
, &unlocked
);
255 } END_FOR_EACH_PTR(tmp
);
260 void register_locking(int id
)
263 add_hook(&match_call
, FUNCTION_CALL_HOOK
);
264 add_hook(&match_return
, RETURN_HOOK
);
265 add_hook(&check_consistency
, END_FUNC_HOOK
);