/* * Copyright (C) 2006 Dan Carpenter. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt * * Copyright 2019 Joyent, Inc. */ #include #include #include #include "smatch.h" #include "smatch_slist.h" #include "check_list.h" char *option_debug_check = (char *)""; char *option_project_str = (char *)"smatch_generic"; static char *option_db_file = (char *)"smatch_db.sqlite"; enum project_type option_project = PROJ_NONE; char *bin_dir; char *data_dir; int option_no_data = 0; int option_spammy = 0; int option_info = 0; int option_full_path = 0; int option_call_tree = 0; int option_no_db = 0; int option_enable = 0; int option_disable = 0; int option_file_output; int option_time; int option_mem; char *option_datadir_str; int option_fatal_checks; int option_succeed; int option_timeout = 60; FILE *sm_outfd; FILE *sql_outfd; FILE *caller_info_fd; int sm_nr_errors; int sm_nr_checks; bool __silence_warnings_for_stmt; const char *progname; typedef void (*reg_func) (int id); #define CK(_x) {.name = #_x, .func = &_x, .enabled = 0}, static struct reg_func_info { const char *name; reg_func func; int enabled; } reg_funcs[] = { {NULL, NULL}, #include "check_list.h" }; #undef CK int num_checks = ARRAY_SIZE(reg_funcs) - 1; const char *check_name(unsigned short id) { if (id >= ARRAY_SIZE(reg_funcs)) return "internal"; return reg_funcs[id].name; } int id_from_name(const char *name) { int i; for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) { if (!strcmp(name, reg_funcs[i].name)) return i; } return 0; } static void show_checks(void) { int i; for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) { if (!strncmp(reg_funcs[i].name, "check_", 6)) printf("%3d. %s\n", i, reg_funcs[i].name); } exit(0); } static void enable_disable_checks(char *s, bool enable) { char buf[128]; char *next; int i; do { next = strchr(s, ','); if (next) { *next = '\0'; next++; } if (*s == '\0') return; if (strncmp(s, "check_", 6) == 0) snprintf(buf, sizeof(buf), "%s", s); else snprintf(buf, sizeof(buf), "check_%s", s); for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) { if (strcmp(reg_funcs[i].name, buf) == 0) { reg_funcs[i].enabled = (enable == true) ? 1 : -1; break; } } if (i == ARRAY_SIZE(reg_funcs)) sm_fatal("'%s' not found", s); } while ((s = next)); } static void help(void) { printf("Usage: smatch [smatch arguments][sparse arguments] file.c\n"); printf("--project= or -p=: project specific tests\n"); printf("--succeed: don't exit with an error\n"); printf("--spammy: print superfluous crap.\n"); printf("--info: print info used to fill smatch_data/.\n"); printf("--debug: print lots of debug output.\n"); printf("--no-data: do not use the /smatch_data/ directory.\n"); printf("--data=: overwrite path to default smatch data directory.\n"); printf("--full-path: print the full pathname.\n"); printf("--debug-implied: print debug output about implications.\n"); printf("--assume-loops: assume loops always go through at least once.\n"); printf("--two-passes: use a two pass system for each function.\n"); printf("--file-output: instead of printing stdout, print to \"file.c.smatch_out\".\n"); printf("--fatal-checks: check output is treated as an error.\n"); printf("--help: print this helpful message.\n"); exit(1); } static int match_option(const char *arg, const char *option) { char *str; char *tmp; int ret = 0; str = malloc(strlen(option) + 3); snprintf(str, strlen(option) + 3, "--%s", option); tmp = str; while (*tmp) { if (*tmp == '_') *tmp = '-'; tmp++; } if (!strcmp(arg, str)) ret = 1; free(str); return ret; } #define OPTION(_x) do { \ if (match_option((*argvp)[i], #_x)) { \ option_##_x = 1; \ } \ } while (0) void parse_args(int *argcp, char ***argvp) { int i; for (i = 1 ; i < *argcp; i++) { if (!strcmp((*argvp)[i], "--help")) help(); if (!strcmp((*argvp)[i], "--show-checks")) show_checks(); if (!strncmp((*argvp)[i], "--project=", 10)) option_project_str = (*argvp)[i] + 10; if (!strncmp((*argvp)[i], "-p=", 3)) option_project_str = (*argvp)[i] + 3; if (!strncmp((*argvp)[i], "--db-file=", 10)) option_db_file = (*argvp)[i] + 10; if (!strncmp((*argvp)[i], "--data=", 7)) option_datadir_str = (*argvp)[i] + 7; if (!strncmp((*argvp)[i], "--debug=", 8)) option_debug_check = (*argvp)[i] + 8; if (strncmp((*argvp)[i], "--trace=", 8) == 0) trace_variable = (*argvp)[i] + 8; if (strncmp((*argvp)[i], "--enable=", 9) == 0) { enable_disable_checks((*argvp)[i] + 9, 1); option_enable = 1; } if (strncmp((*argvp)[i], "--disable=", 10) == 0) { enable_disable_checks((*argvp)[i] + 10, 0); option_enable = 1; option_disable = 1; } if (!strncmp((*argvp)[i], "--timeout=", 10)) { if (sscanf((*argvp)[i] + 10, "%d", &option_timeout) != 1) sm_fatal("invalid option %s", (*argvp)[i]); } OPTION(fatal_checks); OPTION(spammy); OPTION(info); OPTION(debug); OPTION(assume_loops); OPTION(no_data); OPTION(two_passes); OPTION(full_path); OPTION(call_tree); OPTION(file_output); OPTION(time); OPTION(mem); OPTION(no_db); OPTION(succeed); } if (strcmp(option_project_str, "smatch_generic") != 0) option_project = PROJ_UNKNOWN; if (strcmp(option_project_str, "kernel") == 0) option_project = PROJ_KERNEL; else if (strcmp(option_project_str, "wine") == 0) option_project = PROJ_WINE; else if (strcmp(option_project_str, "illumos_kernel") == 0) option_project = PROJ_ILLUMOS_KERNEL; else if (strcmp(option_project_str, "illumos_user") == 0) option_project = PROJ_ILLUMOS_USER; } static char *read_bin_filename(void) { char filename[PATH_MAX] = {}; char proc[PATH_MAX]; pid_t pid = getpid(); sprintf(proc, "/proc/%d/exe", pid); if (readlink(proc, filename, PATH_MAX) < 0) return NULL; return alloc_string(filename); } static char *get_bin_dir(char *arg0) { char *orig; orig = read_bin_filename(); if (!orig) orig = alloc_string(arg0); return dirname(orig); } static char *get_data_dir(char *arg0) { char buf[256]; char *dir; if (option_no_data) return NULL; if (option_datadir_str) { if (access(option_datadir_str, R_OK)) sm_warning("%s is not accessible -- ignored.", option_datadir_str); else return alloc_string(option_datadir_str); } strncpy(buf, "smatch_data/", sizeof(buf)); dir = alloc_string(buf); if (!access(dir, R_OK)) return dir; strncpy(buf, bin_dir, 254); buf[255] = '\0'; strncat(buf, "/smatch_data/", 254 - strlen(buf)); dir = alloc_string(buf); if (!access(dir, R_OK)) return dir; free_string(dir); snprintf(buf, 254, "%s/smatch_data/", SMATCHDATADIR); dir = alloc_string(buf); if (!access(dir, R_OK)) return dir; sm_warning("%s is not accessible.", dir); sm_warning("Use --no-data or --data to suppress this message."); return NULL; } int main(int argc, char **argv) { struct string_list *filelist = NULL; int i; reg_func func; sm_outfd = stdout; sql_outfd = stdout; caller_info_fd = stdout; progname = argv[0]; parse_args(&argc, &argv); if (argc < 2) help(); /* this gets set back to zero when we parse the first function */ final_pass = 1; bin_dir = get_bin_dir(argv[0]); data_dir = get_data_dir(argv[0]); allocate_hook_memory(); allocate_dynamic_states_array(num_checks); allocate_tracker_array(num_checks); create_function_hook_hash(); open_smatch_db(option_db_file); sparse_initialize(argc, argv, &filelist); alloc_valid_ptr_rl(); for (i = 1; i < ARRAY_SIZE(reg_funcs); i++) { func = reg_funcs[i].func; /* The script IDs start at 1. 0 is used for internal stuff. */ if (!option_enable || reg_funcs[i].enabled == 1 || (option_disable && reg_funcs[i].enabled != -1) || strncmp(reg_funcs[i].name, "register_", 9) == 0) func(i); } smatch(filelist); free_string(data_dir); if (option_succeed) return 0; if (sm_nr_errors > 0) return 1; if (sm_nr_checks > 0 && option_fatal_checks) return 1; return 0; }