1 /*
2  * Copyright (C) 2013 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 /*
19  * The way I'm detecting missing breaks is if there is an assignment inside a
20  * switch statement which is over written.
21  *
22  */
23 
24 #include "smatch.h"
25 #include "smatch_slist.h"
26 
27 static int my_id;
28 static struct expression *skip_this;
29 
30 /*
31  * It goes like this:
32  * - Allocate a state which stores the switch expression.  I wanted to
33  *   just have a state &assigned but we need to know the switch statement where
34  *   it was assigned.
35  * - If it gets used then we change it to &used.
36  * - For unmatched states we use &used (because of cleanness, not because we need
37  *   to).
38  * - If we merge inside a case statement and one of the states is &assigned (or
39  *   if it is &nobreak) then &nobreak is used.
40  *
41  * We print an error when we assign something to a &no_break symbol.
42  *
43  */
44 
45 STATE(used);
46 STATE(no_break);
47 
48 static int in_switch_stmt;
49 
alloc_my_state(struct expression * expr)50 static struct smatch_state *alloc_my_state(struct expression *expr)
51 {
52 	struct smatch_state *state;
53 	char *name;
54 
55 	state = __alloc_smatch_state(0);
56 	expr = strip_expr(expr);
57 	name = expr_to_str(expr);
58 	if (!name)
59 		name = alloc_string("");
60 	state->name = alloc_sname(name);
61 	free_string(name);
62 	state->data = expr;
63 	return state;
64 }
65 
66 struct expression *last_print_expr;
print_missing_break(struct expression * expr)67 static void print_missing_break(struct expression *expr)
68 {
69 	char *name;
70 
71 	if (get_switch_expr() == last_print_expr)
72 		return;
73 	last_print_expr = get_switch_expr();
74 
75 	name = expr_to_var(expr);
76 	sm_warning("missing break? reassigning '%s'", name);
77 	free_string(name);
78 }
79 
match_assign(struct expression * expr)80 static void match_assign(struct expression *expr)
81 {
82 	struct expression *left;
83 
84 	if (expr->op != '=')
85 		return;
86 	if (!get_switch_expr())
87 		return;
88 	left = strip_expr(expr->left);
89 	if (get_state_expr(my_id, left) == &no_break)
90 		print_missing_break(left);
91 
92 	set_state_expr(my_id, left, alloc_my_state(get_switch_expr()));
93 	skip_this = left;
94 }
95 
match_symbol(struct expression * expr)96 static void match_symbol(struct expression *expr)
97 {
98 	if (outside_of_function())
99 		return;
100 	if (!get_switch_expr())
101 		return;
102 
103 	expr = strip_expr(expr);
104 	if (expr == skip_this)
105 		return;
106 	set_state_expr(my_id, expr, &used);
107 }
108 
unmatched_state(struct sm_state * sm)109 static struct smatch_state *unmatched_state(struct sm_state *sm)
110 {
111 	return &used;
112 }
113 
114 static int in_case;
merge_hook(struct smatch_state * s1,struct smatch_state * s2)115 static struct smatch_state *merge_hook(struct smatch_state *s1, struct smatch_state *s2)
116 {
117 	struct expression *switch_expr;
118 
119 	if (s1 == &no_break || s2 == &no_break)
120 		return &no_break;
121 	if (!in_case)
122 		return &used;
123 	switch_expr = get_switch_expr();
124 	if (s1->data == switch_expr || s2->data == switch_expr)
125 		return &no_break;
126 	return &used;
127 }
128 
match_stmt(struct statement * stmt)129 static void match_stmt(struct statement *stmt)
130 {
131 	if (stmt->type == STMT_CASE)
132 		in_case = 1;
133 	else
134 		in_case = 0;
135 }
136 
match_switch(struct statement * stmt)137 static void match_switch(struct statement *stmt)
138 {
139 	if (stmt->type != STMT_SWITCH)
140 		return;
141 
142 	in_switch_stmt++;
143 }
144 
delete_my_states(int owner)145 static void delete_my_states(int owner)
146 {
147 	struct state_list *slist = NULL;
148 	struct sm_state *sm;
149 
150 	FOR_EACH_MY_SM(owner, __get_cur_stree(), sm) {
151 		add_ptr_list(&slist, sm);
152 	} END_FOR_EACH_SM(sm);
153 
154 	FOR_EACH_PTR(slist, sm) {
155 		delete_state(sm->owner, sm->name, sm->sym);
156 	} END_FOR_EACH_PTR(sm);
157 
158 	free_slist(&slist);
159 }
160 
match_switch_end(struct statement * stmt)161 static void match_switch_end(struct statement *stmt)
162 {
163 
164 	if (stmt->type != STMT_SWITCH)
165 		return;
166 
167 	in_switch_stmt--;
168 
169 	if (!in_switch_stmt)
170 		delete_my_states(my_id);
171 }
172 
check_missing_break(int id)173 void check_missing_break(int id)
174 {
175 	my_id = id;
176 
177 	if (!option_spammy)
178 		return;
179 
180 	set_dynamic_states(my_id);
181 	add_unmatched_state_hook(my_id, &unmatched_state);
182 	add_merge_hook(my_id, &merge_hook);
183 
184 	add_hook(&match_assign, ASSIGNMENT_HOOK);
185 	add_hook(&match_symbol, SYM_HOOK);
186 	add_hook(&match_stmt, STMT_HOOK);
187 	add_hook(&match_switch, STMT_HOOK);
188 	add_hook(&match_switch_end, STMT_HOOK_AFTER);
189 }
190