1 /*
   2  * Copyright (C) 2018 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 <stdlib.h>
  19 #include "parse.h"
  20 #include "smatch.h"
  21 #include "smatch_slist.h"
  22 #include "smatch_extra.h"
  23 
  24 static int my_id;
  25 static int barrier_id;
  26 
  27 STATE(nospec);
  28 
  29 static bool in_nospec_stmt;
  30 
  31 static struct smatch_state *unmatched_state(struct sm_state *sm)
  32 {
  33         struct range_list *rl;
  34 
  35         if (__in_function_def && !get_user_rl_var_sym(sm->name, sm->sym, &rl))
  36                 return &nospec;
  37         return &undefined;
  38 }
  39 
  40 bool is_nospec(struct expression *expr)
  41 {
  42         char *macro;
  43 
  44         if (in_nospec_stmt)
  45                 return true;
  46         if (!expr)
  47                 return false;
  48         if (get_state_expr(my_id, expr) == &nospec)
  49                 return true;
  50         macro = get_macro_name(expr->pos);
  51         if (macro && strcmp(macro, "array_index_nospec") == 0)
  52                 return true;
  53         return false;
  54 }
  55 
  56 static void nospec_assign(struct expression *expr)
  57 {
  58         if (is_nospec(expr->right))
  59                 set_state_expr(my_id, expr->left, &nospec);
  60 }
  61 
  62 static void set_param_nospec(const char *name, struct symbol *sym, char *key, char *value)
  63 {
  64         char fullname[256];
  65 
  66         if (strcmp(key, "*$") == 0)
  67                 snprintf(fullname, sizeof(fullname), "*%s", name);
  68         else if (strncmp(key, "$", 1) == 0)
  69                 snprintf(fullname, 256, "%s%s", name, key + 1);
  70         else
  71                 return;
  72 
  73         set_state(my_id, fullname, sym, &nospec);
  74 }
  75 
  76 static void match_call_info(struct expression *expr)
  77 {
  78         struct expression *arg;
  79         int i = 0;
  80 
  81         FOR_EACH_PTR(expr->args, arg) {
  82                 if (get_state_expr(my_id, arg) == &nospec)
  83                         sql_insert_caller_info(expr, NOSPEC, i, "$", "");
  84                 i++;
  85         } END_FOR_EACH_PTR(arg);
  86 }
  87 
  88 static void struct_member_callback(struct expression *call, int param, char *printed_name, struct sm_state *sm)
  89 {
  90         struct range_list *rl;
  91 
  92         if (!get_user_rl_var_sym(sm->name, sm->sym, &rl))
  93                 return;
  94         sql_insert_caller_info(call, NOSPEC, param, printed_name, "");
  95 }
  96 
  97 static void returned_struct_members(int return_id, char *return_ranges, struct expression *expr)
  98 {
  99         struct stree *start_states = get_start_states();
 100         struct symbol *returned_sym;
 101         struct sm_state *sm;
 102         const char *param_name;
 103         struct range_list *rl;
 104         int param;
 105 
 106         returned_sym = expr_to_sym(expr);
 107 
 108         FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
 109                 if (get_state_stree(start_states, my_id, sm->name, sm->sym) == sm->state)
 110                         continue;
 111                 param = get_param_num_from_sym(sm->sym);
 112                 if (param < 0) {
 113                         if (!returned_sym || returned_sym != sm->sym)
 114                                 continue;
 115                         param = -1;
 116                 }
 117 
 118                 param_name = get_param_name(sm);
 119                 if (!param_name)
 120                         continue;
 121                 if (param != -1 && strcmp(param_name, "$") == 0)
 122                         continue;
 123 
 124                 if (!get_user_rl_var_sym(sm->name, sm->sym, &rl))
 125                         continue;
 126 
 127                 sql_insert_return_states(return_id, return_ranges, NOSPEC, param, param_name, "");
 128         } END_FOR_EACH_SM(sm);
 129 
 130         if (is_nospec(expr) && get_user_rl(expr, &rl))
 131                 sql_insert_return_states(return_id, return_ranges, NOSPEC, -1, "$", "");
 132 
 133         if (get_state(barrier_id, "barrier", NULL) == &nospec)
 134                 sql_insert_return_states(return_id, return_ranges, NOSPEC_WB, -1, "", "");
 135 }
 136 
 137 static int is_return_statement(void)
 138 {
 139         if (__cur_stmt && __cur_stmt->type == STMT_RETURN)
 140                 return 1;
 141         return 0;
 142 }
 143 
 144 static void db_returns_nospec(struct expression *expr, int param, char *key, char *value)
 145 {
 146         struct expression *call;
 147         struct expression *arg;
 148         char *name;
 149         struct symbol *sym;
 150 
 151         call = expr;
 152         while (call->type == EXPR_ASSIGNMENT)
 153                 call = strip_expr(call->right);
 154         if (call->type != EXPR_CALL)
 155                 return;
 156 
 157         if (param == -1 && expr->type == EXPR_ASSIGNMENT) {
 158                 name = get_variable_from_key(expr->left, key, &sym);
 159         } else if (param == -1 && is_return_statement()) {
 160                 in_nospec_stmt = true;
 161                 return;
 162         } else {
 163                 arg = get_argument_from_call_expr(call->args, param);
 164                 if (!arg)
 165                         return;
 166                 name = get_variable_from_key(arg, key, &sym);
 167         }
 168         if (!name || !sym)
 169                 goto free;
 170 
 171         set_state(my_id, name, sym, &nospec);
 172 free:
 173         free_string(name);
 174 }
 175 
 176 static int is_nospec_asm(struct statement *stmt)
 177 {
 178         char *macro;
 179 
 180         if (!stmt || stmt->type != STMT_ASM)
 181                 return 0;
 182         if (!stmt->asm_string)
 183                 return 0;
 184         macro = get_macro_name(stmt->asm_string->pos);
 185         if (!macro || strcmp(macro, "CALL_NOSPEC") != 0)
 186                 return 0;
 187         return 1;
 188 }
 189 
 190 static void match_asm(struct statement *stmt)
 191 {
 192         if (is_nospec_asm(stmt))
 193                 in_nospec_stmt = true;
 194 }
 195 
 196 static void match_after_nospec_asm(struct statement *stmt)
 197 {
 198         in_nospec_stmt = false;
 199 }
 200 
 201 static void mark_user_data_as_nospec(void)
 202 {
 203         struct stree *stree;
 204         struct symbol *type;
 205         struct sm_state *sm;
 206 
 207         stree = get_user_stree();
 208         FOR_EACH_SM(stree, sm) {
 209                 if (is_whole_rl(estate_rl(sm->state)))
 210                         continue;
 211                 type = estate_type(sm->state);
 212                 if (!type || type->type != SYM_BASETYPE)
 213                         continue;
 214                 if (!is_capped_var_sym(sm->name, sm->sym))
 215                         continue;
 216                 set_state(my_id, sm->name, sm->sym, &nospec);
 217         } END_FOR_EACH_SM(sm);
 218         free_stree(&stree);
 219 }
 220 
 221 static void match_barrier(struct statement *stmt)
 222 {
 223         char *macro;
 224 
 225         macro = get_macro_name(stmt->pos);
 226         if (!macro)
 227                 return;
 228         if (strcmp(macro, "rmb") != 0 &&
 229             strcmp(macro, "smp_rmb") != 0 &&
 230             strcmp(macro, "barrier_nospec") != 0 &&
 231             strcmp(macro, "preempt_disable") != 0)
 232                 return;
 233 
 234         set_state(barrier_id, "barrier", NULL, &nospec);
 235         mark_user_data_as_nospec();
 236 }
 237 
 238 static void db_returns_barrier(struct expression *expr, int param, char *key, char *value)
 239 {
 240         mark_user_data_as_nospec();
 241 }
 242 
 243 static void select_return_stmt_cnt(struct expression *expr, int param, char *key, char *value)
 244 {
 245         int cnt;
 246 
 247         cnt = atoi(value);
 248         if (cnt > 400)
 249                 mark_user_data_as_nospec();
 250 }
 251 
 252 void check_nospec(int id)
 253 {
 254         my_id = id;
 255 
 256         add_hook(&nospec_assign, ASSIGNMENT_HOOK);
 257 
 258         select_caller_info_hook(set_param_nospec, NOSPEC);
 259         add_unmatched_state_hook(my_id, &unmatched_state);
 260 
 261         add_hook(&match_call_info, FUNCTION_CALL_HOOK);
 262         add_member_info_callback(my_id, struct_member_callback);
 263         add_split_return_callback(&returned_struct_members);
 264         select_return_states_hook(NOSPEC, &db_returns_nospec);
 265         select_return_states_hook(NOSPEC_WB, &db_returns_barrier);
 266         select_return_states_hook(STMT_CNT, &select_return_stmt_cnt);
 267 
 268         add_hook(&match_asm, ASM_HOOK);
 269         add_hook(&match_after_nospec_asm, STMT_HOOK_AFTER);
 270 }
 271 
 272 void check_nospec_barrier(int id)
 273 {
 274         barrier_id = id;
 275 
 276         add_hook(&match_barrier, ASM_HOOK);
 277 }