1 /*
   2  * Copyright (C) 2009 Dan Carpenter.
   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  * This check is supposed to find places like this:
  20  * err = foo();
  21  * err = bar();
  22  * if (err)
  23  *         return err;
  24  * (the first assignment isn't used)
  25  *
  26  * How the check works is that every assignment gets an ID.
  27  * We store that assignment ID in a list of assignments that
  28  * haven't been used.  We also set the state of 'err' from
  29  * the example above to be.  Then when we use 'err' we remove
  30  * it from the list.  At the end of the function we print
  31  * a list of assignments that still haven't been used.
  32  *
  33  * Note that this check only works for assignments to
  34  * EXPR_SYMBOL.  Maybe it could be modified to cover other
  35  * assignments later but then you would have to deal with
  36  * scope issues.
  37  *
  38  * Also this state is quite tied to the order the callbacks
  39  * are called in smatch_flow.c.  (If the order changed it
  40  * would break).
  41  *
  42  */
  43 
  44 #include "smatch.h"
  45 #include "smatch_slist.h"
  46 #include "smatch_function_hashtable.h"
  47 
  48 static int my_id;
  49 
  50 struct assignment {
  51         int assign_id;
  52         char *name;
  53         char *function;
  54         int line;
  55 };
  56 ALLOCATOR(assignment, "assignment id");
  57 DECLARE_PTR_LIST(assignment_list, struct assignment);
  58 static struct assignment_list *assignment_list;
  59 
  60 static struct expression *skip_this;
  61 static int assign_id;
  62 
  63 static DEFINE_HASHTABLE_INSERT(insert_func, char, int);
  64 static DEFINE_HASHTABLE_SEARCH(search_func, char, int);
  65 static struct hashtable *ignored_funcs;
  66 
  67 static const char *kernel_ignored[] = {
  68         "inb",
  69         "inl",
  70         "inw",
  71         "readb",
  72         "readl",
  73         "readw",
  74 };
  75 
  76 static char *get_fn_name(struct expression *expr)
  77 {
  78         if (expr->type != EXPR_CALL)
  79                 return NULL;
  80         if (expr->fn->type != EXPR_SYMBOL)
  81                 return NULL;
  82         return expr_to_var(expr->fn);
  83 }
  84 
  85 static int ignored_function(struct expression *expr)
  86 {
  87         char *func;
  88         int ret = 0;
  89 
  90         func = get_fn_name(expr);
  91         if (!func)
  92                 return 0;
  93         if (search_func(ignored_funcs, func))
  94                 ret = 1;
  95         free_string(func);
  96         return ret;
  97 }
  98 
  99 static void match_assign_call(struct expression *expr)
 100 {
 101         struct expression *left;
 102         struct assignment *assign;
 103 
 104         if (final_pass)
 105                 return;
 106         if (in_condition())
 107                 return;
 108         if (expr->op != '=')
 109                 return;
 110         if (unreachable())
 111                 return;
 112         if (ignored_function(expr->right))
 113                 return;
 114         left = strip_expr(expr->left);
 115         if (!left || left->type != EXPR_SYMBOL)
 116                 return;
 117         if (left->symbol->ctype.modifiers & (MOD_TOPLEVEL | MOD_EXTERN | MOD_STATIC))
 118                 return;
 119 
 120         skip_this = left;
 121 
 122         set_state_expr(my_id, left, alloc_state_num(assign_id));
 123 
 124         assign = __alloc_assignment(0);
 125         assign->assign_id = assign_id++;
 126         assign->name = expr_to_var(left);
 127         assign->function = get_fn_name(expr->right);
 128         assign->line = get_lineno();
 129         add_ptr_list(&assignment_list, assign);
 130 }
 131 
 132 static void match_assign(struct expression *expr)
 133 {
 134         struct expression *left;
 135 
 136         if (expr->op != '=')
 137                 return;
 138         left = strip_expr(expr->left);
 139         if (!left || left->type != EXPR_SYMBOL)
 140                 return;
 141         set_state_expr(my_id, left, &undefined);
 142 }
 143 
 144 static void delete_used(int assign_id)
 145 {
 146         struct assignment *tmp;
 147 
 148         FOR_EACH_PTR(assignment_list, tmp) {
 149                 if (tmp->assign_id == assign_id) {
 150                         DELETE_CURRENT_PTR(tmp);
 151                         return;
 152                 }
 153         } END_FOR_EACH_PTR(tmp);
 154 }
 155 
 156 static void delete_used_symbols(struct state_list *possible)
 157 {
 158         struct sm_state *tmp;
 159 
 160         FOR_EACH_PTR(possible, tmp) {
 161                 delete_used(PTR_INT(tmp->state->data));
 162         } END_FOR_EACH_PTR(tmp);
 163 }
 164 
 165 static void match_symbol(struct expression *expr)
 166 {
 167         struct sm_state *sm;
 168 
 169         expr = strip_expr(expr);
 170         if (expr == skip_this)
 171                 return;
 172         sm = get_sm_state_expr(my_id, expr);
 173         if (!sm)
 174                 return;
 175         delete_used_symbols(sm->possible);
 176         set_state_expr(my_id, expr, &undefined);
 177 }
 178 
 179 static void match_end_func(struct symbol *sym)
 180 {
 181         struct assignment *tmp;
 182 
 183         if (__inline_fn)
 184                 return;
 185         FOR_EACH_PTR(assignment_list, tmp) {
 186                 sm_printf("%s:%d %s() ", get_filename(), tmp->line, get_function());
 187                 sm_printf("warn: unused return: %s = %s()\n",
 188                         tmp->name, tmp->function);
 189         } END_FOR_EACH_PTR(tmp);
 190 }
 191 
 192 static void match_after_func(struct symbol *sym)
 193 {
 194         if (__inline_fn)
 195                 return;
 196         clear_assignment_alloc();
 197         __free_ptr_list((struct ptr_list **)&assignment_list);
 198 }
 199 
 200 void check_unused_ret(int id)
 201 {
 202         my_id = id;
 203 
 204         /* It turns out that this test is worthless unless you use --two-passes.  */
 205         if (!option_two_passes)
 206                 return;
 207         add_hook(&match_assign_call, CALL_ASSIGNMENT_HOOK);
 208         add_hook(&match_assign, ASSIGNMENT_HOOK);
 209         add_hook(&match_symbol, SYM_HOOK);
 210         add_hook(&match_end_func, END_FUNC_HOOK);
 211         add_hook(&match_after_func, AFTER_FUNC_HOOK);
 212         ignored_funcs = create_function_hashtable(100);
 213         if (option_project == PROJ_KERNEL) {
 214                 int i;
 215 
 216                 for (i = 0; i < ARRAY_SIZE(kernel_ignored); i++)
 217                         insert_func(ignored_funcs, (char *)kernel_ignored[i], (int *)1);
 218         }
 219 }