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         macro = get_macro_name(stmt->asm_string->pos);
 183         if (!macro || strcmp(macro, "CALL_NOSPEC") != 0)
 184                 return 0;
 185         return 1;
 186 }
 187 
 188 static void match_asm(struct statement *stmt)
 189 {
 190         if (is_nospec_asm(stmt))
 191                 in_nospec_stmt = true;
 192 }
 193 
 194 static void match_after_nospec_asm(struct statement *stmt)
 195 {
 196         in_nospec_stmt = false;
 197 }
 198 
 199 static void mark_user_data_as_nospec(void)
 200 {
 201         struct stree *stree;
 202         struct symbol *type;
 203         struct sm_state *sm;
 204 
 205         stree = get_user_stree();
 206         FOR_EACH_SM(stree, sm) {
 207                 if (is_whole_rl(estate_rl(sm->state)))
 208                         continue;
 209                 type = estate_type(sm->state);
 210                 if (!type || type->type != SYM_BASETYPE)
 211                         continue;
 212                 if (!is_capped_var_sym(sm->name, sm->sym))
 213                         continue;
 214                 set_state(my_id, sm->name, sm->sym, &nospec);
 215         } END_FOR_EACH_SM(sm);
 216         free_stree(&stree);
 217 }
 218 
 219 static void match_barrier(struct statement *stmt)
 220 {
 221         char *macro;
 222 
 223         macro = get_macro_name(stmt->pos);
 224         if (!macro)
 225                 return;
 226         if (strcmp(macro, "rmb") != 0 &&
 227             strcmp(macro, "smp_rmb") != 0 &&
 228             strcmp(macro, "barrier_nospec") != 0 &&
 229             strcmp(macro, "preempt_disable") != 0)
 230                 return;
 231 
 232         set_state(barrier_id, "barrier", NULL, &nospec);
 233         mark_user_data_as_nospec();
 234 }
 235 
 236 static void db_returns_barrier(struct expression *expr, int param, char *key, char *value)
 237 {
 238         mark_user_data_as_nospec();
 239 }
 240 
 241 static void select_return_stmt_cnt(struct expression *expr, int param, char *key, char *value)
 242 {
 243         int cnt;
 244 
 245         cnt = atoi(value);
 246         if (cnt > 400)
 247                 mark_user_data_as_nospec();
 248 }
 249 
 250 void check_nospec(int id)
 251 {
 252         my_id = id;
 253 
 254         add_hook(&nospec_assign, ASSIGNMENT_HOOK);
 255 
 256         select_caller_info_hook(set_param_nospec, NOSPEC);
 257         add_unmatched_state_hook(my_id, &unmatched_state);
 258 
 259         add_hook(&match_call_info, FUNCTION_CALL_HOOK);
 260         add_member_info_callback(my_id, struct_member_callback);
 261         add_split_return_callback(&returned_struct_members);
 262         select_return_states_hook(NOSPEC, &db_returns_nospec);
 263         select_return_states_hook(NOSPEC_WB, &db_returns_barrier);
 264         select_return_states_hook(STMT_CNT, &select_return_stmt_cnt);
 265 
 266         add_hook(&match_asm, ASM_HOOK);
 267         add_hook(&match_after_nospec_asm, STMT_HOOK_AFTER);
 268 }
 269 
 270 void check_nospec_barrier(int id)
 271 {
 272         barrier_id = id;
 273 
 274         add_hook(&match_barrier, ASM_HOOK);
 275 }