missing_error_code: introduce new check
[smatch.git] / check_unwind.c
blob496966fa3322b9208ce5efc002e10264152d0fb1
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);
29 STATE(ignore);
31 static unsigned long fn_has_alloc;
33 struct ref_func_info {
34 const char *name;
35 int type;
36 int param;
37 const char *key;
38 const sval_t *implies_start, *implies_end;
39 func_hook *call_back;
42 static sval_t zero_sval = { .type = &int_ctype };
44 static struct ref_func_info func_table[] = {
45 { "clk_prepare_enable", ALLOC, 0, "$", &zero_sval, &zero_sval },
46 { "clk_disable_unprepare", RELEASE, 0, "$" },
48 { "alloc_etherdev_mqs", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
49 { "free_netdev", RELEASE, 0, "$" },
52 * FIXME: A common pattern in release functions like amd76xrom_cleanup()
53 * is to do:
55 * if (window->rsrc.parent)
56 * release_resource(&window->rsrc);
58 * Which is slightly tricky to know how to merge the states so let's
59 * hold off checking request_resource() for now.
61 * { "request_resource", ALLOC, 1, "$", &zero_sval, &zero_sval },
62 * { "release_resource", RELEASE, 0, "$" },
66 { "pci_request_regions", ALLOC, 0, "$", &zero_sval, &zero_sval },
67 { "pci_release_regions", RELEASE, 0, "$" },
69 { "request_free_mem_region", ALLOC, -1, "$->start", &valid_ptr_min_sval, &valid_ptr_max_sval },
70 { "__request_region", ALLOC, 1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
71 { "__release_region", RELEASE, 1, "$" },
73 { "ioremap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
74 { "of_iomap", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
75 { "ioremap_encrypted", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
76 { "iounmap", RELEASE, 0, "$" },
78 { "request_threaded_irq", ALLOC, 0, "$", &zero_sval, &zero_sval },
79 { "request_irq", ALLOC, 0, "$", &zero_sval, &zero_sval },
80 { "free_irq", RELEASE, 0, "$" },
81 { "pci_request_irq", ALLOC, 1, "$", &zero_sval, &zero_sval },
82 { "pci_free_irq", RELEASE, 1, "$" },
84 { "register_netdev", ALLOC, 0, "$", &zero_sval, &zero_sval },
85 { "unregister_netdev", RELEASE, 0, "$" },
87 { "misc_register", ALLOC, 0, "$", &zero_sval, &zero_sval },
88 { "misc_deregister", RELEASE, 0, "$" },
90 { "ieee80211_alloc_hw", ALLOC, -1, "$", &valid_ptr_min_sval, &valid_ptr_max_sval },
91 { "ieee80211_free_hw", RELEASE, 0, "$" },
94 struct smatch_state *unmatched_state(struct sm_state *sm)
96 struct smatch_state *state;
98 if (sm->state != &param_released)
99 return &undefined;
101 if (is_impossible_path())
102 return &param_released;
104 state = get_state(SMATCH_EXTRA, sm->name, sm->sym);
105 if (!state)
106 return &undefined;
107 if (!estate_rl(state) || is_err_or_null(estate_rl(state)))
108 return &param_released;
109 if (parent_is_err_or_null_var_sym(sm->name, sm->sym))
110 return &param_released;
112 return &undefined;
115 static struct sm_state *get_start_sm(const char *name, struct symbol *sym)
117 struct sm_state *sm;
119 sm = get_sm_state(my_id, name, sym);
120 if (sm)
121 return sm;
123 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
124 if (get_comparison_strings(name, sm->name) == SPECIAL_EQUAL)
125 return sm;
126 } END_FOR_EACH_SM(sm);
128 return NULL;
131 static bool is_param_var_sym(const char *name, struct symbol *sym)
133 const char *key;
135 return get_param_key_from_var_sym(name, sym, NULL, &key) >= 0;
138 static void mark_matches_as_undefined(const char *key)
140 struct sm_state *sm;
141 int start_pos, state_len, key_len;
142 char *p;
144 while ((p = strchr(key, '-'))) {
145 if (p[1] != '>')
146 return;
147 key = p + 2;
149 key_len = strlen(key);
151 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
152 state_len = strlen(sm->name);
153 if (state_len < key_len)
154 continue;
155 if (!slist_has_state(sm->possible, &alloc))
156 continue;
158 start_pos = state_len - key_len;
159 if ((start_pos == 0 || !isalnum(sm->name[start_pos - 1])) &&
160 strcmp(sm->name + start_pos, key) == 0)
161 set_state(my_id, sm->name, sm->sym, &undefined);
163 } END_FOR_EACH_SM(sm);
166 static void db_helper(struct expression *expr, int param, const char *key, int inc_dec)
168 struct sm_state *start_sm;
169 char *name;
170 struct symbol *sym;
172 if (inc_dec == ALLOC)
173 fn_has_alloc = true;
175 name = get_name_sym_from_key(expr, param, key, &sym);
176 if (!name || !sym)
177 goto free;
179 start_sm = get_start_sm(name, sym);
180 if (inc_dec == RELEASE && !start_sm) {
181 if (fn_has_alloc) {
182 mark_matches_as_undefined(key);
183 goto free;
185 if (is_param_var_sym(name, sym)) {
186 set_state(my_id, name, sym, &param_released);
187 goto free;
189 goto free;
192 if (inc_dec == ALLOC)
193 set_state(my_id, name, sym, &alloc);
194 else {
195 if (start_sm)
196 set_state(my_id, start_sm->name, start_sm->sym, &release);
197 else
198 set_state(my_id, name, sym, &release);
200 free:
201 free_string(name);
204 static void refcount_function(const char *fn, struct expression *expr, void *data)
206 struct ref_func_info *info = data;
208 db_helper(expr, info->param, info->key, info->type);
211 static void refcount_implied(const char *fn, struct expression *call_expr,
212 struct expression *assign_expr, void *data)
214 struct ref_func_info *info = data;
216 db_helper(assign_expr ?: call_expr, info->param, info->key, info->type);
219 static void ignore_path(const char *fn, struct expression *expr, void *data)
221 set_state(my_id, "path", NULL, &ignore);
224 static bool is_alloc_primitive(struct expression *expr)
226 int i;
228 while (expr->type == EXPR_ASSIGNMENT)
229 expr = strip_expr(expr->right);
230 if (expr->type != EXPR_CALL)
231 return false;
233 if (expr->fn->type != EXPR_SYMBOL)
234 return false;
236 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
237 if (sym_name_is(func_table[i].name, expr->fn))
238 return true;
241 return false;
244 static void db_dec(struct expression *expr, int param, char *key, char *value)
246 if (is_alloc_primitive(expr))
247 return;
248 db_helper(expr, param, key, RELEASE);
251 static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
253 struct sm_state *sm;
254 const char *param_name;
255 int param;
257 if (is_impossible_path())
258 return;
260 FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
261 if (sm->state != &param_released)
262 continue;
263 param = get_param_key_from_sm(sm, expr, &param_name);
264 if (param < 0)
265 continue;
266 sql_insert_return_states(return_id, return_ranges, RELEASE,
267 param, param_name, "");
268 } END_FOR_EACH_SM(sm);
271 enum {
272 UNKNOWN, FAIL, SUCCESS, NUM_BUCKETS
275 static int success_fail_positive(struct range_list *rl)
277 if (!rl)
278 return UNKNOWN;
280 if (sval_is_negative(rl_min(rl)) && sval_is_negative(rl_max(rl)))
281 return FAIL;
283 if (rl_min(rl).value == 0)
284 return SUCCESS;
286 return UNKNOWN;
289 static void check_balance(const char *name, struct symbol *sym)
291 struct range_list *inc_lines = NULL;
292 int inc_buckets[NUM_BUCKETS] = {};
293 struct stree *stree, *orig_stree;
294 struct smatch_state *state;
295 struct sm_state *return_sm;
296 struct sm_state *sm;
297 sval_t line = sval_type_val(&int_ctype, 0);
298 int bucket;
300 FOR_EACH_PTR(get_all_return_strees(), stree) {
301 orig_stree = __swap_cur_stree(stree);
303 if (is_impossible_path())
304 goto swap_stree;
305 if (db_incomplete())
306 goto swap_stree;
307 if (get_state(my_id, "path", NULL) == &ignore)
308 goto swap_stree;
310 return_sm = get_sm_state(RETURN_ID, "return_ranges", NULL);
311 if (!return_sm)
312 goto swap_stree;
313 line.value = return_sm->line;
315 sm = get_sm_state(my_id, name, sym);
316 if (!sm)
317 goto swap_stree;
319 state = sm->state;
320 if (state == &param_released)
321 state = &release;
323 if (state != &alloc &&
324 state != &release)
325 goto swap_stree;
327 bucket = success_fail_positive(estate_rl(return_sm->state));
328 if (bucket != FAIL)
329 goto swap_stree;
331 if (state == &alloc) {
332 add_range(&inc_lines, line, line);
333 inc_buckets[bucket] = true;
335 swap_stree:
336 __swap_cur_stree(orig_stree);
337 } END_FOR_EACH_PTR(stree);
339 if (inc_buckets[FAIL])
340 goto complain;
342 return;
344 complain:
345 sm_warning("'%s' not released on lines: %s.", name, show_rl(inc_lines));
348 static void match_check_balanced(struct symbol *sym)
350 struct sm_state *sm;
352 FOR_EACH_MY_SM(my_id, get_all_return_states(), sm) {
353 if (sm->sym == NULL && strcmp(sm->name, "path") == 0)
354 continue;
355 check_balance(sm->name, sm->sym);
356 } END_FOR_EACH_SM(sm);
359 void check_unwind(int id)
361 struct ref_func_info *info;
362 int i;
364 my_id = id;
366 if (option_project != PROJ_KERNEL)
367 return;
369 for (i = 0; i < ARRAY_SIZE(func_table); i++) {
370 info = &func_table[i];
372 if (info->call_back) {
373 add_function_hook(info->name, info->call_back, info);
374 } else if (info->implies_start && info->type == ALLOC) {
375 return_implies_exact(info->name,
376 *info->implies_start,
377 *info->implies_end,
378 &refcount_implied, info);
379 } else if (info->implies_start) {
380 return_implies_state_sval(info->name,
381 *info->implies_start,
382 *info->implies_end,
383 &refcount_implied, info);
384 } else {
385 add_function_hook(info->name, &refcount_function, info);
389 add_function_hook("devm_add_action_or_reset", &ignore_path, NULL);
391 add_unmatched_state_hook(my_id, &unmatched_state);
392 add_function_data(&fn_has_alloc);
394 add_split_return_callback(match_return_info);
395 select_return_states_hook(RELEASE, &db_dec);
396 add_hook(&match_check_balanced, END_FUNC_HOOK);