1 /*
   2  * Copyright (C) 2016 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 #include "smatch.h"
  19 #include "smatch_extra.h"
  20 #include "smatch_slist.h"
  21 
  22 static int my_id;
  23 
  24 STATE(inc);
  25 STATE(orig);
  26 STATE(dec);
  27 
  28 static void db_inc_dec(struct expression *expr, int param, const char *key, const char *value, int inc_dec)
  29 {
  30         struct smatch_state *start_state;
  31         struct expression *arg;
  32         char *name;
  33         struct symbol *sym;
  34 
  35         while (expr->type == EXPR_ASSIGNMENT)
  36                 expr = strip_expr(expr->right);
  37         if (expr->type != EXPR_CALL)
  38                 return;
  39 
  40         arg = get_argument_from_call_expr(expr->args, param);
  41         if (!arg)
  42                 return;
  43 
  44         name = get_variable_from_key(arg, key, &sym);
  45         if (!name || !sym)
  46                 goto free;
  47 
  48         start_state = get_state(my_id, name, sym);
  49 
  50         if (inc_dec == ATOMIC_INC) {
  51 //              if (start_state == &inc)
  52 //                      sm_error("XXX double increment '%s'", name);
  53                 set_state(my_id, name, sym, &inc);
  54         } else {
  55 //              if (start_state == &dec)
  56 //                      sm_error("XXX double decrement '%s'", name);
  57                 if (start_state == &inc)
  58                         set_state(my_id, name, sym, &orig);
  59                 else
  60                         set_state(my_id, name, sym, &dec);
  61         }
  62 
  63 free:
  64         free_string(name);
  65 }
  66 
  67 static void db_inc(struct expression *expr, int param, char *key, char *value)
  68 {
  69         db_inc_dec(expr, param, key, value, ATOMIC_INC);
  70 }
  71 
  72 static void db_dec(struct expression *expr, int param, char *key, char *value)
  73 {
  74         db_inc_dec(expr, param, key, value, ATOMIC_DEC);
  75 }
  76 
  77 static void match_atomic_inc(const char *fn, struct expression *expr, void *_unused)
  78 {
  79         db_inc_dec(expr, 0, "$->counter", "", ATOMIC_INC);
  80 }
  81 
  82 static void match_atomic_dec(const char *fn, struct expression *expr, void *_unused)
  83 {
  84         db_inc_dec(expr, 0, "$->counter", "", ATOMIC_DEC);
  85 }
  86 
  87 static void match_atomic_add(const char *fn, struct expression *expr, void *_unused)
  88 {
  89         struct expression *amount;
  90         sval_t sval;
  91 
  92         amount = get_argument_from_call_expr(expr->args, 0);
  93         if (get_implied_value(amount, &sval) && sval_is_negative(sval)) {
  94                 db_inc_dec(expr, 1, "$->counter", "", ATOMIC_DEC);
  95                 return;
  96         }
  97 
  98         db_inc_dec(expr, 1, "$->counter", "", ATOMIC_INC);
  99 }
 100 
 101 static void match_atomic_sub(const char *fn, struct expression *expr, void *_unused)
 102 {
 103         db_inc_dec(expr, 1, "$->counter", "", ATOMIC_DEC);
 104 }
 105 
 106 static void refcount_inc(const char *fn, struct expression *expr, void *param)
 107 {
 108         db_inc_dec(expr, PTR_INT(param), "$->ref.counter", "", ATOMIC_INC);
 109 }
 110 
 111 static void refcount_dec(const char *fn, struct expression *expr, void *param)
 112 {
 113         db_inc_dec(expr, PTR_INT(param), "$->ref.counter", "", ATOMIC_DEC);
 114 }
 115 
 116 static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
 117 {
 118         struct sm_state *sm;
 119         const char *param_name;
 120         int param;
 121 
 122         FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
 123                 if (sm->state != &inc &&
 124                     sm->state != &dec)
 125                         continue;
 126                 param = get_param_num_from_sym(sm->sym);
 127                 if (param < 0)
 128                         continue;
 129                 param_name = get_param_name(sm);
 130                 if (!param_name)
 131                         continue;
 132                 sql_insert_return_states(return_id, return_ranges,
 133                                          (sm->state == &inc) ? ATOMIC_INC : ATOMIC_DEC,
 134                                          param, param_name, "");
 135         } END_FOR_EACH_SM(sm);
 136 }
 137 
 138 enum {
 139         NEGATIVE, ZERO, POSITIVE,
 140 };
 141 
 142 static int success_fail_positive(struct range_list *rl)
 143 {
 144         if (!rl)
 145                 return ZERO;
 146 
 147         if (sval_is_negative(rl_min(rl)))
 148                 return NEGATIVE;
 149 
 150         if (rl_min(rl).value == 0)
 151                 return ZERO;
 152 
 153         return POSITIVE;
 154 }
 155 
 156 static void check_counter(const char *name, struct symbol *sym)
 157 {
 158         struct range_list *inc_lines = NULL;
 159         struct range_list *dec_lines = NULL;
 160         int inc_buckets[3] = {};
 161         struct stree *stree;
 162         struct sm_state *return_sm;
 163         struct sm_state *sm;
 164         sval_t line = sval_type_val(&int_ctype, 0);
 165 
 166         FOR_EACH_PTR(get_all_return_strees(), stree) {
 167                 return_sm = get_sm_state_stree(stree, RETURN_ID, "return_ranges", NULL);
 168                 if (!return_sm)
 169                         continue;
 170                 line.value = return_sm->line;
 171 
 172                 sm = get_sm_state_stree(stree, my_id, name, sym);
 173                 if (!sm)
 174                         continue;
 175 
 176                 if (sm->state != &inc && sm->state != &dec)
 177                         continue;
 178 
 179                 if (sm->state == &inc) {
 180                         add_range(&inc_lines, line, line);
 181                         inc_buckets[success_fail_positive(estate_rl(return_sm->state))] = 1;
 182                 }
 183                 if (sm->state == &dec)
 184                         add_range(&dec_lines, line, line);
 185         } END_FOR_EACH_PTR(stree);
 186 
 187         if (inc_buckets[NEGATIVE] &&
 188             inc_buckets[ZERO]) {
 189                 // sm_warning("XXX '%s' not decremented on lines: %s.", name, show_rl(inc_lines));
 190         }
 191 
 192 }
 193 
 194 static void match_check_missed(struct symbol *sym)
 195 {
 196         struct sm_state *sm;
 197 
 198         FOR_EACH_MY_SM(my_id, get_all_return_states(), sm) {
 199                 check_counter(sm->name, sm->sym);
 200         } END_FOR_EACH_SM(sm);
 201 }
 202 
 203 int on_atomic_dec_path(void)
 204 {
 205         struct sm_state *sm;
 206 
 207         FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
 208                 if (sm->state == &dec)
 209                         return 1;
 210         } END_FOR_EACH_SM(sm);
 211 
 212         return 0;
 213 }
 214 
 215 int was_inced(const char *name, struct symbol *sym)
 216 {
 217         return get_state(my_id, name, sym) == &inc;
 218 }
 219 
 220 void check_atomic_inc_dec(int id)
 221 {
 222         my_id = id;
 223 
 224         if (option_project != PROJ_KERNEL)
 225                 return;
 226 
 227         select_return_states_hook(ATOMIC_INC, &db_inc);
 228         select_return_states_hook(ATOMIC_DEC, &db_dec);
 229         add_function_hook("atomic_inc_return", &match_atomic_inc, NULL);
 230         add_function_hook("atomic_add_return", &match_atomic_add, NULL);
 231         add_function_hook("atomic_sub_return", &match_atomic_sub, NULL);
 232         add_function_hook("atomic_sub_and_test", &match_atomic_sub, NULL);
 233         add_function_hook("atomic_dec_and_test", &match_atomic_dec, NULL);
 234         add_function_hook("_atomic_dec_and_lock", &match_atomic_dec, NULL);
 235         add_function_hook("atomic_dec", &match_atomic_dec, NULL);
 236         add_function_hook("atomic_long_inc", &match_atomic_inc, NULL);
 237         add_function_hook("atomic_long_dec", &match_atomic_dec, NULL);
 238         add_function_hook("atomic_inc", &match_atomic_inc, NULL);
 239         add_function_hook("atomic_sub", &match_atomic_sub, NULL);
 240         add_split_return_callback(match_return_info);
 241 
 242         add_function_hook("refcount_add_not_zero", &refcount_inc, INT_PTR(1));
 243         add_function_hook("refcount_inc_not_zero", &refcount_inc, INT_PTR(0));
 244         add_function_hook("refcount_sub_and_test", &refcount_dec, INT_PTR(1));
 245         add_function_hook("refcount_dec_and_test", &refcount_dec, INT_PTR(1));
 246 
 247         add_hook(&match_check_missed, END_FUNC_HOOK);
 248 }