/* This file is part of GNU cflow Copyright (C) 1997, 2005, 2007, 2009-2011, 2014-2016 Sergey Poznyakoff GNU cflow 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 3 of the License, or (at your option) any later version. GNU cflow 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 . */ #include #include #include #include #include #include #include const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">"; static char doc[] = N_("generate a program flowgraph") "\v" N_("* The effect of each option marked with an asterisk is reversed if the option's long name is prefixed with `no-'. For example, --no-cpp cancels --cpp."); const char *program_authors[] = { "Sergey Poznyakoff", NULL }; enum option_code { OPT_DEFINES = 256, OPT_LEVEL_INDENT, OPT_DEBUG, OPT_PREPROCESS, OPT_NO_PREPROCESS, OPT_EMACS, OPT_NO_USE_INDENTATION, OPT_NO_ANSI, OPT_NO_TREE, OPT_NO_BRIEF, OPT_NO_EMACS, OPT_NO_VERBOSE, OPT_NO_NUMBER, OPT_NO_PRINT_LEVEL, OPT_NO_REVERSE, OPT_OMIT_ARGUMENTS, OPT_NO_OMIT_ARGUMENTS, OPT_OMIT_SYMBOL_NAMES, OPT_NO_OMIT_SYMBOL_NAMES }; static struct argp_option options[] = { #define GROUP_ID 0 { NULL, 0, NULL, 0, N_("General options:"), GROUP_ID }, { "depth", 'd', N_("NUMBER"), 0, N_("Set the depth at which the flowgraph is cut off"), GROUP_ID+1 }, { "include", 'i', N_("CLASSES"), 0, N_("Include specified classes of symbols (see below). Prepend CLASSES with ^ or - to exclude them from the output"), GROUP_ID+1 }, { "format", 'f', N_("NAME"), 0, N_("Use given output format NAME. Valid names are `gnu' (default) and `posix'"), GROUP_ID+1 }, { "reverse", 'r', NULL, 0, N_("* Print reverse call tree"), GROUP_ID+1 }, { "xref", 'x', NULL, 0, N_("Produce cross-reference listing only"), GROUP_ID+1 }, { "print", 'P', N_("OPT"), OPTION_HIDDEN, N_("Set printing option to OPT. Valid OPT values are: xref (or cross-ref), tree. Any unambiguous abbreviation of the above is also accepted"), GROUP_ID+1 }, { "output", 'o', N_("FILE"), 0, N_("Set output file name (default -, meaning stdout)"), GROUP_ID+1 }, { NULL, 0, NULL, 0, N_("Symbols classes for --include argument"), GROUP_ID+2 }, {" x", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("all data symbols, both external and static"), GROUP_ID+3 }, {" _", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("symbols whose names begin with an underscore"), GROUP_ID+3 }, {" s", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("static symbols"), GROUP_ID+3 }, {" t", 0, NULL, OPTION_DOC|OPTION_NO_TRANS, N_("typedefs (for cross-references only)"), GROUP_ID+3 }, #undef GROUP_ID #define GROUP_ID 10 { NULL, 0, NULL, 0, N_("Parser control:"), GROUP_ID }, { "use-indentation", 'S', NULL, 0, N_("* Rely on indentation"), GROUP_ID+1 }, { "no-use-indentation", OPT_NO_USE_INDENTATION, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "ansi", 'a', NULL, 0, N_("* Accept only sources in ANSI C"), GROUP_ID+1 }, { "no-ansi", OPT_NO_ANSI, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "pushdown", 'p', N_("NUMBER"), 0, N_("Set initial token stack size to NUMBER"), GROUP_ID+1 }, { "symbol", 's', N_("SYMBOL:[=]TYPE"), 0, N_("Register SYMBOL with given TYPE, or define an alias (if := is used). Valid types are: keyword (or kw), modifier, qualifier, identifier, type, wrapper. Any unambiguous abbreviation of the above is also accepted"), GROUP_ID+1 }, { "main", 'm', N_("NAME"), 0, N_("Assume main function to be called NAME"), GROUP_ID+1 }, { "define", 'D', N_("NAME[=DEFN]"), 0, N_("Predefine NAME as a macro"), GROUP_ID+1 }, { "undefine", 'U', N_("NAME"), 0, N_("Cancel any previous definition of NAME"), GROUP_ID+1 }, { "include-dir", 'I', N_("DIR"), 0, N_("Add the directory DIR to the list of directories to be searched for header files."), GROUP_ID+1 }, { "preprocess", OPT_PREPROCESS, N_("COMMAND"), OPTION_ARG_OPTIONAL, N_("* Run the specified preprocessor command"), GROUP_ID+1 }, { "cpp", 0, NULL, OPTION_ALIAS, NULL, GROUP_ID+1 }, { "no-preprocess", OPT_NO_PREPROCESS, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "no-cpp", 0, NULL, OPTION_ALIAS|OPTION_HIDDEN, NULL, GROUP_ID+1 }, #undef GROUP_ID #define GROUP_ID 20 { NULL, 0, NULL, 0, N_("Output control:"), GROUP_ID }, { "number", 'n', NULL, 0, N_("* Print line numbers"), GROUP_ID+1 }, { "no-number", OPT_NO_NUMBER, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "print-level", 'l', NULL, 0, N_("* Print nesting level along with the call tree"), GROUP_ID+1 }, { "no-print-level", OPT_NO_PRINT_LEVEL, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "level-indent", OPT_LEVEL_INDENT, "ELEMENT", 0, N_("Control graph appearance"), GROUP_ID+1 }, { "tree", 'T', NULL, 0, N_("* Draw ASCII art tree"), GROUP_ID+1 }, { "no-tree", OPT_NO_TREE, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "brief", 'b', NULL, 0, N_("* Brief output"), GROUP_ID+1 }, { "no-brief", OPT_NO_BRIEF, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "emacs", OPT_EMACS, NULL, 0, N_("* Additionally format output for use with GNU Emacs"), GROUP_ID+1 }, { "no-emacs", OPT_NO_EMACS, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "omit-arguments", OPT_OMIT_ARGUMENTS, NULL, 0, N_("* Do not print argument lists in function declarations"), GROUP_ID+1 }, { "no-ignore-arguments", OPT_NO_OMIT_ARGUMENTS, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "omit-symbol-names", OPT_OMIT_SYMBOL_NAMES, NULL, 0, N_("* Do not print symbol names in declaration strings"), GROUP_ID+1 }, { "no-omit-symbol-names", OPT_NO_OMIT_SYMBOL_NAMES, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, #undef GROUP_ID #define GROUP_ID 30 { NULL, 0, NULL, 0, N_("Informational options:"), GROUP_ID }, { "verbose", 'v', NULL, 0, N_("* Verbose error diagnostics"), GROUP_ID+1 }, { "no-verbose", OPT_NO_VERBOSE, NULL, OPTION_HIDDEN, "", GROUP_ID+1 }, { "debug", OPT_DEBUG, "NUMBER", OPTION_ARG_OPTIONAL, N_("Set debugging level"), GROUP_ID+1 }, #undef GROUP_ID { 0, } }; /* Structure representing various arguments of command line options */ struct option_type { char *str; /* optarg value */ int min_match; /* minimal number of characters to match */ int type; /* data associated with the arg */ }; int debug; /* debug level */ char *outname = "-"; /* default output file name */ int print_option = 0; /* what to print. */ int verbose; /* be verbose on output */ int use_indentation; /* Rely on indentation, * i.e. suppose the function body * is necessarily surrounded by the curly braces * in the first column */ int record_defines; /* Record macro definitions */ int strict_ansi; /* Assume sources to be written in ANSI C */ int print_line_numbers; /* Print line numbers */ int print_levels; /* Print level number near every branch */ int print_as_tree; /* Print as tree */ int brief_listing; /* Produce short listing */ int reverse_tree; /* Generate reverse tree */ int max_depth; /* The depth at which the flowgraph is cut off */ int emacs_option; /* Format and check for use with Emacs cflow-mode */ int omit_arguments_option; /* Omit arguments from function declaration string */ int omit_symbol_names_option; /* Omit symbol name from symbol declaration string */ #define SM_FUNCTIONS 0x0001 #define SM_DATA 0x0002 #define SM_STATIC 0x0004 #define SM_UNDERSCORE 0x0008 #define SM_TYPEDEF 0x0010 #define SM_UNDEFINED 0x0020 #define CHAR_TO_SM(c) ((c)=='x' ? SM_DATA : \ (c)=='_' ? SM_UNDERSCORE : \ (c)=='s' ? SM_STATIC : \ (c)=='t' ? SM_TYPEDEF : \ (c)=='u' ? SM_UNDEFINED : 0) #define SYMBOL_INCLUDE(c) (symbol_map |= CHAR_TO_SM(c)) #define SYMBOL_EXCLUDE(c) (symbol_map &= ~CHAR_TO_SM(c)) int symbol_map; /* A bitmap of symbols included in the graph. */ char *level_indent[] = { NULL, NULL }; char *level_end[] = { "", "" }; char *level_begin = ""; int preprocess_option = 0; /* Do they want to preprocess sources? */ char *start_name = "main"; /* Name of start symbol */ struct linked_list *arglist; /* List of command line arguments */ /* Given the option_type array and (possibly abbreviated) option argument * find the type corresponding to that argument. * Return 0 if the argument does not match any one of OPTYPE entries */ static int find_option_type(struct option_type *optype, const char *str, int len) { if (len == 0) len = strlen(str); for ( ; optype->str; optype++) { if (len >= optype->min_match && memcmp(str, optype->str, len) == 0) { return optype->type; } } return 0; } /* Args for --symbol option */ static struct option_type symbol_optype[] = { { "keyword", 2, WORD }, { "kw", 2, WORD }, { "modifier", 1, MODIFIER }, { "identifier", 1, IDENTIFIER }, { "type", 1, TYPE }, { "wrapper", 1, PARM_WRAPPER }, { "qualifier", 1, QUALIFIER }, { 0 }, }; /* Parse the string STR and store the symbol in the temporary symbol table. * STR is the string of form: NAME:TYPE * NAME means symbol name, TYPE means symbol type (possibly abbreviated) */ static void symbol_override(const char *str) { const char *ptr; char *name; Symbol *sp; ptr = strchr(str, ':'); if (!ptr) error(EX_USAGE, 0, _("%s: no symbol type supplied"), str); else { name = strndup(str, ptr - str); if (ptr[1] == '=') { Symbol *alias = lookup(ptr+2); if (!alias) { alias = install(xstrdup(ptr+2), INSTALL_OVERWRITE); alias->type = SymToken; alias->token_type = 0; alias->source = NULL; alias->def_line = -1; alias->ref_line = NULL; } sp = install(name, INSTALL_OVERWRITE); sp->type = SymToken; sp->alias = alias; sp->flag = symbol_alias; } else { int type = find_option_type(symbol_optype, ptr+1, 0); if (type == 0) error(EX_USAGE, 0, _("unknown symbol type: %s"), ptr+1); sp = install(name, INSTALL_OVERWRITE); sp->type = SymToken; sp->token_type = type; } sp->source = NULL; sp->def_line = -1; sp->ref_line = NULL; } } /* Args for --print option */ static struct option_type print_optype[] = { { "xref", 1, PRINT_XREF }, { "cross-ref", 1, PRINT_XREF }, { "tree", 1, PRINT_TREE }, { 0 }, }; static void set_print_option(char *str) { int opt; opt = find_option_type(print_optype, str, 0); if (opt == 0) { error(EX_USAGE, 0, _("unknown print option: %s"), str); return; } print_option |= opt; } /* Convert first COUNT bytes of the string pointed to by STR_PTR * to integer using BASE. Move STR_PTR to the point where the * conversion stopped. * Return the number obtained. */ static int number(const char **str_ptr, int base, int count) { int c, n; unsigned i; const char *str = *str_ptr; for (n = 0; *str && count; count--) { c = *str++; if (isdigit(c)) i = c - '0'; else i = toupper(c) - 'A' + 10; if (i > base) { break; } n = n * base + i; } *str_ptr = str - 1; return n; } /* Processing for --level option * The option syntax is * --level NUMBER * or * --level KEYWORD=STR * where * KEYWORD is one of "begin", "0", ", "1", "end0", "end1", * or an abbreviation thereof, * STR is the value to be assigned to the parameter. * * STR can contain usual C escape sequences plus \e meaning '\033'. * Apart from this any character followed by xN suffix (where N is * a decimal number) is expanded to the sequence of N such characters. * 'x' looses its special meaning at the start of the string. */ #define MAXLEVELINDENT 216 #define LEVEL_BEGIN 1 #define LEVEL_INDENT0 2 #define LEVEL_INDENT1 3 #define LEVEL_END0 4 #define LEVEL_END1 5 static struct option_type level_indent_optype[] = { { "begin", 1, LEVEL_BEGIN }, { "start", 1, LEVEL_BEGIN }, { "0", 1, LEVEL_INDENT0 }, { "1", 1, LEVEL_INDENT1 }, { "end0", 4, LEVEL_END0 }, { "end1", 4, LEVEL_END1 }, }; static void parse_level_string(const char *str, char **return_ptr) { static char text[MAXLEVELINDENT]; char *p; int i, c, num; p = text; memset(text, ' ', sizeof(text)); text[sizeof(text)-1] = 0; while (*str) { switch (*str) { case '\\': switch (*++str) { case 'a': *p++ = '\a'; break; case 'b': *p++ = '\b'; break; case 'e': *p++ = '\033'; break; case 'f': *p++ = '\f'; break; case 'n': *p++ = '\n'; break; case 'r': *p++ = '\r'; break; case 't': *p++ = '\t'; break; case 'x': case 'X': ++str; *p++ = number(&str,16,2); break; case '0': ++str; *p++ = number(&str,8,3); break; default: *p++ = *str; } ++str; break; case 'x': if (p == text) { goto copy; } num = strtol(str+1, (char**)&str, 10); c = p[-1]; for (i = 1; i < num; i++) { *p++ = c; if (*p == 0) error(EX_USAGE, 0, _("level indent string is too long")); } break; default: copy: *p++ = *str++; if (*p == 0) error(EX_USAGE, 0, _("level indent string is too long")); } } *p = 0; *return_ptr = strdup(text); } static void set_level_indent(const char *str) { long n; const char *p; char *q; n = strtol(str, &q, 0); if (*q == 0 && n > 0) { char *s = xmalloc(n+1); memset(s, ' ', n-1); s[n-1] = 0; level_indent[0] = level_indent[1] = s; return; } p = str; while (*p != '=') { if (*p == 0) error(EX_USAGE, 0, _("level-indent syntax")); p++; } ++p; switch (find_option_type(level_indent_optype, str, p - str - 1)) { case LEVEL_BEGIN: parse_level_string(p, &level_begin); break; case LEVEL_INDENT0: parse_level_string(p, &level_indent[0]); break; case LEVEL_INDENT1: parse_level_string(p, &level_indent[1]); break; case LEVEL_END0: parse_level_string(p, &level_end[0]); break; case LEVEL_END1: parse_level_string(p, &level_end[1]); break; default: error(EX_USAGE, 0, _("unknown level indent option: %s"), str); } } static void add_name(const char *name) { linked_list_append(&arglist, (void*) name); } static void add_preproc_option(int key, const char *arg) { char *opt = xmalloc(3 + strlen(arg)); sprintf(opt, "-%c%s", key, arg); add_name(opt); preprocess_option = 1; } static error_t parse_opt (int key, char *arg, struct argp_state *state) { int num; switch (key) { case 'a': strict_ansi = 1; break; case OPT_NO_ANSI: strict_ansi = 0; break; case OPT_DEBUG: debug = arg ? atoi(arg) : 1; break; case 'P': set_print_option(arg); break; case 'S': use_indentation = 1; break; case OPT_NO_USE_INDENTATION: use_indentation = 0; break; case 'T': print_as_tree = 1; set_level_indent("0= "); /* two spaces */ set_level_indent("1=| "); set_level_indent("end0=+-"); set_level_indent("end1=\\\\-"); break; case OPT_NO_TREE: print_as_tree = 0; level_indent[0] = level_indent[1] = NULL; level_end[0] = level_end[0] = NULL; break; case 'b': brief_listing = 1; break; case OPT_NO_BRIEF: brief_listing = 0; break; case 'd': max_depth = atoi(arg); if (max_depth < 0) max_depth = 0; break; case OPT_DEFINES: /* FIXME: Not used. */ record_defines = 1; break; case OPT_EMACS: emacs_option = 1; break; case OPT_NO_EMACS: emacs_option = 0; break; case 'f': if (select_output_driver(arg)) error(EX_USAGE, 0, _("%s: No such output driver"), optarg); output_init(); break; case OPT_LEVEL_INDENT: set_level_indent(arg); break; case 'i': num = 1; for (; *arg; arg++) switch (*arg) { case '-': case '^': num = 0; break; case '+': num = 1; break; case 'x': case '_': case 's': case 't': case 'u': if (num) SYMBOL_INCLUDE(*arg); else SYMBOL_EXCLUDE(*arg); break; default: error(EX_USAGE, 0, _("Unknown symbol class: %c"), *arg); } break; case OPT_OMIT_ARGUMENTS: omit_arguments_option = 1; break; case OPT_NO_OMIT_ARGUMENTS: omit_arguments_option = 0; break; case OPT_OMIT_SYMBOL_NAMES: omit_symbol_names_option = 1; break; case OPT_NO_OMIT_SYMBOL_NAMES: omit_symbol_names_option = 0; break; case 'l': print_levels = 1; break; case OPT_NO_PRINT_LEVEL: print_levels = 0; break; case 'm': start_name = strdup(arg); break; case 'n': print_line_numbers = 1; break; case OPT_NO_NUMBER: print_line_numbers = 0; break; case 'o': outname = strdup(arg); break; case 'p': num = atoi(arg); if (num > 0) token_stack_length = num; break; case 'r': reverse_tree = 1; break; case OPT_NO_REVERSE: reverse_tree = 0; break; case 's': symbol_override(arg); break; case 'v': verbose = 1; break; case OPT_NO_VERBOSE: verbose = 0; break; case 'x': print_option = PRINT_XREF; break; case OPT_PREPROCESS: preprocess_option = 1; set_preprocessor(arg ? arg : CFLOW_PREPROC); break; case OPT_NO_PREPROCESS: preprocess_option = 0; break; case ARGP_KEY_ARG: add_name(arg); break; case 'I': case 'D': case 'U': add_preproc_option(key, arg); break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { options, parse_opt, N_("[FILE]..."), doc, NULL, NULL, NULL }; int globals_only() { return !(symbol_map & SM_STATIC); } int include_symbol(Symbol *sym) { int type = 0; if (!sym) return 0; if (sym->type == SymIdentifier) { if (sym->name[0] == '_' && !(symbol_map & SM_UNDERSCORE)) return 0; if (sym->storage == StaticStorage) type |= SM_STATIC; if (sym->arity == -1 && sym->storage != AutoStorage) type |= SM_DATA; else if (sym->arity >= 0) type |= SM_FUNCTIONS; if (!sym->source) type |= SM_UNDEFINED; } else if (sym->type == SymToken) { if (sym->token_type == TYPE && sym->source) type |= SM_TYPEDEF; else return 0; } return (symbol_map & type) == type; } void xalloc_die(void) { error(EX_FATAL, ENOMEM, _("Exiting")); } void init() { if (level_indent[0] == NULL) level_indent[0] = " "; /* 4 spaces */ if (level_indent[1] == NULL) level_indent[1] = level_indent[0]; if (level_end[0] == NULL) level_end[0] = ""; if (level_end[1] == NULL) level_end[1] = ""; init_lex(debug > 2); init_parse(); } const char version_etc_copyright[] = /* Do *not* mark this string for translation. %s is a copyright symbol suitable for this locale, and %d is the copyright year. */ "Copyright %s 2005, 2006, 2009, 2010, 2011, 2014 %d Sergey Poznyakoff"; int main(int argc, char **argv) { int index; int status = EX_OK; set_program_name(argv[0]); argp_version_setup("cflow", program_authors); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); register_output("gnu", gnu_output_handler, NULL); register_output("posix", posix_output_handler, NULL); symbol_map = SM_FUNCTIONS|SM_STATIC|SM_UNDEFINED; if (getenv("POSIXLY_CORRECT")) { if (select_output_driver("posix")) { error(0, 0, _("INTERNAL ERROR: %s: No such output driver"), "posix"); abort(); } output_init(); } sourcerc(&argc, &argv); if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, &index, NULL)) exit(EX_USAGE); if (print_option == 0) print_option = PRINT_TREE; init(); if (arglist) { struct linked_list_entry *p; for (p = arglist->head; p; p = p->next) { char *s = (char*)p->data; if (s[0] == '-') pp_option(s); else if (source(s) == 0) yyparse(); } } argc -= index; argv += index; while (argc--) { if (source(*argv++) == 0) yyparse(); else status = EX_SOFT; } if (input_file_count == 0) error(EX_USAGE, 0, _("no input files")); output(); return status; }