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
27static int my_id;
28static 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
45STATE(used);
46STATE(no_break);
47
48static int in_switch_stmt;
49
50static 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
66struct expression *last_print_expr;
67static 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
80static 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
96static 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
109static struct smatch_state *unmatched_state(struct sm_state *sm)
110{
111	return &used;
112}
113
114static int in_case;
115static 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
129static 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
137static void match_switch(struct statement *stmt)
138{
139	if (stmt->type != STMT_SWITCH)
140		return;
141
142	in_switch_stmt++;
143}
144
145static 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
161static 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
173void 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