deref: avoid the big_statement_stack
[smatch.git] / check_unwind.c
blob8f51adb556204edd45f06a9d5a27cec8dcd8cb85
1 /*
2 * Copyright (C) 2020 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(alloc);
27 STATE(release);
28 STATE(param_released);
30 static unsigned long fn_has_alloc;
32 struct ref_func_info {
33 const char *name;
34 int type;
35 int param;
36 const char *key;
37 const sval_t *implies_start, *implies_end;
38 func_hook *call_back;
41 static sval_t zero_sval = { .type = &int_ctype };
43 static struct ref_func_info func_table[] = {
44 { "clk_prepare_enable", ALLOC, 0, "$", &zero_sval, &zero_sval },
45 { "clk_disable_unprepare", RELEASE, 0, "$" },
47 { "alloc_etherdev_mqs", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
48 { "free_netdev", RELEASE, 0, "$" },
51 * FIXME: A common pattern in release functions like amd76xrom_cleanup()
52 * is to do:
54 * if (window->rsrc.parent)
55 * release_resource(&window->rsrc);
57 * Which is slightly tricky to know how to merge the states so let's
58 * hold off checking request_resource() for now.
60 * { "request_resource", ALLOC, 1, "$", &zero_sval, &zero_sval },
61 * { "release_resource", RELEASE, 0, "$" },
65 { "pci_request_regions", ALLOC, 0, "$", &zero_sval, &zero_sval },
66 { "pci_release_regions", RELEASE, 0, "$" },
68 { "request_free_mem_region", ALLOC, -1, "$->start", &valid_ptr_min_sval, &valid_ptr_max_sval },
69 { "__request_region", ALLOC, 1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
70 { "__release_region", RELEASE, 1, "$" },
72 { "ioremap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
73 { "of_iomap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
74 { "ioremap_encrypted", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
75 { "iounmap", RELEASE, 0, "$" },
77 { "pci_iomap_range", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
78 { "pci_iomap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
79 { "pci_iounmap", RELEASE, 0, "$" },
81 { "request_threaded_irq", ALLOC, 0, "$", &zero_sval, &zero_sval },
82 { "request_irq", ALLOC, 0, "$", &zero_sval, &zero_sval },
83 { "free_irq", RELEASE, 0, "$" },
84 { "pci_request_irq", ALLOC, 1, "$", &zero_sval, &zero_sval },
85 { "pci_free_irq", RELEASE, 1, "$" },
87 { "register_netdev", ALLOC, 0, "$", &zero_sval, &zero_sval },
88 { "unregister_netdev", RELEASE, 0, "$" },
90 { "misc_register", ALLOC, 0, "$", &zero_sval, &zero_sval },
91 { "misc_deregister", RELEASE, 0, "$" },
93 { "ieee80211_alloc_hw", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
94 { "ieee80211_free_hw", RELEASE, 0, "$" },
97 struct smatch_state *unmatched_state(struct sm_state *sm)
99 struct smatch_state *state;
101 if (sm->state != &param_released)
102 return &undefined;
104 if (is_impossible_path())
105 return &param_released;
107 state = get_state(SMATCH_EXTRA, sm->name, sm->sym);
108 if (!state)
109 return &undefined;
110 if (!estate_rl(state) || is_err_or_null(estate_rl(state)))
111 return &param_released;
113 return &undefined;
116 static struct sm_state *get_start_sm(const char *name, struct symbol *sym)
118 struct sm_state *sm;
120 sm = get_sm_state(my_id, name, sym);
121 if (sm)
122 return sm;
124 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
125 if (get_comparison_strings(name, sm->name) == SPECIAL_EQUAL)
126 return sm;
127 } END_FOR_EACH_SM(sm);
129 return NULL;
132 static bool is_param_var_sym(const char *name, struct symbol *sym)
134 const char *key;
136 return get_param_key_from_var_sym(name, sym, NULL, &key) >= 0;
139 static void mark_matches_as_undefined(const char *key)
141 struct sm_state *sm;
142 int start_pos, state_len, key_len;
143 char *p;
145 while ((p = strchr(key, '-'))) {
146 if (p[1] != '>')
147 return;
148 key = p + 2;
150 key_len = strlen(key);
152 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
153 state_len = strlen(sm->name);
154 if (state_len < key_len)
155 continue;
156 if (!slist_has_state(sm->possible, &alloc))
157 continue;
159 start_pos = state_len - key_len;
160 if ((start_pos == 0 || !isalnum(sm->name[start_pos - 1])) &&
161 strcmp(sm->name + start_pos, key) == 0)
162 set_state(my_id, sm->name, sm->sym, &undefined);
164 } END_FOR_EACH_SM(sm);
167 static void db_helper(struct expression *expr, int param, const char *key, int inc_dec)
169 struct sm_state *start_sm;
170 char *name;
171 struct symbol *sym;
173 if (inc_dec == ALLOC)
174 fn_has_alloc = true;
176 name = get_name_sym_from_key(expr, param, key, &sym);
177 if (!name || !sym)
178 goto free;
180 start_sm = get_start_sm(name, sym);
181 if (inc_dec == RELEASE && !start_sm) {
182 if (fn_has_alloc) {
183 mark_matches_as_undefined(key);
184 goto free;
186 if (is_param_var_sym(name, sym)) {
187 set_state(my_id, name, sym, &param_released);
188 goto free;
190 goto free;
193 if (inc_dec == ALLOC)
194 set_state(my_id, name, sym, &alloc);
195 else {
196 if (start_sm)
197 set_state(my_id, start_sm->name, start_sm->sym, &release);
198 else
199 set_state(my_id, name, sym, &release);
201 free:
202 free_string(name);
205 static void refcount_function(const char *fn, struct expression *expr, void *data)
207 struct ref_func_info *info = data;
209 db_helper(expr, info->param, info->key, info->type);
212 static void refcount_implied(const char *fn, struct expression *call_expr,
213 struct expression *assign_expr, void *data)
215 struct ref_func_info *info = data;
217 db_helper(assign_expr ?: call_expr, info->param, info->key, info->type);
220 static bool is_alloc_primitive(struct expression *expr)
222 int i;
224 while (expr->type == EXPR_ASSIGNMENT)
225 expr = strip_expr(expr->right);
226 if (expr->type != EXPR_CALL)
227 return false;
229 if (expr->fn->type != EXPR_SYMBOL)
230 return false;
232 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
233 if (sym_name_is(func_table[i].name, expr->fn))
234 return true;
237 return false;
240 static void db_dec(struct expression *expr, int param, char *key, char *value)
242 if (is_alloc_primitive(expr))
243 return;
244 db_helper(expr, param, key, RELEASE);
247 static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
249 struct sm_state *sm;
250 const char *param_name;
251 int param;
253 if (is_impossible_path())
254 return;
256 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
257 if (sm->state != &param_released)
258 continue;
259 param = get_param_key_from_sm(sm, expr, &param_name);
260 if (param < 0)
261 continue;
262 sql_insert_return_states(return_id, return_ranges, RELEASE,
263 param, param_name, "");
264 } END_FOR_EACH_SM(sm);
267 enum {
268 EMPTY, NEGATIVE, ZERO, POSITIVE, NUM_BUCKETS
271 static int success_fail_positive(struct range_list *rl)
273 if (!rl)
274 return EMPTY;
276 if (sval_is_negative(rl_min(rl)) && sval_is_negative(rl_max(rl)))
277 return NEGATIVE;
279 if (rl_min(rl).value == 0)
280 return ZERO;
282 return POSITIVE;
285 static void check_balance(const char *name, struct symbol *sym)
287 struct range_list *inc_lines = NULL;
288 int inc_buckets[NUM_BUCKETS] = {};
289 struct stree *stree, *orig_stree;
290 struct smatch_state *state;
291 struct sm_state *return_sm;
292 struct sm_state *sm;
293 sval_t line = sval_type_val(&int_ctype, 0);
294 int bucket;
296 FOR_EACH_PTR(get_all_return_strees(), stree) {
297 orig_stree = __swap_cur_stree(stree);
299 if (is_impossible_path())
300 goto swap_stree;
301 if (db_incomplete())
302 goto swap_stree;
304 return_sm = get_sm_state(RETURN_ID, "return_ranges", NULL);
305 if (!return_sm)
306 goto swap_stree;
307 line.value = return_sm->line;
309 sm = get_sm_state(my_id, name, sym);
310 if (!sm)
311 goto swap_stree;
313 state = sm->state;
314 if (state == &param_released)
315 state = &release;
317 if (state != &alloc &&
318 state != &release)
319 goto swap_stree;
321 bucket = success_fail_positive(estate_rl(return_sm->state));
322 if (bucket != NEGATIVE)
323 goto swap_stree;
325 if (state == &alloc) {
326 add_range(&inc_lines, line, line);
327 inc_buckets[bucket] = true;
329 swap_stree:
330 __swap_cur_stree(orig_stree);
331 } END_FOR_EACH_PTR(stree);
333 if (inc_buckets[NEGATIVE])
334 goto complain;
336 return;
338 complain:
339 sm_warning("'%s' not released on lines: %s.", name, show_rl(inc_lines));
342 static void match_check_balanced(struct symbol *sym)
344 struct sm_state *sm;
346 FOR_EACH_MY_SM(my_id, get_all_return_states(), sm) {
347 check_balance(sm->name, sm->sym);
348 } END_FOR_EACH_SM(sm);
351 void check_unwind(int id)
353 struct ref_func_info *info;
354 int i;
356 my_id = id;
358 if (option_project != PROJ_KERNEL)
359 return;
361 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
362 info = &func_table[i];
364 if (info->call_back) {
365 add_function_hook(info->name, info->call_back, info);
366 } else if (info->implies_start) {
367 return_implies_state_sval(info->name,
368 *info->implies_start,
369 *info->implies_end,
370 &refcount_implied, info);
371 } else {
372 add_function_hook(info->name, &refcount_function, info);
376 add_unmatched_state_hook(my_id, &unmatched_state);
377 add_function_data(&fn_has_alloc);
379 add_split_return_callback(match_return_info);
380 select_return_states_hook(RELEASE, &db_dec);
381 add_hook(&match_check_balanced, END_FUNC_HOOK);