comparison: add support for COMPARE_LIMIT
[smatch.git] / check_direct_return_instead_of_goto.c
blob24cf89b60ba4a2b411642eadba2abddc12ed7fdc
1 /*
2 * Copyright (C) 2022 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 "smatch.h"
19 #include "smatch_extra.h"
20 #include "smatch_slist.h"
22 static int my_id;
24 static struct statement *goto_stmt, *next_goto;
25 static struct expression *return_expr;
27 static unsigned long set_label;
28 static unsigned long label_cnt;
30 static void reset(void)
32 goto_stmt = NULL;
33 next_goto = NULL;
34 return_expr = NULL;
37 static bool is_do_nothing_goto(struct statement *goto_stmt)
39 struct symbol *fn;
40 struct statement *stmt;
42 fn = get_base_type(cur_func_sym);
43 if (!fn)
44 return false;
45 stmt = fn->stmt;
46 if (!stmt)
47 stmt = fn->inline_stmt;
48 if (!stmt || stmt->type != STMT_COMPOUND)
49 return false;
50 stmt = last_ptr_list((struct ptr_list *)stmt->stmts);
51 if (!stmt)
52 return false;
53 if (stmt->type != STMT_LABEL)
54 return false;
56 if (!stmt->label_identifier ||
57 stmt->label_identifier->type != SYM_LABEL ||
58 !stmt->label_identifier->ident)
59 return false;
61 if (strcmp(stmt->label_identifier->ident->name,
62 goto_stmt->goto_label->ident->name) == 0)
63 return true;
65 return false;
68 static bool is_printk_stmt(struct statement *stmt)
70 char *str;
72 if (!stmt)
73 return false;
75 str = pos_ident(stmt->pos);
76 if (!str)
77 return false;
79 if (strcmp(str, "dev_err") == 0 ||
80 strcmp(str, "dev_info") == 0 ||
81 strcmp(str, "dev_warn") == 0 ||
82 strcmp(str, "dev_notice") == 0 ||
83 strcmp(str, "dev_dbg") == 0)
84 return true;
86 if (strcmp(str, "pr_err") == 0 ||
87 strcmp(str, "pr_info") == 0 ||
88 strcmp(str, "pr_warn") == 0 ||
89 strcmp(str, "pr_notice") == 0 ||
90 strcmp(str, "pr_debug") == 0)
91 return true;
93 if (strstr(str, "_dev_err") ||
94 strstr(str, "_dev_warn") ||
95 strstr(str, "_dev_dbg"))
96 return true;
98 return false;
101 static bool label_name_matches(struct statement *goto_stmt,
102 struct statement *stmt)
104 if (!stmt)
105 return false;
107 if (stmt->type != STMT_LABEL)
108 return false;
110 if (!stmt->label_identifier ||
111 stmt->label_identifier->type != SYM_LABEL ||
112 !stmt->label_identifier->ident)
113 return false;
115 if (strcmp(stmt->label_identifier->ident->name,
116 goto_stmt->goto_label->ident->name) == 0)
117 return true;
119 return false;
122 static bool is_printk_goto(struct statement *goto_stmt)
124 struct symbol *fn;
125 struct statement *stmt, *tmp;
127 fn = get_base_type(cur_func_sym);
128 if (!fn)
129 return false;
130 stmt = fn->stmt;
131 if (!stmt)
132 stmt = fn->inline_stmt;
133 if (!stmt || stmt->type != STMT_COMPOUND)
134 return false;
136 FOR_EACH_PTR_REVERSE(stmt->stmts, tmp) {
137 if (tmp->type == STMT_RETURN)
138 continue;
139 if (tmp->type == STMT_LABEL &&
140 !is_printk_stmt(tmp->label_statement))
141 return false;
142 if (is_printk_stmt(tmp))
143 continue;
144 if (label_name_matches(goto_stmt, tmp))
145 return true;
146 return false;
147 } END_FOR_EACH_PTR_REVERSE(tmp);
149 return false;
152 static void match_goto(struct statement *stmt)
154 /* Find the first goto */
156 if (next_goto)
157 return;
159 if (stmt->type != STMT_GOTO ||
160 !stmt->goto_label ||
161 stmt->goto_label->type != SYM_LABEL ||
162 !stmt->goto_label->ident)
163 return;
165 if (is_do_nothing_goto(stmt))
166 return;
168 if (is_printk_goto(stmt))
169 return;
171 goto_stmt = stmt;
174 static void match_return(struct expression *expr)
176 struct range_list *rl;
178 if (next_goto)
179 return;
180 if (!goto_stmt)
181 return;
183 if (!expr ||
184 !get_implied_rl(expr, &rl) ||
185 success_fail_return(rl) != RET_FAIL) {
186 reset();
187 return;
190 return_expr = expr;
193 static void match_next_goto(struct statement *stmt)
195 if (next_goto)
196 return;
198 if (stmt->type != STMT_GOTO ||
199 !stmt->goto_label ||
200 stmt->goto_label->type != SYM_LABEL ||
201 !stmt->goto_label->ident)
202 return;
204 if (!goto_stmt || !return_expr) {
205 reset();
206 return;
209 next_goto = stmt;
212 static void add_label_cnt(struct statement *stmt)
214 if (!next_goto) {
215 reset();
216 return;
219 if (!stmt ||
220 !stmt->label_identifier ||
221 stmt->label_identifier->type != SYM_LABEL ||
222 !stmt->label_identifier->ident)
223 return;
225 if (strcmp(stmt->label_identifier->ident->name,
226 goto_stmt->goto_label->ident->name) == 0)
227 label_cnt++;
229 if (strcmp(stmt->label_identifier->ident->name,
230 next_goto->goto_label->ident->name) == 0)
231 label_cnt++;
234 static void match_label(struct statement *stmt)
236 sval_t sval;
238 if (stmt->type != STMT_LABEL)
239 return;
241 if (get_state(my_id, "cleanup", NULL) == &true_state) {
242 /* The second label in a cleanup block is still cleanup. */
243 set_label = true;
244 add_label_cnt(stmt);
245 return;
249 * If the cleanup block isn't preceded by a "return 0;" then it could
250 * be a success path.
253 if (__get_cur_stree())
254 return;
255 if (!__prev_stmt || __prev_stmt->type != STMT_RETURN)
256 return;
257 if (!get_implied_value(__prev_stmt->ret_value, &sval) || sval.value != 0)
258 return;
259 /* We can't set the state until after we've merged the gotos. */
260 set_label = true;
261 add_label_cnt(stmt);
264 static void match_label_after(struct statement *stmt)
266 if (stmt->type != STMT_LABEL)
267 return;
268 if (set_label) {
269 set_state(my_id, "cleanup", NULL, &true_state);
270 set_label = false;
274 static void match_final_return(struct expression *expr)
276 struct range_list *rl;
278 if (!expr)
279 return;
280 if (!next_goto)
281 return;
282 if (label_cnt != 2)
283 return;
285 if (get_state(my_id, "cleanup", NULL) != &true_state)
286 return;
288 if (!get_implied_rl(expr, &rl))
289 return;
290 if (success_fail_return(rl) != RET_FAIL)
291 return;
293 sm_warning_line(return_expr->pos.line, "missing unwind goto?");
296 void check_direct_return_instead_of_goto(int id)
298 my_id = id;
300 if (option_project != PROJ_KERNEL)
301 return;
303 add_function_data(&set_label);
304 add_function_data(&label_cnt);
306 add_function_data((unsigned long *)&goto_stmt);
307 add_function_data((unsigned long *)&return_expr);
308 add_function_data((unsigned long *)&next_goto);
310 // check them in reverse order
311 add_hook(&match_next_goto, STMT_HOOK);
312 add_hook(&match_return, RETURN_HOOK);
313 add_hook(&match_goto, STMT_HOOK);
314 add_hook(&match_final_return, RETURN_HOOK);
317 add_hook(&match_label, STMT_HOOK);
318 add_hook(&match_label_after, STMT_HOOK_AFTER);