comparison: select the caller_info
[smatch.git] / check_unwind.c
blob569792ad5a575157ba1ca981a6a6c4caa7fc9026
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;
25 static int info_id;
27 STATE(alloc);
28 STATE(release);
29 STATE(param_released);
30 STATE(ignore);
32 static unsigned long fn_has_alloc;
34 struct ref_func_info {
35 const char *name;
36 int type;
37 int param;
38 const char *key;
39 const sval_t *implies_start, *implies_end;
40 func_hook *call_back;
43 static struct ref_func_info func_table[] = {
44 { "clk_prepare", ALLOC, 0, "$", &int_zero, &int_zero },
45 { "clk_prepare_enable", ALLOC, 0, "$", &int_zero, &int_zero },
46 { "clk_disable_unprepare", RELEASE, 0, "$" },
47 { "clk_unprepare", RELEASE, 0, "$" },
49 { "alloc_etherdev_mqs", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
50 { "free_netdev", RELEASE, 0, "$" },
53 * FIXME: A common pattern in release functions like amd76xrom_cleanup()
54 * is to do:
56 * if (window->rsrc.parent)
57 * release_resource(&window->rsrc);
59 * Which is slightly tricky to know how to merge the states so let's
60 * hold off checking request_resource() for now.
62 * { "request_resource", ALLOC, 1, "$", &int_zero, &int_zero },
63 * { "release_resource", RELEASE, 0, "$" },
67 { "pci_request_regions", ALLOC, 0, "$", &int_zero, &int_zero },
68 { "pci_release_regions", RELEASE, 0, "$" },
70 { "request_free_mem_region", ALLOC, -1, "$->start", &valid_ptr_min_sval, &valid_ptr_max_sval },
71 { "__request_region", ALLOC, 1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
72 { "release_and_free_resource", RELEASE, 0, "$->start" },
73 { "release_resource", RELEASE, 0, "$->start" },
74 { "__release_region", RELEASE, 1, "$" },
76 { "ioremap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
77 { "of_iomap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
78 { "ioremap_encrypted", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
79 { "iounmap", RELEASE, 0, "$" },
81 { "request_threaded_irq", ALLOC, 0, "$", &int_zero, &int_zero },
82 { "request_irq", ALLOC, 0, "$", &int_zero, &int_zero },
83 { "free_irq", RELEASE, 0, "$" },
84 { "pci_request_irq", ALLOC, 1, "$", &int_zero, &int_zero },
85 { "pci_free_irq", RELEASE, 1, "$" },
87 { "register_netdev", ALLOC, 0, "$", &int_zero, &int_zero },
88 { "unregister_netdev", RELEASE, 0, "$" },
90 { "misc_register", ALLOC, 0, "$", &int_zero, &int_zero },
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 static 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;
112 if (parent_is_err_or_null_var_sym(sm->name, sm->sym))
113 return &param_released;
115 if (estate_min(state).value == 0 &&
116 estate_max(state).value == 0)
117 return &param_released;
118 if (estate_type(state) == &int_ctype &&
119 sval_is_negative(estate_min(state)) &&
120 (estate_max(state).value == -1 || estate_max(state).value == 0))
121 return &param_released;
123 return &undefined;
126 static bool is_param_var_sym(const char *name, struct symbol *sym)
128 const char *key;
130 return get_param_key_from_var_sym(name, sym, NULL, &key) >= 0;
133 static void mark_matches_as_undefined(const char *key)
135 struct sm_state *sm;
136 int start_pos, state_len, key_len;
137 char *p;
139 while ((p = strchr(key, '-'))) {
140 if (p[1] != '>')
141 return;
142 key = p + 2;
144 key_len = strlen(key);
146 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
147 state_len = strlen(sm->name);
148 if (state_len < key_len)
149 continue;
150 if (!slist_has_state(sm->possible, &alloc))
151 continue;
153 start_pos = state_len - key_len;
154 if ((start_pos == 0 || !isalnum(sm->name[start_pos - 1])) &&
155 strcmp(sm->name + start_pos, key) == 0)
156 update_ssa_state(my_id, sm->name, sm->sym, &undefined);
158 } END_FOR_EACH_SM(sm);
161 static bool is_alloc_primitive(struct expression *expr)
163 int i;
165 while (expr->type == EXPR_ASSIGNMENT)
166 expr = strip_expr(expr->right);
167 if (expr->type != EXPR_CALL)
168 return false;
170 if (expr->fn->type != EXPR_SYMBOL)
171 return false;
173 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
174 if (sym_name_is(func_table[i].name, expr->fn))
175 return true;
178 return false;
181 static void return_param_alloc(struct expression *expr, const char *name, struct symbol *sym, void *data)
183 fn_has_alloc = true;
184 set_ssa_state(my_id, name, sym, &alloc);
187 static void return_param_release(struct expression *expr, const char *name, struct symbol *sym, void *data)
189 struct sm_state *start_sm;
191 /* The !data means this comes from the DB (not hard coded). */
192 if (!data && is_alloc_primitive(expr))
193 return;
195 start_sm = get_ssa_sm_state(my_id, name, sym);
196 if (start_sm) {
197 update_ssa_state(my_id, start_sm->name, start_sm->sym, &release);
198 } else {
199 if (fn_has_alloc) {
200 mark_matches_as_undefined(name);
201 return;
203 if (is_param_var_sym(name, sym))
204 set_state(info_id, name, sym, &param_released);
208 static void ignore_path(const char *fn, struct expression *expr, void *data)
210 set_state(my_id, "path", NULL, &ignore);
213 static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
215 struct sm_state *sm;
216 const char *param_name;
217 int param;
219 if (is_impossible_path())
220 return;
222 FOR_EACH_MY_SM(info_id, __get_cur_stree(), sm) {
223 if (sm->state != &param_released)
224 continue;
225 param = get_param_key_from_sm(sm, expr, &param_name);
226 if (param < 0)
227 continue;
228 sql_insert_return_states(return_id, return_ranges, RELEASE,
229 param, param_name, "");
230 } END_FOR_EACH_SM(sm);
233 enum {
234 UNKNOWN, FAIL, SUCCESS, NUM_BUCKETS
237 static int success_fail_positive(struct range_list *rl)
239 if (!rl)
240 return UNKNOWN;
242 if (sval_is_negative(rl_min(rl)) && sval_is_negative(rl_max(rl)))
243 return FAIL;
245 if (rl_min(rl).value == 0)
246 return SUCCESS;
248 return UNKNOWN;
251 static void check_balance(const char *name, struct symbol *sym)
253 struct range_list *inc_lines = NULL;
254 int inc_buckets[NUM_BUCKETS] = {};
255 struct stree *stree, *orig_stree;
256 struct smatch_state *state;
257 struct sm_state *return_sm;
258 struct sm_state *sm;
259 sval_t line = sval_type_val(&int_ctype, 0);
260 int bucket;
262 FOR_EACH_PTR(get_all_return_strees(), stree) {
263 orig_stree = __swap_cur_stree(stree);
265 if (is_impossible_path())
266 goto swap_stree;
267 if (db_incomplete())
268 goto swap_stree;
269 if (get_state(my_id, "path", NULL) == &ignore)
270 goto swap_stree;
272 return_sm = get_sm_state(RETURN_ID, "return_ranges", NULL);
273 if (!return_sm)
274 goto swap_stree;
275 line.value = return_sm->line;
277 sm = get_sm_state(my_id, name, sym);
278 if (!sm)
279 goto swap_stree;
281 state = sm->state;
282 if (state == &param_released)
283 state = &release;
285 if (state != &alloc &&
286 state != &release)
287 goto swap_stree;
289 bucket = success_fail_positive(estate_rl(return_sm->state));
290 if (bucket != FAIL)
291 goto swap_stree;
293 if (state == &alloc) {
294 add_range(&inc_lines, line, line);
295 inc_buckets[bucket] = true;
297 swap_stree:
298 __swap_cur_stree(orig_stree);
299 } END_FOR_EACH_PTR(stree);
301 if (inc_buckets[FAIL])
302 goto complain;
304 return;
306 complain:
307 sm_warning("'%s' not released on lines: %s.", ssa_name(name), show_rl(inc_lines));
310 static void match_check_balanced(struct symbol *sym)
312 struct sm_state *sm;
314 FOR_EACH_MY_SM(my_id, get_all_return_states(), sm) {
315 if (sm->sym == NULL && strcmp(sm->name, "path") == 0)
316 continue;
317 check_balance(sm->name, sm->sym);
318 } END_FOR_EACH_SM(sm);
321 void check_unwind(int id)
323 struct ref_func_info *info;
324 int i;
326 my_id = id;
328 if (option_project != PROJ_KERNEL)
329 return;
331 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
332 info = &func_table[i];
334 if (info->call_back) {
335 add_function_hook(info->name, info->call_back, info);
336 } else if (info->implies_start && info->type == ALLOC) {
337 return_implies_param_key_exact(info->name,
338 *info->implies_start,
339 *info->implies_end,
340 &return_param_alloc,
341 info->param, info->key, info);
342 } else if (info->implies_start) {
343 return_implies_param_key(info->name,
344 *info->implies_start,
345 *info->implies_end,
346 &return_param_release,
347 info->param, info->key, info);
348 } else {
349 add_function_param_key_hook(info->name,
350 (info->type == ALLOC) ? &return_param_alloc : &return_param_release,
351 info->param, info->key, info);
355 add_function_hook("devm_add_action_or_reset", &ignore_path, NULL);
356 add_function_hook("drmm_add_action", &ignore_path, NULL);
357 add_function_hook("__drmm_add_action", &ignore_path, NULL);
358 add_function_hook("pcim_enable_device", &ignore_path, NULL);
359 add_function_hook("pci_enable_device", &ignore_path, NULL);
361 add_function_data(&fn_has_alloc);
363 add_split_return_callback(match_return_info);
364 select_return_param_key(RELEASE, &return_param_release);
365 add_hook(&match_check_balanced, END_FUNC_HOOK);
368 void check_unwind_info(int id)
370 info_id = id;
371 add_unmatched_state_hook(info_id, &unmatched_state);