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 
  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;
  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 
  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 
  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 
 109 static struct smatch_state *unmatched_state(struct sm_state *sm)
 110 {
 111         return &used;
 112 }
 113 
 114 static int in_case;
 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 
 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 
 137 static void match_switch(struct statement *stmt)
 138 {
 139         if (stmt->type != STMT_SWITCH)
 140                 return;
 141 
 142         in_switch_stmt++;
 143 }
 144 
 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 
 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 
 173 void check_missing_break(int id)
 174 {
 175         my_id = id;
 176 
 177         if (!option_spammy)
 178                 return;
 179 
 180         add_unmatched_state_hook(my_id, &unmatched_state);
 181         add_merge_hook(my_id, &merge_hook);
 182 
 183         add_hook(&match_assign, ASSIGNMENT_HOOK);
 184         add_hook(&match_symbol, SYM_HOOK);
 185         add_hook(&match_stmt, STMT_HOOK);
 186         add_hook(&match_switch, STMT_HOOK);
 187         add_hook(&match_switch_end, STMT_HOOK_AFTER);
 188 }