xref: /illumos-gate/usr/src/tools/smatch/src/check_unreachable.c (revision 8226594fdd4479be135127f43632f1f995074654)
1 /*
2  * Copyright (C) 2014 Oracle.
3  *
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.
8  *
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.
13  *
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
16  */
17 
18 #include "smatch.h"
19 
20 static int my_id;
21 
22 static int print_unreached = 1;
23 static struct string_list *turn_off_names;
24 static struct string_list *ignore_names;
25 
26 static int empty_statement(struct statement *stmt)
27 {
28 	if (!stmt)
29 		return 0;
30 	if (stmt->type == STMT_EXPRESSION && !stmt->expression)
31 		return 1;
32 	return 0;
33 }
34 
35 static int is_last_stmt(struct statement *cur_stmt)
36 {
37 	struct symbol *fn = get_base_type(cur_func_sym);
38 	struct statement *stmt;
39 
40 	if (!fn)
41 		return 0;
42 	stmt = fn->stmt;
43 	if (!stmt)
44 		stmt = fn->inline_stmt;
45 	if (!stmt || stmt->type != STMT_COMPOUND)
46 		return 0;
47 	stmt = last_ptr_list((struct ptr_list *)stmt->stmts);
48 	if (stmt == cur_stmt)
49 		return 1;
50 	return 0;
51 }
52 
53 static void print_unreached_initializers(struct symbol_list *sym_list)
54 {
55 	struct symbol *sym;
56 
57 	FOR_EACH_PTR(sym_list, sym) {
58 		if (sym->initializer && !(sym->ctype.modifiers & MOD_STATIC))
59 			sm_msg("info: '%s' is not actually initialized (unreached code).",
60 				(sym->ident ? sym->ident->name : "this variable"));
61 	} END_FOR_EACH_PTR(sym);
62 }
63 
64 static int is_ignored_macro(struct statement *stmt)
65 {
66 	char *name;
67 	char *tmp;
68 
69 	name = get_macro_name(stmt->pos);
70 	if (!name)
71 		return 0;
72 
73 	FOR_EACH_PTR(ignore_names, tmp) {
74 		if (strcmp(tmp, name) == 0)
75 			return 1;
76 	} END_FOR_EACH_PTR(tmp);
77 
78 	return 0;
79 }
80 
81 static int prev_line_was_endif(struct statement *stmt)
82 {
83 	struct token *token;
84 	struct position pos = stmt->pos;
85 
86 	pos.line--;
87 	pos.pos = 2;
88 
89 	token = pos_get_token(pos);
90 	if (token && token_type(token) == TOKEN_IDENT &&
91 	    strcmp(show_ident(token->ident), "endif") == 0)
92 		return 1;
93 
94 	pos.line--;
95 	token = pos_get_token(pos);
96 	if (token && token_type(token) == TOKEN_IDENT &&
97 	    strcmp(show_ident(token->ident), "endif") == 0)
98 		return 1;
99 
100 	return 0;
101 }
102 
103 static int we_jumped_into_the_middle_of_a_loop(struct statement *stmt)
104 {
105 	struct statement *prev;
106 
107 	/*
108 	 * Smatch doesn't handle loops correctly and this is a hack.  What we
109 	 * do is that if the first unreachable statement is a loop and the
110 	 * previous statement was a goto then it's probably code like this:
111 	 * 	goto first;
112 	 * 	for (;;) {
113 	 *		frob();
114 	 * first:
115 	 *		more_frob();
116 	 * 	}
117 	 * Every statement is reachable but only on the second iteration.
118 	 */
119 
120 	if (stmt->type != STMT_ITERATOR)
121 		return 0;
122 	prev = get_prev_statement();
123 	if (prev && prev->type == STMT_GOTO)
124 		return 1;
125 	return 0;
126 }
127 
128 static void unreachable_stmt(struct statement *stmt)
129 {
130 
131 	if (__inline_fn)
132 		return;
133 
134 	if (!__path_is_null()) {
135 		print_unreached = 1;
136 		return;
137 	}
138 
139 	/* if we hit a label then assume there is a matching goto */
140 	if (stmt->type == STMT_LABEL)
141 		print_unreached = 0;
142 	if (prev_line_was_endif(stmt))
143 		print_unreached = 0;
144 	if (we_jumped_into_the_middle_of_a_loop(stmt))
145 		print_unreached = 0;
146 
147 	if (!print_unreached)
148 		return;
149 	if (empty_statement(stmt))
150 		return;
151 	if (is_ignored_macro(stmt))
152 		return;
153 
154 	switch (stmt->type) {
155 	case STMT_COMPOUND: /* after a switch before a case stmt */
156 	case STMT_RANGE:
157 	case STMT_CASE:
158 		return;
159 	case STMT_DECLARATION: /* switch (x) { int a; case foo: ... */
160 		print_unreached_initializers(stmt->declaration);
161 		return;
162 	case STMT_RETURN: /* gcc complains if you don't have a return statement */
163 		if (is_last_stmt(stmt))
164 			return;
165 		break;
166 	case STMT_GOTO:
167 		/* people put extra breaks inside switch statements */
168 		if (stmt->goto_label && stmt->goto_label->type == SYM_NODE &&
169 		    strcmp(stmt->goto_label->ident->name, "break") == 0)
170 			return;
171 		break;
172 	default:
173 		break;
174 	}
175 	sm_warning("ignoring unreachable code.");
176 	print_unreached = 0;
177 }
178 
179 static int is_turn_off(char *name)
180 {
181 	char *tmp;
182 
183 	if (!name)
184 		return 0;
185 
186 	FOR_EACH_PTR(turn_off_names, tmp) {
187 		if (strcmp(tmp, name) == 0)
188 			return 1;
189 	} END_FOR_EACH_PTR(tmp);
190 
191 	return 0;
192 }
193 
194 static char *get_function_name(struct statement *stmt)
195 {
196 	struct expression *expr;
197 
198 	if (stmt->type != STMT_EXPRESSION)
199 		return NULL;
200 	expr = stmt->expression;
201 	if (!expr || expr->type != EXPR_CALL)
202 		return NULL;
203 	if (expr->fn->type != EXPR_SYMBOL || !expr->fn->symbol_name)
204 		return NULL;
205 	return expr->fn->symbol_name->name;
206 }
207 
208 static void turn_off_unreachable(struct statement *stmt)
209 {
210 	char *name;
211 
212 	name = get_macro_name(stmt->pos);
213 	if (is_turn_off(name)) {
214 		print_unreached = 0;
215 		return;
216 	}
217 
218 	if (stmt->type == STMT_IF &&
219 	    known_condition_true(stmt->if_conditional) &&  __path_is_null()) {
220 		print_unreached = 0;
221 		return;
222 	}
223 
224 	name = get_function_name(stmt);
225 	if (is_turn_off(name))
226 		print_unreached = 0;
227 }
228 
229 static void register_turn_off_macros(void)
230 {
231 	struct token *token;
232 	char *macro;
233 	char name[256];
234 
235 	if (option_project == PROJ_NONE)
236 		strcpy(name, "unreachable.turn_off");
237 	else
238 		snprintf(name, 256, "%s.unreachable.turn_off", option_project_str);
239 
240 	token = get_tokens_file(name);
241 	if (!token)
242 		return;
243 	if (token_type(token) != TOKEN_STREAMBEGIN)
244 		return;
245 	token = token->next;
246 	while (token_type(token) != TOKEN_STREAMEND) {
247 		if (token_type(token) != TOKEN_IDENT)
248 			return;
249 		macro = alloc_string(show_ident(token->ident));
250 		add_ptr_list(&turn_off_names, macro);
251 		token = token->next;
252 	}
253 	clear_token_alloc();
254 }
255 
256 static void register_ignored_macros(void)
257 {
258 	struct token *token;
259 	char *macro;
260 	char name[256];
261 
262 	if (option_project == PROJ_NONE)
263 		strcpy(name, "unreachable.ignore");
264 	else
265 		snprintf(name, 256, "%s.unreachable.ignore", option_project_str);
266 
267 	token = get_tokens_file(name);
268 	if (!token)
269 		return;
270 	if (token_type(token) != TOKEN_STREAMBEGIN)
271 		return;
272 	token = token->next;
273 	while (token_type(token) != TOKEN_STREAMEND) {
274 		if (token_type(token) != TOKEN_IDENT)
275 			return;
276 		macro = alloc_string(show_ident(token->ident));
277 		add_ptr_list(&ignore_names, macro);
278 		token = token->next;
279 	}
280 	clear_token_alloc();
281 }
282 
283 void check_unreachable(int id)
284 {
285 	my_id = id;
286 
287 	register_turn_off_macros();
288 	register_ignored_macros();
289 	add_hook(&unreachable_stmt, STMT_HOOK);
290 	add_hook(&turn_off_unreachable, STMT_HOOK_AFTER);
291 }
292