math: array parameters can be NULL
[smatch.git] / check_atomic_inc_dec.c
blobc8afa074be415d01ac3aee6c8a2d0b4940c1b6dc
1 /*
2 * Copyright (C) 2016 Oracle.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
18 #include <ctype.h>
20 #include "smatch.h"
21 #include "smatch_extra.h"
22 #include "smatch_slist.h"
24 static int my_id;
26 STATE(inc);
27 STATE(start_state);
28 STATE(dec);
30 struct ref_func_info {
31 const char *name;
32 int type;
33 int param;
34 const char *key;
35 func_hook *call_back;
36 bool implied;
37 long long implies_start, implies_end;
40 static void match_atomic_add(const char *fn, struct expression *expr, void *_unused);
42 static struct ref_func_info func_table[] = {
43 { "atomic_inc", ATOMIC_INC, 0, "$->counter" },
44 { "atomic_long_inc", ATOMIC_INC, 0, "$->counter" },
45 { "atomic_inc_return", ATOMIC_INC, 0, "$->counter" },
47 { "atomic_add_return", ATOMIC_INC, 1, "$->counter", match_atomic_add },
49 { "atomic_dec", ATOMIC_DEC, 0, "$->counter" },
50 { "atomic_dec_return", ATOMIC_DEC, 0, "$->counter" },
51 { "atomic_dec_and_test", ATOMIC_DEC, 0, "$->counter" },
52 { "atomic_long_dec", ATOMIC_DEC, 0, "$->counter" },
53 { "atomic_long_dec_and_test", ATOMIC_DEC, 0, "$->counter" },
54 { "atomic64_dec_and_test", ATOMIC_DEC, 0, "$->counter" },
55 { "_atomic_dec_and_lock", ATOMIC_DEC, 0, "$->counter" },
57 { "atomic_sub", ATOMIC_DEC, 1, "$->counter" },
58 { "atomic_sub_return", ATOMIC_DEC, 1, "$->counter" },
59 { "atomic_sub_and_test", ATOMIC_DEC, 1, "$->counter" },
60 { "atomic_long_sub_and_test", ATOMIC_DEC, 1, "$->counter" },
61 { "atomic64_sub_and_test", ATOMIC_DEC, 1, "$->counter" },
63 { "refcount_inc", ATOMIC_INC, 0, "$->ref.counter" },
64 { "refcount_dec", ATOMIC_DEC, 0, "$->ref.counter" },
65 { "refcount_dec_and_test", ATOMIC_DEC, 0, "$->ref.counter" },
66 { "refcount_add", ATOMIC_INC, 1, "$->ref.counter" },
67 { "refcount_sub_and_test", ATOMIC_DEC, 1, "$->ref.counter" },
69 { "pm_runtime_get_sync", ATOMIC_INC, 0, "$->power.usage_count.counter" },
70 { "of_clk_del_provider", ATOMIC_DEC, 0, "$->kobj.kref.refcount.ref.counter" },
72 { "refcount_inc_not_zero", ATOMIC_INC, 0, "$->ref.counter", NULL, true, 1, 1},
73 { "refcount_add_not_zero", ATOMIC_INC, 1, "$->ref.counter", NULL, true, 1, 1},
75 { "atomic_dec_if_positive", ATOMIC_DEC, 0, "$->counter", NULL, true, 0, INT_MAX},
77 { "of_node_get", ATOMIC_INC, 0, "$->kobj.kref.refcount.ref.counter" },
78 { "of_node_put", ATOMIC_DEC, 0, "$->kobj.kref.refcount.ref.counter" },
79 { "of_get_parent", ATOMIC_INC, -1, "$->kobj.kref.refcount.ref.counter" },
80 { "of_clk_del_provider", ATOMIC_DEC, 0, "$->kobj.kref.refcount.ref.counter" },
83 static struct smatch_state *unmatched_state(struct sm_state *sm)
86 * We default to decremented. For example, say we have:
87 * if (p)
88 * atomic_dec(p);
89 * <- p is decreemented.
92 if ((sm->state == &dec) &&
93 parent_is_gone_var_sym(sm->name, sm->sym))
94 return sm->state;
95 return &start_state;
98 static struct stree *start_states;
99 static struct stree_stack *saved_stack;
100 static void set_start_state(const char *name, struct symbol *sym, struct smatch_state *start)
102 struct smatch_state *orig;
104 orig = get_state_stree(start_states, my_id, name, sym);
105 if (!orig)
106 set_state_stree(&start_states, my_id, name, sym, start);
107 else if (orig != start)
108 set_state_stree(&start_states, my_id, name, sym, &undefined);
111 static struct sm_state *get_best_match(const char *key)
113 struct sm_state *sm;
114 struct sm_state *match;
115 int cnt = 0;
116 int start_pos, state_len, key_len, chunks, i;
118 if (strncmp(key, "$->", 3) == 0)
119 key += 3;
121 key_len = strlen(key);
122 chunks = 0;
123 for (i = key_len - 1; i > 0; i--) {
124 if (key[i] == '>' || key[i] == '.')
125 chunks++;
126 if (chunks == 2) {
127 key += (i + 1);
128 key_len = strlen(key);
129 break;
133 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
134 state_len = strlen(sm->name);
135 if (state_len < key_len)
136 continue;
137 start_pos = state_len - key_len;
138 if ((start_pos == 0 || !isalnum(sm->name[start_pos - 1])) &&
139 strcmp(sm->name + start_pos, key) == 0) {
140 cnt++;
141 match = sm;
143 } END_FOR_EACH_SM(sm);
145 if (cnt == 1)
146 return match;
147 return NULL;
150 static void db_inc_dec(struct expression *expr, int param, const char *key, int inc_dec)
152 struct sm_state *start_sm;
153 struct expression *arg;
154 char *name;
155 struct symbol *sym;
156 bool free_at_end = true;
158 while (expr->type == EXPR_ASSIGNMENT)
159 expr = strip_expr(expr->right);
160 if (expr->type != EXPR_CALL)
161 return;
163 arg = get_argument_from_call_expr(expr->args, param);
164 if (!arg)
165 return;
167 name = get_variable_from_key(arg, key, &sym);
168 if (!name || !sym)
169 goto free;
171 start_sm = get_sm_state(my_id, name, sym);
172 if (!start_sm && inc_dec == ATOMIC_DEC) {
173 start_sm = get_best_match(key);
174 if (start_sm) {
175 free_string(name);
176 free_at_end = false;
177 name = (char *)start_sm->name;
178 sym = start_sm->sym;
182 if (inc_dec == ATOMIC_INC) {
183 if (!start_sm)
184 set_start_state(name, sym, &dec);
185 // set_refcount_inc(name, sym);
186 set_state(my_id, name, sym, &inc);
187 } else {
188 // set_refcount_dec(name, sym);
189 if (!start_sm)
190 set_start_state(name, sym, &inc);
192 if (start_sm && start_sm->state == &inc)
193 set_state(my_id, name, sym, &start_state);
194 else
195 set_state(my_id, name, sym, &dec);
198 free:
199 if (free_at_end)
200 free_string(name);
203 static bool is_inc_dec_primitive(struct expression *expr)
205 int i;
207 while (expr->type == EXPR_ASSIGNMENT)
208 expr = strip_expr(expr->right);
209 if (expr->type != EXPR_CALL)
210 return false;
212 if (expr->fn->type != EXPR_SYMBOL)
213 return false;
215 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
216 if (sym_name_is(func_table[i].name, expr->fn))
217 return true;
220 return false;
223 static void db_inc(struct expression *expr, int param, char *key, char *value)
225 if (is_inc_dec_primitive(expr))
226 return;
227 db_inc_dec(expr, param, key, ATOMIC_INC);
230 static void db_dec(struct expression *expr, int param, char *key, char *value)
232 if (is_inc_dec_primitive(expr))
233 return;
234 db_inc_dec(expr, param, key, ATOMIC_DEC);
237 static void match_atomic_add(const char *fn, struct expression *expr, void *_unused)
239 struct expression *amount;
240 sval_t sval;
242 amount = get_argument_from_call_expr(expr->args, 0);
243 if (get_implied_value(amount, &sval) && sval_is_negative(sval)) {
244 db_inc_dec(expr, 1, "$->counter", ATOMIC_DEC);
245 return;
248 db_inc_dec(expr, 1, "$->counter", ATOMIC_INC);
251 static void refcount_function(const char *fn, struct expression *expr, void *data)
253 struct ref_func_info *info = data;
255 db_inc_dec(expr, info->param, info->key, info->type);
258 static void refcount_implied(const char *fn, struct expression *call_expr,
259 struct expression *assign_expr, void *data)
261 struct ref_func_info *info = data;
264 db_inc_dec(call_expr, info->param, info->key, info->type);
267 static bool is_maybe_dec(struct sm_state *sm)
269 if (sm->state == &dec)
270 return true;
271 if (slist_has_state(sm->possible, &dec) &&
272 !slist_has_state(sm->possible, &inc))
273 return true;
274 return false;
277 static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
279 struct sm_state *sm;
280 const char *param_name;
281 int param;
283 if (is_impossible_path())
284 return;
286 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
287 if (sm->state != &inc && !is_maybe_dec(sm))
288 continue;
289 if (sm->state == get_state_stree(start_states, my_id, sm->name, sm->sym))
290 continue;
291 if (parent_is_gone_var_sym(sm->name, sm->sym))
292 continue;
293 param = get_param_num_from_sym(sm->sym);
294 if (param < 0)
295 continue;
296 param_name = get_param_name(sm);
297 if (!param_name)
298 continue;
299 sql_insert_return_states(return_id, return_ranges,
300 (sm->state == &inc) ? ATOMIC_INC : ATOMIC_DEC,
301 param, param_name, "");
302 } END_FOR_EACH_SM(sm);
305 enum {
306 EMPTY, NEGATIVE, ZERO, POSITIVE, NUM_BUCKETS
309 static int success_fail_positive(struct range_list *rl)
311 if (!rl)
312 return EMPTY;
314 if (!is_whole_rl(rl) && sval_is_negative(rl_min(rl)))
315 return NEGATIVE;
317 if (rl_min(rl).value == 0)
318 return ZERO;
320 return POSITIVE;
323 static void check_counter(const char *name, struct symbol *sym)
325 struct range_list *inc_lines = NULL;
326 struct range_list *dec_lines = NULL;
327 int inc_buckets[NUM_BUCKETS] = {};
328 int dec_buckets[NUM_BUCKETS] = {};
329 struct stree *stree, *orig_stree;
330 struct smatch_state *state;
331 struct sm_state *return_sm;
332 struct sm_state *sm;
333 sval_t line = sval_type_val(&int_ctype, 0);
334 int bucket;
336 /* static variable are probably just counters */
337 if (sym->ctype.modifiers & MOD_STATIC &&
338 !(sym->ctype.modifiers & MOD_TOPLEVEL))
339 return;
341 FOR_EACH_PTR(get_all_return_strees(), stree) {
342 orig_stree = __swap_cur_stree(stree);
344 if (is_impossible_path())
345 goto swap_stree;
347 return_sm = get_sm_state(RETURN_ID, "return_ranges", NULL);
348 if (!return_sm)
349 goto swap_stree;
350 line.value = return_sm->line;
352 if (get_state_stree(start_states, my_id, name, sym) == &inc)
353 goto swap_stree;
355 if (parent_is_gone_var_sym(name, sym))
356 goto swap_stree;
358 sm = get_sm_state(my_id, name, sym);
359 if (sm)
360 state = sm->state;
361 else
362 state = &start_state;
364 if (state != &inc &&
365 state != &dec &&
366 state != &start_state)
367 goto swap_stree;
369 bucket = success_fail_positive(estate_rl(return_sm->state));
371 if (state == &inc) {
372 add_range(&inc_lines, line, line);
373 inc_buckets[bucket] = true;
375 if (state == &dec || state == &start_state) {
376 add_range(&dec_lines, line, line);
377 dec_buckets[bucket] = true;
379 swap_stree:
380 __swap_cur_stree(orig_stree);
381 } END_FOR_EACH_PTR(stree);
383 if (inc_buckets[NEGATIVE] &&
384 inc_buckets[ZERO]) {
385 // sm_warning("XXX '%s' not decremented on lines: %s.", name, show_rl(inc_lines));
390 static void match_check_missed(struct symbol *sym)
392 struct sm_state *sm;
394 FOR_EACH_MY_SM(my_id, get_all_return_states(), sm) {
395 check_counter(sm->name, sm->sym);
396 } END_FOR_EACH_SM(sm);
399 int on_atomic_dec_path(void)
401 struct sm_state *sm;
403 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
404 if (sm->state == &dec)
405 return 1;
406 } END_FOR_EACH_SM(sm);
408 return 0;
411 int was_inced(const char *name, struct symbol *sym)
413 return get_state(my_id, name, sym) == &inc;
416 static void match_save_states(struct expression *expr)
418 push_stree(&saved_stack, start_states);
419 start_states = NULL;
422 static void match_restore_states(struct expression *expr)
424 start_states = pop_stree(&saved_stack);
427 static void match_after_func(struct symbol *sym)
429 free_stree(&start_states);
432 void check_atomic_inc_dec(int id)
434 struct ref_func_info *info;
435 int i;
437 my_id = id;
439 if (option_project != PROJ_KERNEL)
440 return;
442 add_unmatched_state_hook(my_id, &unmatched_state);
444 add_split_return_callback(match_return_info);
445 select_return_states_hook(ATOMIC_INC, &db_inc);
446 select_return_states_hook(ATOMIC_DEC, &db_dec);
448 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
449 info = &func_table[i];
451 if (info->call_back) {
452 add_function_hook(info->name, info->call_back, info);
453 } else if (info->implied) {
454 return_implies_state(info->name,
455 info->implies_start, info->implies_end,
456 &refcount_implied, info);
457 } else {
458 add_function_hook(info->name, &refcount_function, info);
462 add_hook(&match_check_missed, END_FUNC_HOOK);
464 add_hook(&match_after_func, AFTER_FUNC_HOOK);
465 add_hook(&match_save_states, INLINE_FN_START);
466 add_hook(&match_restore_states, INLINE_FN_END);