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
21 #include "smatch_extra.h"
22 #include "smatch_slist.h"
31 STATE(param_released
);
35 static unsigned long fn_has_alloc
;
37 struct ref_func_info
{
42 const sval_t
*implies_start
, *implies_end
;
46 static struct ref_func_info func_table
[] = {
47 { "clk_prepare", ALLOC
, 0, "$", &int_zero
, &int_zero
},
48 { "clk_prepare_enable", ALLOC
, 0, "$", &int_zero
, &int_zero
},
49 { "clk_disable_unprepare", RELEASE
, 0, "$" },
50 { "clk_unprepare", RELEASE
, 0, "$" },
52 { "alloc_etherdev_mqs", ALLOC
, -1, "$", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
53 { "free_netdev", RELEASE
, 0, "$" },
56 * FIXME: A common pattern in release functions like amd76xrom_cleanup()
59 * if (window->rsrc.parent)
60 * release_resource(&window->rsrc);
62 * Which is slightly tricky to know how to merge the states so let's
63 * hold off checking request_resource() for now.
65 * { "request_resource", ALLOC, 1, "$", &int_zero, &int_zero },
66 * { "release_resource", RELEASE, 0, "$" },
70 { "pci_request_regions", ALLOC
, 0, "$", &int_zero
, &int_zero
},
71 { "pci_release_regions", RELEASE
, 0, "$" },
73 { "request_free_mem_region", ALLOC
, -1, "$->start", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
74 { "__request_region", ALLOC
, 1, "$", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
75 { "release_and_free_resource", RELEASE
, 0, "$->start" },
76 { "release_resource", RELEASE
, 0, "$->start" },
77 { "__release_region", RELEASE
, 1, "$" },
79 { "ioremap", ALLOC
, -1, "$", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
80 { "of_iomap", ALLOC
, -1, "$", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
81 { "ioremap_encrypted", ALLOC
, -1, "$", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
82 { "iounmap", RELEASE
, 0, "$" },
84 { "request_threaded_irq", ALLOC
, 0, "$", &int_zero
, &int_zero
},
85 { "request_irq", ALLOC
, 0, "$", &int_zero
, &int_zero
},
86 { "free_irq", RELEASE
, 0, "$" },
87 { "pci_request_irq", ALLOC
, 1, "$", &int_zero
, &int_zero
},
88 { "pci_free_irq", RELEASE
, 1, "$" },
90 { "register_netdev", ALLOC
, 0, "$", &int_zero
, &int_zero
},
91 { "unregister_netdev", RELEASE
, 0, "$" },
93 { "misc_register", ALLOC
, 0, "$", &int_zero
, &int_zero
},
94 { "misc_deregister", RELEASE
, 0, "$" },
96 { "ieee80211_alloc_hw", ALLOC
, -1, "$", &valid_ptr_min_sval
, &valid_ptr_max_sval
},
97 { "ieee80211_free_hw", RELEASE
, 0, "$" },
99 { "request_firmware", ALLOC
, 0, "*$", &int_zero
, &int_zero
},
100 { "release_firmware", RELEASE
, 0, "$" },
103 static struct smatch_state
*unmatched_state(struct sm_state
*sm
)
105 struct smatch_state
*state
;
107 if (sm
->state
!= ¶m_released
)
110 if (is_impossible_path())
111 return ¶m_released
;
113 state
= get_state(SMATCH_EXTRA
, sm
->name
, sm
->sym
);
116 if (!estate_rl(state
) || is_err_or_null(estate_rl(state
)))
117 return ¶m_released
;
118 if (parent_is_err_or_null_var_sym(sm
->name
, sm
->sym
))
119 return ¶m_released
;
121 if (estate_min(state
).value
== 0 &&
122 estate_max(state
).value
== 0)
123 return ¶m_released
;
124 if (estate_type(state
) == &int_ctype
&&
125 sval_is_negative(estate_min(state
)) &&
126 (estate_max(state
).value
== -1 || estate_max(state
).value
== 0))
127 return ¶m_released
;
132 static void pre_merge_hook(struct sm_state
*cur
, struct sm_state
*other
)
134 if (cur
->state
== ¶m_released
)
136 if (is_impossible_path())
137 set_state(my_id
, cur
->name
, cur
->sym
, &undefined
);
140 static bool is_param_var_sym(const char *name
, struct symbol
*sym
)
144 return get_param_key_from_var_sym(name
, sym
, NULL
, &key
) >= 0;
147 static bool mark_same_name_as_undefined(const char *name
)
157 FOR_EACH_MY_SM(my_id
, __get_cur_stree(), sm
) {
158 if (strncmp(sm
->name
, name
, len
) != 0)
160 if (sm
->name
[len
] != ':')
162 set_state(sm
->owner
, sm
->name
, sm
->sym
, &unknown
);
164 } END_FOR_EACH_SM(sm
);
169 static void mark_partial_matches_as_undefined(const char *key
)
172 int start_pos
, state_len
, key_len
;
175 while ((p
= strchr(key
, '-'))) {
180 key_len
= strlen(key
);
182 FOR_EACH_MY_SM(my_id
, __get_cur_stree(), sm
) {
183 state_len
= strlen(sm
->name
);
184 if (state_len
< key_len
)
187 start_pos
= state_len
- key_len
;
188 if ((start_pos
== 0 || !isalnum(sm
->name
[start_pos
- 1])) &&
189 strcmp(sm
->name
+ start_pos
, key
) == 0)
190 update_ssa_state(my_id
, sm
->name
, sm
->sym
, &unknown
);
192 } END_FOR_EACH_SM(sm
);
195 static void mark_matches_as_undefined(const char *name
)
197 if (mark_same_name_as_undefined(name
))
199 mark_partial_matches_as_undefined(name
);
202 static bool is_alloc_primitive(struct expression
*expr
)
206 while (expr
->type
== EXPR_ASSIGNMENT
)
207 expr
= strip_expr(expr
->right
);
208 if (expr
->type
!= EXPR_CALL
)
211 if (expr
->fn
->type
!= EXPR_SYMBOL
)
214 for (i
= 0; i
< ARRAY_SIZE(func_table
); i
++) {
215 if (sym_name_is(func_table
[i
].name
, expr
->fn
))
222 static void return_param_alloc(struct expression
*expr
, const char *name
, struct symbol
*sym
, void *data
)
224 struct smatch_state
*state
;
229 if (strncmp(name
, "__fake_", 7) == 0)
232 if (sym
->ctype
.modifiers
& (MOD_NONLOCAL
| MOD_STATIC
| MOD_ADDRESSABLE
))
235 while (expr
->type
== EXPR_ASSIGNMENT
)
236 expr
= strip_expr(expr
->right
);
237 if (expr
->type
!= EXPR_CALL
)
239 fn_name
= expr_to_var(expr
->fn
);
243 state
= alloc_state_str(fn_name
);
244 state
->data
= &alloc
;
246 set_ssa_state(my_id
, name
, sym
, state
);
249 static void return_param_release(struct expression
*expr
, const char *name
, struct symbol
*sym
, void *data
)
251 struct sm_state
*start_sm
;
253 /* The !data means this comes from the DB (not hard coded). */
254 if (!data
&& is_alloc_primitive(expr
))
257 start_sm
= get_ssa_sm_state(my_id
, name
, sym
);
259 update_ssa_sm(my_id
, start_sm
, &release
);
262 mark_matches_as_undefined(name
);
265 if (is_param_var_sym(name
, sym
))
266 set_state(info_id
, name
, sym
, ¶m_released
);
270 static void return_param_ignore(struct expression
*expr
, const char *name
, struct symbol
*sym
, void *data
)
272 struct sm_state
*start_sm
;
274 start_sm
= get_ssa_sm_state(my_id
, name
, sym
);
277 update_ssa_sm(my_id
, start_sm
, &ignore
);
280 static void match_sm_assign(struct sm_state
*sm
, struct expression
*mod_expr
)
285 if (!mod_expr
|| mod_expr
->type
!= EXPR_ASSIGNMENT
)
288 left
= expr_to_str(mod_expr
->left
);
289 if (!left
|| strchr(left
, '['))
294 set_state(my_id
, sm
->name
, sm
->sym
, &ignore
);
297 static void unwind_return_info_callback(int return_id
, char *return_ranges
,
298 struct expression
*returned_expr
,
300 const char *printed_name
,
305 if (sm
->state
!= ¶m_released
)
307 if (is_impossible_path())
310 sql_insert_return_states(return_id
, return_ranges
, RELEASE
,
311 param
, printed_name
, "");
314 static const char *get_alloc_fn(struct sm_state
*sm
)
316 struct sm_state
*tmp
;
318 if (slist_has_state(sm
->possible
, &unknown
))
321 if (sm
->state
->data
== &alloc
)
322 return sm
->state
->name
;
324 FOR_EACH_PTR(sm
->possible
, tmp
) {
325 if (tmp
->state
->data
== &alloc
)
326 return tmp
->state
->name
;
327 } END_FOR_EACH_PTR(tmp
);
332 static void check_balance(const char *name
, struct symbol
*sym
)
334 struct range_list
*inc_lines
= NULL
;
335 int inc_buckets
[RET_UNKNOWN
+ 1] = {};
336 struct stree
*stree
, *orig_stree
;
337 struct smatch_state
*state
;
338 struct sm_state
*return_sm
;
340 sval_t line
= sval_type_val(&int_ctype
, 0);
341 const char *tmp_name
, *fn_name
= NULL
;
344 FOR_EACH_PTR(get_all_return_strees(), stree
) {
345 orig_stree
= __swap_cur_stree(stree
);
347 if (is_impossible_path())
351 if (has_devm_cleanup())
354 return_sm
= get_sm_state(RETURN_ID
, "return_ranges", NULL
);
357 line
.value
= return_sm
->line
;
359 sm
= get_sm_state(my_id
, name
, sym
);
364 if (state
== ¶m_released
)
367 tmp_name
= get_alloc_fn(sm
);
373 if (state
!= &alloc
&&
377 bucket
= success_fail_return(estate_rl(return_sm
->state
));
378 if (bucket
!= RET_FAIL
)
381 if (state
== &alloc
) {
382 add_range(&inc_lines
, line
, line
);
383 inc_buckets
[bucket
] = true;
386 __swap_cur_stree(orig_stree
);
387 } END_FOR_EACH_PTR(stree
);
389 if (inc_buckets
[RET_FAIL
])
395 sm_warning("'%s' from %s() not released on lines: %s.", ssa_name(name
), fn_name
, show_rl(inc_lines
));
398 static void match_check_balanced(struct symbol
*sym
)
402 FOR_EACH_MY_SM(my_id
, get_all_return_states(), sm
) {
403 check_balance(sm
->name
, sm
->sym
);
404 } END_FOR_EACH_SM(sm
);
407 void check_unwind(int id
)
409 struct ref_func_info
*info
;
414 if (option_project
!= PROJ_KERNEL
)
417 set_dynamic_states(my_id
);
418 add_pre_merge_hook(my_id
, &pre_merge_hook
);
420 for (i
= 0; i
< ARRAY_SIZE(func_table
); i
++) {
421 param_key_hook
*hook
;
423 info
= &func_table
[i
];
424 if (info
->type
== ALLOC
)
425 hook
= &return_param_alloc
;
426 else if (info
->type
== RELEASE
)
427 hook
= &return_param_release
;
428 else if (info
->type
== IGNORE
)
429 hook
= &return_param_ignore
;
433 if (info
->call_back
) {
434 add_function_hook(info
->name
, info
->call_back
, info
);
435 } else if (info
->implies_start
&& info
->type
== ALLOC
) {
436 return_implies_param_key_exact(info
->name
,
437 *info
->implies_start
,
439 hook
, info
->param
, info
->key
, info
);
440 } else if (info
->implies_start
) {
441 return_implies_param_key(info
->name
,
442 *info
->implies_start
,
444 hook
, info
->param
, info
->key
, info
);
446 add_function_param_key_hook(info
->name
,
447 hook
, info
->param
, info
->key
, info
);
451 add_ssa_state_assigned_hook(my_id
, match_sm_assign
);
453 add_function_data(&fn_has_alloc
);
455 select_return_param_key(RELEASE
, &return_param_release
);
456 add_hook(&match_check_balanced
, END_FUNC_HOOK
);
459 void check_unwind_info(int id
)
463 add_unmatched_state_hook(info_id
, &unmatched_state
);
464 add_return_info_callback(info_id
, unwind_return_info_callback
);