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 symbol *returned_sym;
 100         struct sm_state *sm;
 101         const char *param_name;
 102         struct range_list *rl;
 103         int param;
 104 
 105         returned_sym = expr_to_sym(expr);
 106 
 107         FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
 108                 param = get_param_num_from_sym(sm->sym);
 109                 if (param < 0) {
 110                         if (!returned_sym || returned_sym != sm->sym)
 111                                 continue;
 112                         param = -1;
 113                 }
 114 
 115                 param_name = get_param_name(sm);
 116                 if (!param_name)
 117                         continue;
 118                 if (param != -1 && strcmp(param_name, "$") == 0)
 119                         continue;
 120 
 121                 if (!get_user_rl_var_sym(sm->name, sm->sym, &rl))
 122                         continue;
 123 
 124                 sql_insert_return_states(return_id, return_ranges, NOSPEC, param, param_name, "");
 125         } END_FOR_EACH_SM(sm);
 126 
 127         if (is_nospec(expr) && get_user_rl(expr, &rl))
 128                 sql_insert_return_states(return_id, return_ranges, NOSPEC, -1, "$", "");
 129 
 130         if (get_state(barrier_id, "barrier", NULL) == &nospec)
 131                 sql_insert_return_states(return_id, return_ranges, NOSPEC_WB, -1, "", "");
 132 }
 133 
 134 static int is_return_statement(void)
 135 {
 136         if (__cur_stmt && __cur_stmt->type == STMT_RETURN)
 137                 return 1;
 138         return 0;
 139 }
 140 
 141 static void db_returns_nospec(struct expression *expr, int param, char *key, char *value)
 142 {
 143         struct expression *call;
 144         struct expression *arg;
 145         char *name;
 146         struct symbol *sym;
 147 
 148         call = expr;
 149         while (call->type == EXPR_ASSIGNMENT)
 150                 call = strip_expr(call->right);
 151         if (call->type != EXPR_CALL)
 152                 return;
 153 
 154         if (param == -1 && expr->type == EXPR_ASSIGNMENT) {
 155                 name = get_variable_from_key(expr->left, key, &sym);
 156         } else if (param == -1 && is_return_statement()) {
 157                 in_nospec_stmt = true;
 158                 return;
 159         } else {
 160                 arg = get_argument_from_call_expr(call->args, param);
 161                 if (!arg)
 162                         return;
 163                 name = get_variable_from_key(arg, key, &sym);
 164         }
 165         if (!name || !sym)
 166                 goto free;
 167 
 168         set_state(my_id, name, sym, &nospec);
 169 free:
 170         free_string(name);
 171 }
 172 
 173 static int is_nospec_asm(struct statement *stmt)
 174 {
 175         char *macro;
 176 
 177         if (!stmt || stmt->type != STMT_ASM)
 178                 return 0;
 179         macro = get_macro_name(stmt->asm_string->pos);
 180         if (!macro || strcmp(macro, "CALL_NOSPEC") != 0)
 181                 return 0;
 182         return 1;
 183 }
 184 
 185 static void match_asm(struct statement *stmt)
 186 {
 187         if (is_nospec_asm(stmt))
 188                 in_nospec_stmt = true;
 189 }
 190 
 191 static void match_after_nospec_asm(struct statement *stmt)
 192 {
 193         in_nospec_stmt = false;
 194 }
 195 
 196 static void mark_user_data_as_nospec(void)
 197 {
 198         struct stree *stree;
 199         struct symbol *type;
 200         struct sm_state *sm;
 201 
 202         stree = get_user_stree();
 203         FOR_EACH_SM(stree, sm) {
 204                 if (is_whole_rl(estate_rl(sm->state)))
 205                         continue;
 206                 type = estate_type(sm->state);
 207                 if (!type || type->type != SYM_BASETYPE)
 208                         continue;
 209                 if (!is_capped_var_sym(sm->name, sm->sym))
 210                         continue;
 211                 set_state(my_id, sm->name, sm->sym, &nospec);
 212         } END_FOR_EACH_SM(sm);
 213         free_stree(&stree);
 214 }
 215 
 216 static void match_barrier(struct statement *stmt)
 217 {
 218         char *macro;
 219 
 220         macro = get_macro_name(stmt->pos);
 221         if (!macro)
 222                 return;
 223         if (strcmp(macro, "rmb") != 0 &&
 224             strcmp(macro, "smp_rmb") != 0 &&
 225             strcmp(macro, "barrier_nospec") != 0)
 226                 return;
 227 
 228         set_state(barrier_id, "barrier", NULL, &nospec);
 229         mark_user_data_as_nospec();
 230 }
 231 
 232 static void db_returns_barrier(struct expression *expr, int param, char *key, char *value)
 233 {
 234         mark_user_data_as_nospec();
 235 }
 236 
 237 void check_nospec(int id)
 238 {
 239         my_id = id;
 240 
 241         add_hook(&nospec_assign, ASSIGNMENT_HOOK);
 242 
 243         select_caller_info_hook(set_param_nospec, NOSPEC);
 244         add_unmatched_state_hook(my_id, &unmatched_state);
 245 
 246         add_hook(&match_call_info, FUNCTION_CALL_HOOK);
 247         add_member_info_callback(my_id, struct_member_callback);
 248         add_split_return_callback(&returned_struct_members);
 249         select_return_states_hook(NOSPEC, &db_returns_nospec);
 250         select_return_states_hook(NOSPEC_WB, &db_returns_barrier);
 251 
 252         add_hook(&match_asm, ASM_HOOK);
 253         add_hook(&match_after_nospec_asm, STMT_HOOK_AFTER);
 254 }
 255 
 256 void check_nospec_barrier(int id)
 257 {
 258         barrier_id = id;
 259 
 260         add_hook(&match_barrier, ASM_HOOK);
 261 }