11f5207b7SJohn Levon /*
21f5207b7SJohn Levon  * Copyright (C) 2013 Oracle.
31f5207b7SJohn Levon  *
41f5207b7SJohn Levon  * This program is free software; you can redistribute it and/or
51f5207b7SJohn Levon  * modify it under the terms of the GNU General Public License
61f5207b7SJohn Levon  * as published by the Free Software Foundation; either version 2
71f5207b7SJohn Levon  * of the License, or (at your option) any later version.
81f5207b7SJohn Levon  *
91f5207b7SJohn Levon  * This program is distributed in the hope that it will be useful,
101f5207b7SJohn Levon  * but WITHOUT ANY WARRANTY; without even the implied warranty of
111f5207b7SJohn Levon  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
121f5207b7SJohn Levon  * GNU General Public License for more details.
131f5207b7SJohn Levon  *
141f5207b7SJohn Levon  * You should have received a copy of the GNU General Public License
151f5207b7SJohn Levon  * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
161f5207b7SJohn Levon  */
171f5207b7SJohn Levon 
181f5207b7SJohn Levon /*
191f5207b7SJohn Levon  * The way I'm detecting missing breaks is if there is an assignment inside a
201f5207b7SJohn Levon  * switch statement which is over written.
211f5207b7SJohn Levon  *
221f5207b7SJohn Levon  */
231f5207b7SJohn Levon 
241f5207b7SJohn Levon #include "smatch.h"
251f5207b7SJohn Levon #include "smatch_slist.h"
261f5207b7SJohn Levon 
271f5207b7SJohn Levon static int my_id;
281f5207b7SJohn Levon static struct expression *skip_this;
291f5207b7SJohn Levon 
301f5207b7SJohn Levon /*
311f5207b7SJohn Levon  * It goes like this:
321f5207b7SJohn Levon  * - Allocate a state which stores the switch expression.  I wanted to
331f5207b7SJohn Levon  *   just have a state &assigned but we need to know the switch statement where
341f5207b7SJohn Levon  *   it was assigned.
351f5207b7SJohn Levon  * - If it gets used then we change it to &used.
361f5207b7SJohn Levon  * - For unmatched states we use &used (because of cleanness, not because we need
371f5207b7SJohn Levon  *   to).
381f5207b7SJohn Levon  * - If we merge inside a case statement and one of the states is &assigned (or
391f5207b7SJohn Levon  *   if it is &nobreak) then &nobreak is used.
401f5207b7SJohn Levon  *
411f5207b7SJohn Levon  * We print an error when we assign something to a &no_break symbol.
421f5207b7SJohn Levon  *
431f5207b7SJohn Levon  */
441f5207b7SJohn Levon 
451f5207b7SJohn Levon STATE(used);
461f5207b7SJohn Levon STATE(no_break);
471f5207b7SJohn Levon 
481f5207b7SJohn Levon static int in_switch_stmt;
491f5207b7SJohn Levon 
alloc_my_state(struct expression * expr)501f5207b7SJohn Levon static struct smatch_state *alloc_my_state(struct expression *expr)
511f5207b7SJohn Levon {
521f5207b7SJohn Levon 	struct smatch_state *state;
531f5207b7SJohn Levon 	char *name;
541f5207b7SJohn Levon 
551f5207b7SJohn Levon 	state = __alloc_smatch_state(0);
561f5207b7SJohn Levon 	expr = strip_expr(expr);
571f5207b7SJohn Levon 	name = expr_to_str(expr);
581f5207b7SJohn Levon 	if (!name)
591f5207b7SJohn Levon 		name = alloc_string("");
601f5207b7SJohn Levon 	state->name = alloc_sname(name);
611f5207b7SJohn Levon 	free_string(name);
621f5207b7SJohn Levon 	state->data = expr;
631f5207b7SJohn Levon 	return state;
641f5207b7SJohn Levon }
651f5207b7SJohn Levon 
661f5207b7SJohn Levon struct expression *last_print_expr;
print_missing_break(struct expression * expr)671f5207b7SJohn Levon static void print_missing_break(struct expression *expr)
681f5207b7SJohn Levon {
691f5207b7SJohn Levon 	char *name;
701f5207b7SJohn Levon 
711f5207b7SJohn Levon 	if (get_switch_expr() == last_print_expr)
721f5207b7SJohn Levon 		return;
731f5207b7SJohn Levon 	last_print_expr = get_switch_expr();
741f5207b7SJohn Levon 
751f5207b7SJohn Levon 	name = expr_to_var(expr);
761f5207b7SJohn Levon 	sm_warning("missing break? reassigning '%s'", name);
771f5207b7SJohn Levon 	free_string(name);
781f5207b7SJohn Levon }
791f5207b7SJohn Levon 
match_assign(struct expression * expr)801f5207b7SJohn Levon static void match_assign(struct expression *expr)
811f5207b7SJohn Levon {
821f5207b7SJohn Levon 	struct expression *left;
831f5207b7SJohn Levon 
841f5207b7SJohn Levon 	if (expr->op != '=')
851f5207b7SJohn Levon 		return;
861f5207b7SJohn Levon 	if (!get_switch_expr())
871f5207b7SJohn Levon 		return;
881f5207b7SJohn Levon 	left = strip_expr(expr->left);
891f5207b7SJohn Levon 	if (get_state_expr(my_id, left) == &no_break)
901f5207b7SJohn Levon 		print_missing_break(left);
911f5207b7SJohn Levon 
921f5207b7SJohn Levon 	set_state_expr(my_id, left, alloc_my_state(get_switch_expr()));
931f5207b7SJohn Levon 	skip_this = left;
941f5207b7SJohn Levon }
951f5207b7SJohn Levon 
match_symbol(struct expression * expr)961f5207b7SJohn Levon static void match_symbol(struct expression *expr)
971f5207b7SJohn Levon {
981f5207b7SJohn Levon 	if (outside_of_function())
991f5207b7SJohn Levon 		return;
1001f5207b7SJohn Levon 	if (!get_switch_expr())
1011f5207b7SJohn Levon 		return;
1021f5207b7SJohn Levon 
1031f5207b7SJohn Levon 	expr = strip_expr(expr);
1041f5207b7SJohn Levon 	if (expr == skip_this)
1051f5207b7SJohn Levon 		return;
1061f5207b7SJohn Levon 	set_state_expr(my_id, expr, &used);
1071f5207b7SJohn Levon }
1081f5207b7SJohn Levon 
unmatched_state(struct sm_state * sm)1091f5207b7SJohn Levon static struct smatch_state *unmatched_state(struct sm_state *sm)
1101f5207b7SJohn Levon {
1111f5207b7SJohn Levon 	return &used;
1121f5207b7SJohn Levon }
1131f5207b7SJohn Levon 
1141f5207b7SJohn Levon static int in_case;
merge_hook(struct smatch_state * s1,struct smatch_state * s2)1151f5207b7SJohn Levon static struct smatch_state *merge_hook(struct smatch_state *s1, struct smatch_state *s2)
1161f5207b7SJohn Levon {
1171f5207b7SJohn Levon 	struct expression *switch_expr;
1181f5207b7SJohn Levon 
1191f5207b7SJohn Levon 	if (s1 == &no_break || s2 == &no_break)
1201f5207b7SJohn Levon 		return &no_break;
1211f5207b7SJohn Levon 	if (!in_case)
1221f5207b7SJohn Levon 		return &used;
1231f5207b7SJohn Levon 	switch_expr = get_switch_expr();
1241f5207b7SJohn Levon 	if (s1->data == switch_expr || s2->data == switch_expr)
1251f5207b7SJohn Levon 		return &no_break;
1261f5207b7SJohn Levon 	return &used;
1271f5207b7SJohn Levon }
1281f5207b7SJohn Levon 
match_stmt(struct statement * stmt)1291f5207b7SJohn Levon static void match_stmt(struct statement *stmt)
1301f5207b7SJohn Levon {
1311f5207b7SJohn Levon 	if (stmt->type == STMT_CASE)
1321f5207b7SJohn Levon 		in_case = 1;
1331f5207b7SJohn Levon 	else
1341f5207b7SJohn Levon 		in_case = 0;
1351f5207b7SJohn Levon }
1361f5207b7SJohn Levon 
match_switch(struct statement * stmt)1371f5207b7SJohn Levon static void match_switch(struct statement *stmt)
1381f5207b7SJohn Levon {
1391f5207b7SJohn Levon 	if (stmt->type != STMT_SWITCH)
1401f5207b7SJohn Levon 		return;
1411f5207b7SJohn Levon 
1421f5207b7SJohn Levon 	in_switch_stmt++;
1431f5207b7SJohn Levon }
1441f5207b7SJohn Levon 
delete_my_states(int owner)1451f5207b7SJohn Levon static void delete_my_states(int owner)
1461f5207b7SJohn Levon {
1471f5207b7SJohn Levon 	struct state_list *slist = NULL;
1481f5207b7SJohn Levon 	struct sm_state *sm;
1491f5207b7SJohn Levon 
1501f5207b7SJohn Levon 	FOR_EACH_MY_SM(owner, __get_cur_stree(), sm) {
1511f5207b7SJohn Levon 		add_ptr_list(&slist, sm);
1521f5207b7SJohn Levon 	} END_FOR_EACH_SM(sm);
1531f5207b7SJohn Levon 
1541f5207b7SJohn Levon 	FOR_EACH_PTR(slist, sm) {
1551f5207b7SJohn Levon 		delete_state(sm->owner, sm->name, sm->sym);
1561f5207b7SJohn Levon 	} END_FOR_EACH_PTR(sm);
1571f5207b7SJohn Levon 
1581f5207b7SJohn Levon 	free_slist(&slist);
1591f5207b7SJohn Levon }
1601f5207b7SJohn Levon 
match_switch_end(struct statement * stmt)1611f5207b7SJohn Levon static void match_switch_end(struct statement *stmt)
1621f5207b7SJohn Levon {
1631f5207b7SJohn Levon 
1641f5207b7SJohn Levon 	if (stmt->type != STMT_SWITCH)
1651f5207b7SJohn Levon 		return;
1661f5207b7SJohn Levon 
1671f5207b7SJohn Levon 	in_switch_stmt--;
1681f5207b7SJohn Levon 
1691f5207b7SJohn Levon 	if (!in_switch_stmt)
1701f5207b7SJohn Levon 		delete_my_states(my_id);
1711f5207b7SJohn Levon }
1721f5207b7SJohn Levon 
check_missing_break(int id)1731f5207b7SJohn Levon void check_missing_break(int id)
1741f5207b7SJohn Levon {
1751f5207b7SJohn Levon 	my_id = id;
1761f5207b7SJohn Levon 
1771f5207b7SJohn Levon 	if (!option_spammy)
1781f5207b7SJohn Levon 		return;
1791f5207b7SJohn Levon 
180*efe51d0cSJohn Levon 	set_dynamic_states(my_id);
1811f5207b7SJohn Levon 	add_unmatched_state_hook(my_id, &unmatched_state);
1821f5207b7SJohn Levon 	add_merge_hook(my_id, &merge_hook);
1831f5207b7SJohn Levon 
1841f5207b7SJohn Levon 	add_hook(&match_assign, ASSIGNMENT_HOOK);
1851f5207b7SJohn Levon 	add_hook(&match_symbol, SYM_HOOK);
1861f5207b7SJohn Levon 	add_hook(&match_stmt, STMT_HOOK);
1871f5207b7SJohn Levon 	add_hook(&match_switch, STMT_HOOK);
1881f5207b7SJohn Levon 	add_hook(&match_switch_end, STMT_HOOK_AFTER);
1891f5207b7SJohn Levon }
190