1 /*
   2  * Copyright (C) 2006 Dan Carpenter.
   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  * Copyright 2019 Joyent, Inc.
  18  */
  19 
  20 #include <stdio.h>
  21 #include <unistd.h>
  22 #include <libgen.h>
  23 #include "smatch.h"
  24 #include "smatch_slist.h"
  25 #include "check_list.h"
  26 
  27 char *option_debug_check = (char *)"";
  28 char *option_project_str = (char *)"smatch_generic";
  29 static char *option_db_file = (char *)"smatch_db.sqlite";
  30 enum project_type option_project = PROJ_NONE;
  31 char *bin_dir;
  32 char *data_dir;
  33 int option_no_data = 0;
  34 int option_spammy = 0;
  35 int option_info = 0;
  36 int option_full_path = 0;
  37 int option_call_tree = 0;
  38 int option_no_db = 0;
  39 int option_enable = 0;
  40 int option_disable = 0;
  41 int option_file_output;
  42 int option_time;
  43 int option_mem;
  44 char *option_datadir_str;
  45 int option_fatal_checks;
  46 int option_succeed;
  47 int option_timeout = 60;
  48 
  49 FILE *sm_outfd;
  50 FILE *sql_outfd;
  51 FILE *caller_info_fd;
  52 
  53 int sm_nr_errors;
  54 int sm_nr_checks;
  55 
  56 bool __silence_warnings_for_stmt;
  57 
  58 const char *progname;
  59 
  60 typedef void (*reg_func) (int id);
  61 #define CK(_x) {.name = #_x, .func = &_x, .enabled = 0},
  62 static struct reg_func_info {
  63         const char *name;
  64         reg_func func;
  65         int enabled;
  66 } reg_funcs[] = {
  67         {NULL, NULL},
  68 #include "check_list.h"
  69 };
  70 #undef CK
  71 int num_checks = ARRAY_SIZE(reg_funcs) - 1;
  72 
  73 const char *check_name(unsigned short id)
  74 {
  75         if (id >= ARRAY_SIZE(reg_funcs))
  76                 return "internal";
  77 
  78         return reg_funcs[id].name;
  79 }
  80 
  81 int id_from_name(const char *name)
  82 {
  83         int i;
  84 
  85         for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) {
  86                 if (!strcmp(name, reg_funcs[i].name))
  87                         return i;
  88         }
  89         return 0;
  90 }
  91 
  92 static void show_checks(void)
  93 {
  94         int i;
  95 
  96         for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) {
  97                 if (!strncmp(reg_funcs[i].name, "check_", 6))
  98                         printf("%3d. %s\n", i, reg_funcs[i].name);
  99         }
 100         exit(0);
 101 }
 102 
 103 static void enable_disable_checks(char *s, bool enable)
 104 {
 105         char buf[128];
 106         char *next;
 107         int i;
 108 
 109         do {
 110                 next = strchr(s, ',');
 111                 if (next) {
 112                         *next = '\0';
 113                         next++;
 114                 }
 115                 if (*s == '\0')
 116                         return;
 117                 if (strncmp(s, "check_", 6) == 0)
 118                         snprintf(buf, sizeof(buf), "%s", s);
 119                 else
 120                         snprintf(buf, sizeof(buf), "check_%s", s);
 121 
 122 
 123                 for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) {
 124                         if (strcmp(reg_funcs[i].name, buf) == 0) {
 125                                 reg_funcs[i].enabled = (enable == true) ? 1 : -1;
 126                                 break;
 127                         }
 128                 }
 129 
 130                 if (i == ARRAY_SIZE(reg_funcs))
 131                         sm_fatal("'%s' not found", s);
 132 
 133         } while ((s = next));
 134 }
 135 
 136 static void help(void)
 137 {
 138         printf("Usage:  smatch [smatch arguments][sparse arguments] file.c\n");
 139         printf("--project=<name> or -p=<name>: project specific tests\n");
 140         printf("--succeed: don't exit with an error\n");
 141         printf("--spammy:  print superfluous crap.\n");
 142         printf("--info:  print info used to fill smatch_data/.\n");
 143         printf("--debug:  print lots of debug output.\n");
 144         printf("--no-data:  do not use the /smatch_data/ directory.\n");
 145         printf("--data=<dir>: overwrite path to default smatch data directory.\n");
 146         printf("--full-path:  print the full pathname.\n");
 147         printf("--debug-implied:  print debug output about implications.\n");
 148         printf("--assume-loops:  assume loops always go through at least once.\n");
 149         printf("--two-passes:  use a two pass system for each function.\n");
 150         printf("--file-output:  instead of printing stdout, print to \"file.c.smatch_out\".\n");
 151         printf("--fatal-checks: check output is treated as an error.\n");
 152         printf("--help:  print this helpful message.\n");
 153         exit(1);
 154 }
 155 
 156 static int match_option(const char *arg, const char *option)
 157 {
 158         char *str;
 159         char *tmp;
 160         int ret = 0;
 161 
 162         str = malloc(strlen(option) + 3);
 163         snprintf(str, strlen(option) + 3, "--%s", option);
 164         tmp = str;
 165         while (*tmp) {
 166                 if (*tmp == '_')
 167                         *tmp = '-';
 168                 tmp++;
 169         }
 170         if (!strcmp(arg, str))
 171                 ret = 1;
 172         free(str);
 173         return ret;
 174 }
 175 
 176 #define OPTION(_x) do {                                 \
 177         if (match_option((*argvp)[i], #_x)) {           \
 178                 option_##_x = 1;                        \
 179         }                                               \
 180 } while (0)
 181 
 182 void parse_args(int *argcp, char ***argvp)
 183 {
 184         int i;
 185 
 186         for (i = 1 ; i < *argcp; i++) {
 187                 if (!strcmp((*argvp)[i], "--help"))
 188                         help();
 189 
 190                 if (!strcmp((*argvp)[i], "--show-checks"))
 191                         show_checks();
 192 
 193                 if (!strncmp((*argvp)[i], "--project=", 10))
 194                         option_project_str = (*argvp)[i] + 10;
 195 
 196                 if (!strncmp((*argvp)[i], "-p=", 3))
 197                         option_project_str = (*argvp)[i] + 3;
 198 
 199                 if (!strncmp((*argvp)[i], "--db-file=", 10))
 200                         option_db_file = (*argvp)[i] + 10;
 201 
 202                 if (!strncmp((*argvp)[i], "--data=", 7))
 203                         option_datadir_str = (*argvp)[i] + 7;
 204 
 205                 if (!strncmp((*argvp)[i], "--debug=", 8))
 206                         option_debug_check = (*argvp)[i] + 8;
 207 
 208                 if (strncmp((*argvp)[i], "--trace=", 8) == 0)
 209                         trace_variable = (*argvp)[i] + 8;
 210 
 211                 if (strncmp((*argvp)[i], "--enable=", 9) == 0) {
 212                         enable_disable_checks((*argvp)[i] + 9, 1);
 213                         option_enable = 1;
 214                 }
 215 
 216                 if (strncmp((*argvp)[i], "--disable=", 10) == 0) {
 217                         enable_disable_checks((*argvp)[i] + 10, 0);
 218                         option_enable = 1;
 219                         option_disable = 1;
 220                 }
 221 
 222                 if (!strncmp((*argvp)[i], "--timeout=", 10)) {
 223                         if (sscanf((*argvp)[i] + 10, "%d",
 224                             &option_timeout) != 1)
 225                                 sm_fatal("invalid option %s", (*argvp)[i]);
 226                 }
 227 
 228                 OPTION(fatal_checks);
 229                 OPTION(spammy);
 230                 OPTION(info);
 231                 OPTION(debug);
 232                 OPTION(assume_loops);
 233                 OPTION(no_data);
 234                 OPTION(two_passes);
 235                 OPTION(full_path);
 236                 OPTION(call_tree);
 237                 OPTION(file_output);
 238                 OPTION(time);
 239                 OPTION(mem);
 240                 OPTION(no_db);
 241                 OPTION(succeed);
 242         }
 243 
 244         if (strcmp(option_project_str, "smatch_generic") != 0)
 245                 option_project = PROJ_UNKNOWN;
 246 
 247         if (strcmp(option_project_str, "kernel") == 0)
 248                 option_project = PROJ_KERNEL;
 249         else if (strcmp(option_project_str, "wine") == 0)
 250                 option_project = PROJ_WINE;
 251         else if (strcmp(option_project_str, "illumos_kernel") == 0)
 252                 option_project = PROJ_ILLUMOS_KERNEL;
 253         else if (strcmp(option_project_str, "illumos_user") == 0)
 254                 option_project = PROJ_ILLUMOS_USER;
 255 }
 256 
 257 static char *read_bin_filename(void)
 258 {
 259         char filename[PATH_MAX] = {};
 260         char proc[PATH_MAX];
 261 
 262         pid_t pid = getpid();
 263         sprintf(proc, "/proc/%d/exe", pid);
 264         if (readlink(proc, filename, PATH_MAX) < 0)
 265                 return NULL;
 266         return alloc_string(filename);
 267 }
 268 
 269 static char *get_bin_dir(char *arg0)
 270 {
 271         char *orig;
 272 
 273         orig = read_bin_filename();
 274         if (!orig)
 275                 orig = alloc_string(arg0);
 276         return dirname(orig);
 277 }
 278 
 279 static char *get_data_dir(char *arg0)
 280 {
 281         char buf[256];
 282         char *dir;
 283 
 284         if (option_no_data)
 285                 return NULL;
 286 
 287         if (option_datadir_str) {
 288                 if (access(option_datadir_str, R_OK))
 289                         sm_warning("%s is not accessible -- ignored.",
 290                                         option_datadir_str);
 291                 else
 292                         return alloc_string(option_datadir_str);
 293         }
 294 
 295         strncpy(buf, "smatch_data/", sizeof(buf));
 296         dir = alloc_string(buf);
 297         if (!access(dir, R_OK))
 298                 return dir;
 299 
 300         strncpy(buf, bin_dir, 254);
 301 
 302         buf[255] = '\0';
 303         strncat(buf, "/smatch_data/", 254 - strlen(buf));
 304         dir = alloc_string(buf);
 305         if (!access(dir, R_OK))
 306                 return dir;
 307         free_string(dir);
 308         snprintf(buf, 254, "%s/smatch_data/", SMATCHDATADIR);
 309         dir = alloc_string(buf);
 310         if (!access(dir, R_OK))
 311                 return dir;
 312 
 313         sm_warning("%s is not accessible.", dir);
 314         sm_warning("Use --no-data or --data to suppress this message.");
 315         return NULL;
 316 }
 317 
 318 int main(int argc, char **argv)
 319 {
 320         struct string_list *filelist = NULL;
 321         int i;
 322         reg_func func;
 323 
 324         sm_outfd = stdout;
 325         sql_outfd = stdout;
 326         caller_info_fd = stdout;
 327 
 328         progname = argv[0];
 329 
 330         parse_args(&argc, &argv);
 331 
 332         if (argc < 2)
 333                 help();
 334 
 335         /* this gets set back to zero when we parse the first function */
 336         final_pass = 1;
 337 
 338         bin_dir = get_bin_dir(argv[0]);
 339         data_dir = get_data_dir(argv[0]);
 340 
 341         allocate_hook_memory();
 342         allocate_dynamic_states_array(num_checks);
 343         allocate_tracker_array(num_checks);
 344         create_function_hook_hash();
 345         open_smatch_db(option_db_file);
 346         sparse_initialize(argc, argv, &filelist);
 347         alloc_valid_ptr_rl();
 348 
 349         for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) {
 350                 func = reg_funcs[i].func;
 351                 /* The script IDs start at 1.
 352                    0 is used for internal stuff. */
 353                 if (!option_enable || reg_funcs[i].enabled == 1 ||
 354                     (option_disable && reg_funcs[i].enabled != -1) ||
 355                     strncmp(reg_funcs[i].name, "register_", 9) == 0)
 356                         func(i);
 357         }
 358 
 359         smatch(filelist);
 360         free_string(data_dir);
 361 
 362         if (option_succeed)
 363                 return 0;
 364         if (sm_nr_errors > 0)
 365                 return 1;
 366         if (sm_nr_checks > 0 && option_fatal_checks)
 367                 return 1;
 368         return 0;
 369 }