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 /*
  19  * The point here is to store the relationships between two variables.
  20  * Ie:  y > x.
  21  * To do that we create a state with the two variables in alphabetical order:
  22  * ->name = "x vs y" and the state would be "<".  On the false path the state
  23  * would be ">=".
  24  *
  25  * Part of the trick of it is that if x or y is modified then we need to reset
  26  * the state.  We need to keep a list of all the states which depend on x and
  27  * all the states which depend on y.  The link_id code handles this.
  28  *
  29  */
  30 
  31 #include "smatch.h"
  32 #include "smatch_extra.h"
  33 #include "smatch_slist.h"
  34 
  35 static int compare_id;
  36 static int link_id;
  37 
  38 static struct smatch_state *alloc_link_state(struct string_list *links)
  39 {
  40         struct smatch_state *state;
  41         static char buf[256];
  42         char *tmp;
  43         int i;
  44 
  45         state = __alloc_smatch_state(0);
  46 
  47         i = 0;
  48         FOR_EACH_PTR(links, tmp) {
  49                 if (!i++) {
  50                         snprintf(buf, sizeof(buf), "%s", tmp);
  51                 } else {
  52                         append(buf, ", ", sizeof(buf));
  53                         append(buf, tmp, sizeof(buf));
  54                 }
  55         } END_FOR_EACH_PTR(tmp);
  56 
  57         state->name = alloc_sname(buf);
  58         state->data = links;
  59         return state;
  60 }
  61 
  62 static struct smatch_state *merge_links(struct smatch_state *s1, struct smatch_state *s2)
  63 {
  64         struct smatch_state *ret;
  65         struct string_list *links;
  66 
  67         links = combine_string_lists(s1->data, s2->data);
  68         ret = alloc_link_state(links);
  69         return ret;
  70 }
  71 
  72 static void save_link_var_sym(const char *var, struct symbol *sym, const char *link)
  73 {
  74         struct smatch_state *old_state, *new_state;
  75         struct string_list *links;
  76         char *new;
  77 
  78         old_state = get_state(link_id, var, sym);
  79         if (old_state)
  80                 links = clone_str_list(old_state->data);
  81         else
  82                 links = NULL;
  83 
  84         new = alloc_sname(link);
  85         insert_string(&links, new);
  86 
  87         new_state = alloc_link_state(links);
  88         set_state(link_id, var, sym, new_state);
  89 }
  90 
  91 static void add_comparison_var_sym(const char *left_name,
  92                 struct var_sym_list *left_vsl,
  93                 int comparison,
  94                 const char *right_name, struct var_sym_list *right_vsl)
  95 {
  96         struct smatch_state *state;
  97         struct var_sym *vs;
  98         char state_name[256];
  99 
 100         if (strcmp(left_name, right_name) > 0) {
 101                 const char *tmp_name = left_name;
 102                 struct var_sym_list *tmp_vsl = left_vsl;
 103 
 104                 left_name = right_name;
 105                 left_vsl = right_vsl;
 106                 right_name = tmp_name;
 107                 right_vsl = tmp_vsl;
 108                 comparison = flip_comparison(comparison);
 109         }
 110         snprintf(state_name, sizeof(state_name), "%s vs %s", left_name, right_name);
 111         state = alloc_compare_state(NULL, left_name, left_vsl, comparison, NULL, right_name, right_vsl);
 112 
 113         set_state(compare_id, state_name, NULL, state);
 114 
 115         FOR_EACH_PTR(left_vsl, vs) {
 116                 save_link_var_sym(vs->var, vs->sym, state_name);
 117         } END_FOR_EACH_PTR(vs);
 118         FOR_EACH_PTR(right_vsl, vs) {
 119                 save_link_var_sym(vs->var, vs->sym, state_name);
 120         } END_FOR_EACH_PTR(vs);
 121 }
 122 
 123 /*
 124  * This is quite a bit more limitted, less ambitious, simpler compared to
 125  * smatch_camparison.c.
 126  */
 127 void __compare_param_limit_hook(struct expression *left_expr, struct expression *right_expr,
 128                                 const char *state_name,
 129                                 struct smatch_state *true_state, struct smatch_state *false_state)
 130 {
 131         char *left_name = NULL;
 132         char *right_name = NULL;
 133         char *tmp_name = NULL;
 134         struct symbol *left_sym, *right_sym, *tmp_sym;
 135 
 136         left_name = expr_to_var_sym(left_expr, &left_sym);
 137         if (!left_name || !left_sym)
 138                 goto free;
 139         right_name = expr_to_var_sym(right_expr, &right_sym);
 140         if (!right_name || !right_sym)
 141                 goto free;
 142 
 143         if (get_param_num_from_sym(left_sym) < 0 ||
 144             get_param_num_from_sym(right_sym) < 0)
 145                 return;
 146 
 147         tmp_name = get_other_name_sym(left_name, left_sym, &tmp_sym);
 148         if (tmp_name) {
 149                 free_string(left_name);
 150                 left_name = tmp_name;
 151                 left_sym = tmp_sym;
 152         }
 153 
 154         tmp_name = get_other_name_sym(right_name, right_sym, &tmp_sym);
 155         if (tmp_name) {
 156                 free_string(right_name);
 157                 right_name = tmp_name;
 158                 right_sym = tmp_sym;
 159         }
 160 
 161         if (param_was_set_var_sym(left_name, left_sym))
 162                 return;
 163         if (param_was_set_var_sym(right_name, right_sym))
 164                 return;
 165 
 166         set_true_false_states(compare_id, state_name, NULL, true_state, false_state);
 167         save_link_var_sym(left_name, left_sym, state_name);
 168         save_link_var_sym(right_name, right_sym, state_name);
 169 free:
 170         free_string(left_name);
 171         free_string(right_name);
 172 }
 173 
 174 static void print_return_comparison(int return_id, char *return_ranges, struct expression *expr)
 175 {
 176         struct sm_state *tmp;
 177         struct string_list *links;
 178         char *link;
 179         struct sm_state *sm;
 180         struct compare_data *data;
 181         struct var_sym *left, *right;
 182         int left_param, right_param;
 183         static char left_buf[256];
 184         static char right_buf[256];
 185         static char info_buf[256];
 186         const char *tmp_name;
 187 
 188         FOR_EACH_MY_SM(link_id, __get_cur_stree(), tmp) {
 189                 links = tmp->state->data;
 190                 FOR_EACH_PTR(links, link) {
 191                         sm = get_sm_state(compare_id, link, NULL);
 192                         if (!sm)
 193                                 continue;
 194                         data = sm->state->data;
 195                         if (!data || !data->comparison)
 196                                 continue;
 197                         if (ptr_list_size((struct ptr_list *)data->left_vsl) != 1 ||
 198                             ptr_list_size((struct ptr_list *)data->right_vsl) != 1)
 199                                 continue;
 200                         left = first_ptr_list((struct ptr_list *)data->left_vsl);
 201                         right = first_ptr_list((struct ptr_list *)data->right_vsl);
 202                         if (left->sym == right->sym &&
 203                             strcmp(left->var, right->var) == 0)
 204                                 continue;
 205                         /*
 206                          * Both parameters link to this comparison so only
 207                          * record the first one.
 208                          */
 209                         if (left->sym != tmp->sym ||
 210                             strcmp(left->var, tmp->name) != 0)
 211                                 continue;
 212 
 213                         left_param = get_param_num_from_sym(left->sym);
 214                         right_param = get_param_num_from_sym(right->sym);
 215                         if (left_param < 0 || right_param < 0) /* can't happen hopefully */
 216                                 continue;
 217 
 218                         tmp_name = get_param_name_var_sym(left->var, left->sym);
 219                         if (!tmp_name)
 220                                 continue;
 221                         snprintf(left_buf, sizeof(left_buf), "%s", tmp_name);
 222 
 223                         tmp_name = get_param_name_var_sym(right->var, right->sym);
 224                         if (!tmp_name || tmp_name[0] != '$')
 225                                 continue;
 226                         snprintf(right_buf, sizeof(right_buf), "$%d%s", right_param, tmp_name + 1);
 227 
 228                         snprintf(info_buf, sizeof(info_buf), "%s %s", show_special(data->comparison), right_buf);
 229                         sql_insert_return_states(return_id, return_ranges,
 230                                         COMPARE_LIMIT, left_param, left_buf, info_buf);
 231                 } END_FOR_EACH_PTR(link);
 232 
 233         } END_FOR_EACH_SM(tmp);
 234 }
 235 
 236 static int parse_comparison(char **value, int *op)
 237 {
 238 
 239         *op = **value;
 240 
 241         switch (*op) {
 242         case '<':
 243                 (*value)++;
 244                 if (**value == '=') {
 245                         (*value)++;
 246                         *op = SPECIAL_LTE;
 247                 }
 248                 break;
 249         case '=':
 250                 (*value)++;
 251                 (*value)++;
 252                 *op = SPECIAL_EQUAL;
 253                 break;
 254         case '!':
 255                 (*value)++;
 256                 (*value)++;
 257                 *op = SPECIAL_NOTEQUAL;
 258                 break;
 259         case '>':
 260                 (*value)++;
 261                 if (**value == '=') {
 262                         (*value)++;
 263                         *op = SPECIAL_GTE;
 264                 }
 265                 break;
 266         default:
 267                 return 0;
 268         }
 269 
 270         if (**value != ' ') {
 271                 sm_perror("parsing comparison.  %s", *value);
 272                 return 0;
 273         }
 274 
 275         (*value)++;
 276         return 1;
 277 }
 278 
 279 static int split_op_param_key(char *value, int *op, int *param, char **key)
 280 {
 281         static char buf[256];
 282         char *p;
 283 
 284         if (!parse_comparison(&value, op))
 285                 return 0;
 286 
 287         snprintf(buf, sizeof(buf), value);
 288 
 289         p = buf;
 290         if (*p++ != '$')
 291                 return 0;
 292 
 293         *param = atoi(p);
 294         if (*param < 0 || *param > 99)
 295                 return 0;
 296         p++;
 297         if (*param > 9)
 298                 p++;
 299         p--;
 300         *p = '$';
 301         *key = p;
 302 
 303         return 1;
 304 }
 305 
 306 static void db_return_comparison(struct expression *expr, int left_param, char *key, char *value)
 307 {
 308         struct expression *left_arg, *right_arg;
 309         char *left_name = NULL;
 310         struct symbol *left_sym;
 311         char *right_name = NULL;
 312         struct symbol *right_sym;
 313         int op;
 314         int right_param;
 315         char *right_key;
 316         struct var_sym_list *left_vsl = NULL, *right_vsl = NULL;
 317 
 318         while (expr->type == EXPR_ASSIGNMENT)
 319                 expr = strip_expr(expr->right);
 320         if (expr->type != EXPR_CALL)
 321                 return;
 322 
 323         if (!split_op_param_key(value, &op, &right_param, &right_key))
 324                 return;
 325 
 326         left_arg = get_argument_from_call_expr(expr->args, left_param);
 327         if (!left_arg)
 328                 return;
 329 
 330         right_arg = get_argument_from_call_expr(expr->args, right_param);
 331         if (!right_arg)
 332                 return;
 333 
 334         left_name = get_variable_from_key(left_arg, key, &left_sym);
 335         if (!left_name || !left_sym)
 336                 goto free;
 337         if (get_param_num_from_sym(left_sym) < 0)
 338                 goto free;
 339 
 340         right_name = get_variable_from_key(right_arg, right_key, &right_sym);
 341         if (!right_name || !right_sym)
 342                 goto free;
 343         if (get_param_num_from_sym(right_sym) < 0)
 344                 goto free;
 345 
 346         add_var_sym(&left_vsl, left_name, left_sym);
 347         add_var_sym(&right_vsl, right_name, right_sym);
 348 
 349         add_comparison_var_sym(left_name, left_vsl, op, right_name, right_vsl);
 350 
 351 free:
 352         free_string(left_name);
 353         free_string(right_name);
 354 }
 355 
 356 void register_param_compare_limit(int id)
 357 {
 358         compare_id = id;
 359 
 360         add_merge_hook(compare_id, &merge_compare_states);
 361         add_split_return_callback(&print_return_comparison);
 362 
 363         select_return_states_hook(COMPARE_LIMIT, &db_return_comparison);
 364 }
 365 
 366 void register_param_compare_limit_links(int id)
 367 {
 368         link_id = id;
 369 
 370         add_merge_hook(link_id, &merge_links);
 371 
 372 }
 373