diff options
37 files changed, 1990 insertions, 105 deletions
@@ -19,6 +19,7 @@ graph obfuscate sparse sparse-llvm +sindex test-dissect test-inspect test-lexing diff --git a/Documentation/IR.rst b/Documentation/IR.rst index 9d6f2299..33a76166 100644 --- a/Documentation/IR.rst +++ b/Documentation/IR.rst @@ -47,6 +47,9 @@ Terminators * .type: type of .cond, must be an integral type * .multijmp_list: pairs of case-value - destination basic block +.. op:: OP_UNREACH + Mark code as unreachable + .. op:: OP_COMPUTEDGOTO Computed goto / branch to register @@ -140,6 +140,17 @@ else $(warning Your system does not have libxml, disabling c2xml) endif +HAVE_SQLITE := $(shell $(PKG_CONFIG) --exists sqlite3 2>/dev/null && echo 'yes') +ifeq ($(HAVE_SQLITE),yes) +PROGRAMS += sindex +INST_PROGRAMS += sindex +INST_MAN1 += sindex.1 +sindex-ldlibs := $(shell $(PKG_CONFIG) --libs sqlite3) +sindex-cflags := $(shell $(PKG_CONFIG) --cflags sqlite3) +else +$(warning Your system does not have sqlite3, disabling sindex) +endif + # Can we use gtk (needed for test-inspect) GTK_VERSION:=3.0 HAVE_GTK:=$(shell $(PKG_CONFIG) --exists gtk+-$(GTK_VERSION) 2>/dev/null && echo 'yes') @@ -228,7 +239,6 @@ cflags += $($(*)-cflags) $(CPPFLAGS) $(CFLAGS) selfcheck: $(OBJS:.o=.sc) - SPARSE_VERSION:=$(shell git describe --dirty 2>/dev/null || echo '$(VERSION)') lib.o: version.h version.h: FORCE @@ -418,6 +418,8 @@ void init_builtins(int stream) sym->op = ptr->op; sym->builtin = 1; } + + init_linearized_builtins(stream); } static void declare_builtin(const char *name, struct symbol *rtype, int variadic, ...) @@ -51,8 +51,9 @@ typedef unsigned usage_t; +struct symbol *dissect_ctx; + static struct reporter *reporter; -static struct symbol *return_type; static void do_sym_list(struct symbol_list *list); @@ -118,24 +119,12 @@ static usage_t fix_mode(struct symbol *type, usage_t mode) return mode; } -static inline struct symbol *no_member(struct ident *name) -{ - static struct symbol sym = { - .type = SYM_BAD, - }; - - sym.ctype.base_type = &bad_ctype; - sym.ident = name ?: built_in_ident("?"); - - return &sym; -} - static struct symbol *report_member(usage_t mode, struct position *pos, struct symbol *type, struct symbol *mem) { struct symbol *ret = mem->ctype.base_type; - if (mem->ident) + if (mem->ident || mem->type == SYM_BAD) reporter->r_member(fix_mode(ret, mode), pos, type, mem); return ret; @@ -163,7 +152,7 @@ static inline struct symbol *expr_symbol(struct expression *expr) if (!sym) { sym = alloc_symbol(expr->pos, SYM_BAD); bind_symbol(sym, expr->symbol_name, NS_SYMBOL); - sym->ctype.modifiers = MOD_EXTERN; + sym->kind = expr->op ?: 'v'; /* see EXPR_CALL */ } } @@ -205,20 +194,20 @@ static bool deanon(struct symbol *base, struct ident *node, struct symbol *paren static void report_memdef(struct symbol *sym, struct symbol *mem) { + mem->kind = 'm'; if (sym && mem->ident) reporter->r_memdef(sym, mem); } static void examine_sym_node(struct symbol *node, struct symbol *parent) { - struct symbol *base; - struct ident *name; + struct ident *name = node->ident; + struct symbol *base, *dctx; if (node->examined) return; - node->examined = 1; - name = node->ident; + node->kind = 'v'; while ((base = node->ctype.base_type) != NULL) switch (base->type) { @@ -229,16 +218,27 @@ static void examine_sym_node(struct symbol *node, struct symbol *parent) case SYM_ARRAY: do_expression(U_R_VAL, base->array_size); - case SYM_PTR: case SYM_FN: + case SYM_PTR: + node = base; + break; + + case SYM_FN: + node->kind = 'f'; node = base; break; case SYM_STRUCT: case SYM_UNION: //case SYM_ENUM: if (base->evaluated) return; + base->evaluated = 1; + base->kind = 's'; + if (!base->symbol_list) return; - base->evaluated = 1; + + dctx = dissect_ctx; + if (toplevel(base->scope)) + dissect_ctx = NULL; if (base->ident || deanon(base, name, parent)) reporter->r_symdef(base); @@ -248,6 +248,7 @@ static void examine_sym_node(struct symbol *node, struct symbol *parent) DO_LIST(base->symbol_list, mem, examine_sym_node(mem, parent); report_memdef(parent, mem)); + dissect_ctx = dctx; default: return; } @@ -294,8 +295,23 @@ found: static struct symbol *lookup_member(struct symbol *type, struct ident *name, int *addr) { - return __lookup_member(type, name, addr) - ?: no_member(name); + struct symbol *mem = __lookup_member(type, name, addr); + + if (!mem) { + static struct symbol bad_member = { + .type = SYM_BAD, + .ctype.base_type = &bad_ctype, + .kind = 'm', + }; + + if (!type->symbol_list) + type->scope = file_scope; + + mem = &bad_member; + mem->ident = name; + } + + return mem; } static struct expression *peek_preop(struct expression *expr, int op) @@ -360,6 +376,8 @@ again: ret = do_expression(mode, expr->cond_false); break; case EXPR_CALL: + if (expr->fn->type == EXPR_SYMBOL) + expr->fn->op = 'f'; /* for expr_symbol() */ ret = do_expression(U_R_PTR, expr->fn); if (is_ptr(ret)) ret = ret->ctype.base_type; @@ -483,8 +501,10 @@ static struct symbol *do_statement(usage_t mode, struct statement *stmt) break; case STMT_EXPRESSION: ret = do_expression(mode, stmt->expression); - break; case STMT_RETURN: - do_expression(u_lval(return_type), stmt->expression); + break; case STMT_RETURN: { + struct symbol *type = dissect_ctx->ctype.base_type; + do_expression(u_lval(base_type(type)), stmt->expression); + } break; case STMT_ASM: do_expression(U_R_VAL, stmt->asm_string); @@ -582,6 +602,8 @@ static struct symbol *do_initializer(struct symbol *type, struct expression *exp static inline struct symbol *do_symbol(struct symbol *sym) { struct symbol *type = base_type(sym); + struct symbol *dctx = dissect_ctx; + struct statement *stmt; reporter->r_symdef(sym); @@ -590,14 +612,25 @@ static inline struct symbol *do_symbol(struct symbol *sym) if (!sym->initializer) break; reporter->r_symbol(U_W_VAL, &sym->pos, sym); + if (!dctx) + dissect_ctx = sym; do_initializer(type, sym->initializer); + dissect_ctx = dctx; break; case SYM_FN: + stmt = sym->ctype.modifiers & MOD_INLINE + ? type->inline_stmt : type->stmt; + if (!stmt) + break; + + if (dctx) + sparse_error(dctx->pos, "dissect_ctx change %s -> %s", + show_ident(dctx->ident), show_ident(sym->ident)); + + dissect_ctx = sym; do_sym_list(type->arguments); - return_type = base_type(type); - do_statement(U_VOID, sym->ctype.modifiers & MOD_INLINE - ? type->inline_stmt - : type->stmt); + do_statement(U_VOID, stmt); + dissect_ctx = dctx; } return type; @@ -608,8 +641,9 @@ static void do_sym_list(struct symbol_list *list) DO_LIST(list, sym, do_symbol(sym)); } -void dissect(struct symbol_list *list, struct reporter *rep) +void dissect(struct reporter *rep, struct string_list *filelist) { reporter = rep; - do_sym_list(list); + + DO_LIST(filelist, file, do_sym_list(__sparse(file))); } @@ -4,6 +4,7 @@ #include <stdio.h> #include "parse.h" #include "expression.h" +#include "scope.h" #define U_SHIFT 8 @@ -25,6 +26,13 @@ struct reporter void (*r_member)(unsigned, struct position *, struct symbol *, struct symbol *); }; -extern void dissect(struct symbol_list *, struct reporter *); +extern struct symbol *dissect_ctx; + +static inline bool sym_is_local(struct symbol *sym) +{ + return !toplevel(sym->scope); +} + +extern void dissect(struct reporter *, struct string_list *); #endif @@ -3107,22 +3107,6 @@ static int evaluate_symbol_call(struct expression *expr) if (ctype->op && ctype->op->evaluate) return ctype->op->evaluate(expr); - if (ctype->ctype.modifiers & MOD_INLINE) { - int ret; - struct symbol *curr = current_fn; - - if (ctype->definition) - ctype = ctype->definition; - - current_fn = ctype->ctype.base_type; - - ret = inline_function(expr, ctype); - - /* restore the old function */ - current_fn = curr; - return ret; - } - return 0; } @@ -910,6 +910,25 @@ static int expand_symbol_call(struct expression *expr, int cost) if (fn->type != EXPR_PREOP) return SIDE_EFFECTS; + if (ctype->ctype.modifiers & MOD_INLINE) { + struct symbol *def; + + def = ctype->definition ? ctype->definition : ctype; + if (inline_function(expr, def)) { + struct symbol *fn = def->ctype.base_type; + struct symbol *curr = current_fn; + + current_fn = fn; + evaluate_statement(expr->statement); + current_fn = curr; + + fn->expanding = 1; + cost = expand_expression(expr); + fn->expanding = 0; + return cost; + } + } + if (ctype->op && ctype->op->expand) return ctype->op->expand(expr, cost); diff --git a/expression.c b/expression.c index ffb6cb9d..5b9bddfe 100644 --- a/expression.c +++ b/expression.c @@ -873,7 +873,7 @@ struct token *conditional_expression(struct token *token, struct expression **tr if (*tree && match_op(token, '?')) { struct expression *expr = alloc_expression(token->pos, EXPR_CONDITIONAL); expr->op = token->special; - expr->left = *tree; + expr->conditional = *tree; *tree = expr; token = parse_expression(token->next, &expr->cond_true); token = expect(token, ':', "in conditional expression"); @@ -519,8 +519,6 @@ int inline_function(struct expression *expr, struct symbol *sym) if (fn->expanding) return 0; - fn->expanding = 1; - name_list = fn->arguments; expr->type = EXPR_STATEMENT; @@ -558,9 +556,6 @@ int inline_function(struct expression *expr, struct symbol *sym) unset_replace_list(fn_symbol_list); - evaluate_statement(stmt); - - fn->expanding = 0; return 1; } @@ -280,6 +280,7 @@ int Woverride_init = 1; int Woverride_init_all = 0; int Woverride_init_whole_range = 0; int Wparen_string = 0; +int Wpedantic = 0; int Wpointer_arith = 0; int Wpointer_to_int_cast = 1; int Wptr_subtraction_blows = 0; @@ -725,6 +726,17 @@ static char **handle_switch_o(char *arg, char **next) return next; } +static const struct flag pflags[] = { + { "pedantic", &Wpedantic, NULL, OPT_VAL, WARNING_ON }, + { } +}; + +static char **handle_switch_p(char *arg, char **next) +{ + handle_switches(arg-1, arg, pflags); + return next; +} + static const struct flag warnings[] = { { "address", &Waddress }, { "address-space", &Waddress_space }, @@ -754,6 +766,7 @@ static const struct flag warnings[] = { { "override-init", &Woverride_init }, { "override-init-all", &Woverride_init_all }, { "paren-string", &Wparen_string }, + { "pedantic", &Wpedantic }, { "pointer-to-int-cast", &Wpointer_to_int_cast }, { "ptr-subtraction-blows", &Wptr_subtraction_blows }, { "return-void", &Wreturn_void }, @@ -1181,6 +1194,7 @@ static char **handle_switch(char *arg, char **next) case 'n': return handle_switch_n(arg, next); case 'o': return handle_switch_o(arg, next); case 'O': return handle_switch_O(arg, next); + case 'p': return handle_switch_p(arg, next); case 's': return handle_switch_s(arg, next); case 'U': return handle_switch_U(arg, next); case 'v': return handle_switch_v(arg, next); @@ -169,6 +169,7 @@ extern int Woverride_init; extern int Woverride_init_all; extern int Woverride_init_whole_range; extern int Wparen_string; +extern int Wpedantic; extern int Wpointer_arith; extern int Wpointer_to_int_cast; extern int Wptr_subtraction_blows; diff --git a/linearize.c b/linearize.c index 30ed2a30..b040d345 100644 --- a/linearize.c +++ b/linearize.c @@ -183,6 +183,7 @@ static const char *opcodes[] = { [OP_BR] = "br", [OP_CBR] = "cbr", [OP_SWITCH] = "switch", + [OP_UNREACH] = "unreachable", [OP_COMPUTEDGOTO] = "jmp *", /* Binary */ @@ -399,6 +400,8 @@ const char *show_instruction(struct instruction *insn) } END_FOR_EACH_PTR(jmp); break; } + case OP_UNREACH: + break; case OP_PHISOURCE: { struct instruction *phi; @@ -654,6 +657,13 @@ static void add_one_insn(struct entrypoint *ep, struct instruction *insn) } } +static void add_unreachable(struct entrypoint *ep) +{ + struct instruction *insn = alloc_instruction(OP_UNREACH, 0); + add_one_insn(ep, insn); + ep->active = NULL; +} + static void set_activeblock(struct entrypoint *ep, struct basic_block *bb) { if (!bb_terminated(ep->active)) @@ -1499,6 +1509,11 @@ static pseudo_t linearize_call_expression(struct entrypoint *ep, struct expressi fn = expr->fn; fntype = fn->ctype; + + // handle builtins + if (fntype->op && fntype->op->linearize) + return fntype->op->linearize(ep, expr); + ctype = &fntype->ctype; if (fntype->type == SYM_NODE) fntype = fntype->ctype.base_type; @@ -1548,6 +1563,9 @@ static pseudo_t linearize_call_expression(struct entrypoint *ep, struct expressi add_one_insn(ep, insn); } } END_FOR_EACH_PTR(context); + + if (ctype->modifiers & MOD_NORETURN) + add_unreachable(ep); } return retval; @@ -2513,3 +2531,37 @@ struct entrypoint *linearize_symbol(struct symbol *sym) return linearize_fn(sym, base_type); return NULL; } + +/* + * Builtin functions + */ + +static pseudo_t linearize_unreachable(struct entrypoint *ep, struct expression *exp) +{ + add_unreachable(ep); + return VOID; +} + +static struct sym_init { + const char *name; + pseudo_t (*linearize)(struct entrypoint *, struct expression*); + struct symbol_op op; +} builtins_table[] = { + // must be declared in builtin.c:declare_builtins[] + { "__builtin_unreachable", linearize_unreachable }, + { } +}; + +void init_linearized_builtins(int stream) +{ + struct sym_init *ptr; + + for (ptr = builtins_table; ptr->name; ptr++) { + struct symbol *sym; + sym = create_symbol(stream, ptr->name, SYM_NODE, NS_SYMBOL); + if (!sym->op) + sym->op = &ptr->op; + sym->op->type |= KW_BUILTIN; + ptr->op.linearize = ptr->linearize; + } +} @@ -10,6 +10,7 @@ OPCODE(RET, BADOP, BADOP, BADOP, 1, OPF_NONE) OPCODE(BR, BADOP, BADOP, BADOP, 0, OPF_NONE) OPCODE(CBR, BADOP, BADOP, BADOP, 1, OPF_NONE) OPCODE(SWITCH, BADOP, BADOP, BADOP, 1, OPF_NONE) +OPCODE(UNREACH, BADOP, BADOP, BADOP, 0, OPF_NONE) OPCODE(COMPUTEDGOTO, BADOP, BADOP, BADOP, 1, OPF_NONE) OPCODE_RANGE(TERMINATOR, RET, COMPUTEDGOTO) @@ -60,6 +60,7 @@ static declarator_t thread_specifier, const_qualifier, volatile_qualifier; static declarator_t restrict_qualifier; static declarator_t atomic_qualifier; +static declarator_t autotype_specifier; static struct token *parse_if_statement(struct token *token, struct statement *stmt); static struct token *parse_return_statement(struct token *token, struct statement *stmt); @@ -213,6 +214,13 @@ static struct symbol_op typeof_op = { .set = Set_S|Set_T, }; +static struct symbol_op autotype_op = { + .type = KW_SPECIFIER, + .declarator = autotype_specifier, + .test = Set_Any, + .set = Set_S|Set_T, +}; + static struct symbol_op attribute_op = { .type = KW_ATTRIBUTE, .declarator = attribute_specifier, @@ -505,6 +513,7 @@ static struct init_keyword { { "typeof", NS_TYPEDEF, .op = &typeof_op }, { "__typeof", NS_TYPEDEF, .op = &typeof_op }, { "__typeof__", NS_TYPEDEF, .op = &typeof_op }, + { "__auto_type",NS_TYPEDEF, .op = &autotype_op }, { "__attribute", NS_TYPEDEF, .op = &attribute_op }, { "__attribute__", NS_TYPEDEF, .op = &attribute_op }, @@ -772,6 +781,7 @@ static struct token *struct_union_enum_specifier(enum type type, } sym = alloc_symbol(token->pos, type); + set_current_scope(sym); // used by dissect token = parse(token->next, sym); ctx->ctype.base_type = sym; token = expect(token, '}', "at end of specifier"); @@ -1077,6 +1087,13 @@ static struct token *typeof_specifier(struct token *token, struct decl_state *ct return expect(token, ')', "after typeof"); } +static struct token *autotype_specifier(struct token *token, struct decl_state *ctx) +{ + ctx->ctype.base_type = &autotype_ctype; + ctx->autotype = 1; + return token; +} + static struct token *ignore_attribute(struct token *token, struct symbol *attr, struct decl_state *ctx) { struct expression *expr = NULL; @@ -2984,6 +3001,11 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis } } } else if (base_type && base_type->type == SYM_FN) { + if (base_type->ctype.base_type == &autotype_ctype) { + sparse_error(decl->pos, "'%s()' has __auto_type return type", + show_ident(decl->ident)); + base_type->ctype.base_type = &int_ctype; + } if (base_type->ctype.base_type == &incomplete_ctype) { warning(decl->pos, "'%s()' has implicit return type", show_ident(decl->ident)); @@ -3036,6 +3058,23 @@ struct token *external_declaration(struct token *token, struct symbol_list **lis } } + if (ctx.autotype) { + const char *msg = NULL; + if (decl->ctype.base_type != &autotype_ctype) + msg = "on non-identifier"; + else if (match_op(token, ',')) + msg = "on declaration list"; + else if (!decl->initializer) + msg = "without initializer"; + else if (decl->initializer->type == EXPR_SYMBOL && + decl->initializer->symbol == decl) + msg = "on self-init var"; + if (msg) { + sparse_error(decl->pos, "__auto_type %s", msg); + decl->ctype.base_type = &bad_ctype; + } + } + if (!match_op(token, ',')) break; diff --git a/pre-process.c b/pre-process.c index 7b245a04..c8725dc8 100644 --- a/pre-process.c +++ b/pre-process.c @@ -48,6 +48,7 @@ static struct ident_list *macros; // only needed for -dD static int false_nesting = 0; static int counter_macro = 0; // __COUNTER__ expansion static int include_level = 0; +static int expanding = 0; #define INCLUDEPATHS 300 const char *includepath[INCLUDEPATHS+1] = { @@ -232,8 +233,13 @@ static int expand_one_symbol(struct token **list) sym->expander(token); return 1; } else { + int rc; + sym->used_in = file_scope; - return expand(list, sym); + expanding = 1; + rc = expand(list, sym); + expanding = 0; + return rc; } } @@ -271,8 +277,6 @@ static struct token *collect_arg(struct token *prev, int vararg, struct position while (!eof_token(next = scan_next(p))) { if (next->pos.newline && match_op(next, '#')) { if (!next->pos.noexpand) { - sparse_error(next->pos, - "directive in macro's argument list"); preprocessor_line(stream, p); __free_token(next); /* Free the '#' token */ continue; @@ -305,6 +309,7 @@ static struct token *collect_arg(struct token *prev, int vararg, struct position next->pos.stream = pos->stream; next->pos.line = pos->line; next->pos.pos = pos->pos; + next->pos.newline = 0; p = &next->next; } *p = &eof_token_entry; @@ -755,6 +760,7 @@ static int expand(struct token **list, struct symbol *sym) struct token *token = *list; struct ident *expanding = token->ident; struct token **tail; + struct token *expansion = sym->expansion; int nargs = sym->arglist ? sym->arglist->count.normal : 0; struct arg args[nargs]; @@ -774,7 +780,7 @@ static int expand(struct token **list, struct symbol *sym) expanding->tainted = 1; last = token->next; - tail = substitute(list, sym->expansion, args); + tail = substitute(list, expansion, args); /* * Note that it won't be eof - at least TOKEN_UNTAINT will be there. * We still can lose the newline flag if the sucker expands to nothing, @@ -2073,6 +2079,7 @@ static void handle_preprocessor_line(struct stream *stream, struct token **line, int (*handler)(struct stream *, struct token **, struct token *); struct token *token = start->next; int is_normal = 1; + int is_cond = 0; // is one of {is,ifdef,ifndef,elif,else,endif} if (eof_token(token)) return; @@ -2082,6 +2089,7 @@ static void handle_preprocessor_line(struct stream *stream, struct token **line, if (sym) { handler = sym->handler; is_normal = sym->normal; + is_cond = !sym->normal; } else { handler = handle_nondirective; } @@ -2096,6 +2104,11 @@ static void handle_preprocessor_line(struct stream *stream, struct token **line, if (false_nesting) goto out; } + + if (expanding) { + if (!is_cond || Wpedantic) + warning(start->pos, "directive in macro's argument list"); + } if (!handler(stream, line, token)) /* all set */ return; @@ -40,6 +40,11 @@ struct scope *block_scope = &builtin_scope, // regular automatic variables etc *file_scope = &builtin_scope, // static *global_scope = &builtin_scope; // externally visible +void set_current_scope(struct symbol *sym) +{ + sym->scope = block_scope; +} + void bind_scope(struct symbol *sym, struct scope *scope) { sym->scope = scope; @@ -53,6 +53,7 @@ extern void end_symbol_scope(void); extern void start_function_scope(void); extern void end_function_scope(void); +extern void set_current_scope(struct symbol *); extern void bind_scope(struct symbol *, struct scope *); extern void rebind_scope(struct symbol *, struct scope *); diff --git a/sindex.1 b/sindex.1 new file mode 100644 index 00000000..e3e14ca3 --- /dev/null +++ b/sindex.1 @@ -0,0 +1,151 @@ +.\" Sindex manpage by Alexey Gladkov +.TH sindex "1" +. +.SH NAME +sindex \- Semantic Indexer for C +. +.SH SYNOPSIS +.B sindex +[\fIoptions\fR] +.br +.B sindex +[\fIoptions\fR] \fIadd\fR [\fIcommand options\fR] [\fI--\fR] [\fIcompiler options\fR] [\fIfiles...\fR] +.br +.B sindex +[\fIoptions\fR] \fIrm\fR [\fIcommand options\fR] \fIpattern\fR +.br +.B sindex +[\fIoptions\fR] \fIsearch\fR [\fIcommand options\fR] [\fIpattern\fR] +.br +.B sindex [\fIoptions\fR] \fIsearch\fR [\fIcommand options\fR] (\fI-e\fR|\fI-l\fR) \fIfilename\fR:\fIlinenr\fR:\fIcolumn\fR +.br +.SH DESCRIPTION +.P +sindex is the simple to use cscope-like tool based on sparse/dissect. Unlike +cscope it runs after pre-processor and thus it can't index the code filtered out +by ifdef's, but otoh it understands how the symbol is used and it can track the +usage of struct members. +. +.SH SUBCOMMANDS +.TP +\fBadd\fR +generates or updates semantic index file. +.TP +\fBrm\fR +removes files from the index by \fIpattern\fR. The \fIpattern\fR is a +.BR glob (7) +wildcard pattern. +.TP +\fBsearch\fR +queries information about symbol by \fIpattern\fR. The \fIpattern\fR is a +.BR glob (7) +wildcard pattern. +. +.SH COMMON OPTIONS +.TP +\fB-D\fR, \fB--database=FILE\fR +specify database file (default: ./sindex.sqlite). +.TP +\fB-v\fR, \fB--verbose\fR +show information about what is being done. +.TP +\fB-h\fR, \fB--help\fR +show this text and exit. +. +.SH ADD OPTIONS +.TP +\fB--include-local-syms\fR +include into the index local symbols. +. +.SH SEARCH OPTIONS +.TP +\fB-f\fR, \fB--format=STRING\fR +specify an output format. Default: '(%m) %f\\t%l\\t%c\\t%C\\t%s' (see +.BR FORMAT +below). +.TP +\fB-p\fR, \fB--path=PATTERN\fR +search symbols only in specified directories. +.TP +\fB-m\fR, \fB--mode=MODE\fR +search only the specified type of access (see +.BR MODE +below). +.TP +\fB-k\fR, \fB--kind=KIND\fR +specify a kind of symbol (see +.BR KIND +below). +.TP +\fB-e\fR, \fB--explain\fR +Show what happens in the specified file position; +.TP +\fB-l\fR, \fB--location\fR +Show usage of symbols from a specific file position; +.TP +\fB-v\fR, \fB--verbose\fR +show information about what is being done; +.TP +\fB-h\fR, \fB--help\fR +show this text and exit. +. +.SH FORMAT +.TP +\fB%m\fR +access mode in human readable form (see +.BR MODE +below). +.TP +\fB%f\fR +file name. +.TP +\fB%l\fR +line number. +.TP +\fB%c\fR +column number. +.TP +\fB%C\fR +the name of the function in which the symbol occurs. +.TP +\fB%n\fR +symbol name. +.TP +\fB%s\fR +source code line. Indexer does not save source code lines. They are read from +the file during the search. +. +.SH KIND +.TP +\fBf\fR +function +.TP +\fBs\fR +strict +.TP +\fBm\fR +struct member +. +.SH MODE +The \fBMODE\fR is dumped as a 3-letter string. The first letter denotes address +of part, 2-nd - access by value, 3-rd - access by pointer. A special value +\'\fIdef\fR\' means a symbol definition. +.TP +\fBr\fR +read +.TP +\fBw\fR +write +.TP +\fBm\fR +read and write +. +.SH SEE ALSO +.BR sparse (1) +. +.SH HOMEPAGE +http://www.kernel.org/pub/software/devel/sparse/ +. +.SH MAILING LIST +linux-sparse@vger.kernel.org +. diff --git a/sindex.c b/sindex.c new file mode 100644 index 00000000..ea092a4a --- /dev/null +++ b/sindex.c @@ -0,0 +1,1164 @@ +/* + * sindex - semantic indexer for C. + * + * Copyright (C) 2020 Alexey Gladkov + */ + +#define _GNU_SOURCE +#include <sys/types.h> +#include <sys/stat.h> + +#include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <getopt.h> +#include <ctype.h> +#include <errno.h> +#include <sqlite3.h> + +#include "dissect.h" + +#define U_DEF (0x100 << U_SHIFT) +#define SINDEX_DATABASE_VERSION 1 + +#define message(fmt, ...) sindex_error(0, 0, (fmt), ##__VA_ARGS__) + +static const char *progname; +static const char *sindex_command = NULL; + +// common options +static const char *sindex_dbfile = "sindex.sqlite"; +static int sindex_verbose = 0; +static char cwd[PATH_MAX]; +static size_t n_cwd; + +// 'add' command options +static struct string_list *sindex_filelist = NULL; +static int sindex_include_local_syms = 0; + +struct sindex_streams { + sqlite3_int64 id; +}; + +static struct sindex_streams *sindex_streams = NULL; +static int sindex_streams_nr = 0; + +// 'search' command options +static int sindex_search_modmask; +static int sindex_search_modmask_defined = 0; +static int sindex_search_kind = 0; +static char *sindex_search_path = NULL; +static char *sindex_search_symbol = NULL; +static const char *sindex_search_format = "(%m) %f\t%l\t%c\t%C\t%s"; + +#define EXPLAIN_LOCATION 1 +#define USAGE_BY_LOCATION 2 +static int sindex_search_by_location; +static char *sindex_search_filename; +static int sindex_search_line; +static int sindex_search_column; + +static sqlite3 *sindex_db = NULL; +static sqlite3_stmt *lock_stmt = NULL; +static sqlite3_stmt *unlock_stmt = NULL; +static sqlite3_stmt *insert_rec_stmt = NULL; +static sqlite3_stmt *select_file_stmt = NULL; +static sqlite3_stmt *insert_file_stmt = NULL; +static sqlite3_stmt *delete_file_stmt = NULL; + +struct command { + const char *name; + int dbflags; + void (*parse_cmdline)(int argc, char **argv); + void (*handler)(int argc, char **argv); +}; + +static void show_usage(void) +{ + if (sindex_command) + printf("Try '%s %s --help' for more information.\n", + progname, sindex_command); + else + printf("Try '%s --help' for more information.\n", + progname); + exit(1); +} + +static void show_help(int ret) +{ + printf( + "Usage: %1$s [options]\n" + " or: %1$s [options] add [command options] [--] [compiler options] [files...]\n" + " or: %1$s [options] rm [command options] pattern\n" + " or: %1$s [options] search [command options] pattern\n" + "\n" + "These are common %1$s commands used in various situations:\n" + " add Generate or updates semantic index file for c-source code;\n" + " rm Remove files from the index by pattern;\n" + " search Make index queries.\n" + "\n" + "Options:\n" + " -D, --database=FILE Specify database file (default: %2$s);\n" + " -v, --verbose Show information about what is being done;\n" + " -h, --help Show this text and exit.\n" + "\n" + "Environment:\n" + " SINDEX_DATABASE Database file location.\n" + "\n" + "Report bugs to authors.\n" + "\n", + progname, sindex_dbfile); + exit(ret); +} + +static void show_help_add(int ret) +{ + printf( + "Usage: %1$s add [options] [--] [compiler options] files...\n" + "\n" + "Utility creates or updates a symbol index.\n" + "\n" + "Options:\n" + " --include-local-syms Include into the index local symbols;\n" + " -v, --verbose Show information about what is being done;\n" + " -h, --help Show this text and exit.\n" + "\n" + "Environment:\n" + " SINDEX_BASEDIRE Project top directory.\n" + "\n" + "Report bugs to authors.\n" + "\n", + progname); + exit(ret); + +} + +static void show_help_rm(int ret) +{ + printf( + "Usage: %1$s rm [options] pattern\n" + "\n" + "Utility removes source files from the index.\n" + "The pattern is a glob(7) wildcard pattern.\n" + "\n" + "Options:\n" + " -v, --verbose Show information about what is being done;\n" + " -h, --help Show this text and exit.\n" + "\n" + "Report bugs to authors.\n" + "\n", + progname); + exit(ret); +} + +static void show_help_search(int ret) +{ + printf( + "Usage: %1$s search [options] [pattern]\n" + " or: %1$s search [options] (-e|-l) filename[:linenr[:column]]\n" + "\n" + "Utility searches information about symbol by pattern.\n" + "The pattern is a glob(7) wildcard pattern.\n" + "\n" + "Options:\n" + " -f, --format=STRING Specify an output format;\n" + " -p, --path=PATTERN Search symbols only in specified directories;\n" + " -m, --mode=MODE Search only the specified type of access;\n" + " -k, --kind=KIND Specify a kind of symbol;\n" + " -e, --explain Show what happens in the specified file position;\n" + " -l, --location Show usage of symbols from a specific file position;\n" + " -v, --verbose Show information about what is being done;\n" + " -h, --help Show this text and exit.\n" + "\n" + "The KIND can be one of the following: `s', `f', `v', `m'.\n" + "\n" + "Report bugs to authors.\n" + "\n", + progname); + exit(ret); +} + +static void sindex_print_progname(void) +{ + fprintf(stderr, "%s: ", progname); + if (sindex_command) + fprintf(stderr, "%s: ", sindex_command); +} + +static void sindex_error(int status, int errnum, const char *fmt, ...) +{ + va_list ap; + sindex_print_progname(); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (errnum > 0) + fprintf(stderr, ": %s", strerror(errnum)); + + fprintf(stderr, "\n"); + + if (status) + exit(status); +} + +static void set_search_modmask(const char *v) +{ + size_t n = strlen(v); + + if (n != 1 && n != 3) + sindex_error(1, 0, "the length of mode value must be 1 or 3: %s", v); + + sindex_search_modmask_defined = 1; + sindex_search_modmask = 0; + + if (n == 1) { + switch (v[0]) { + case 'r': v = "rrr"; break; + case 'w': v = "ww-"; break; + case 'm': v = "mmm"; break; + case '-': v = "---"; break; + default: sindex_error(1, 0, "unknown modificator: %s", v); + } + } else if (!strcmp(v, "def")) { + sindex_search_modmask = U_DEF; + return; + } + + static const int modes[] = { + U_R_AOF, U_W_AOF, U_R_AOF | U_W_AOF, + U_R_VAL, U_W_VAL, U_R_VAL | U_W_VAL, + U_R_PTR, U_W_PTR, U_R_PTR | U_W_PTR, + }; + + for (int i = 0; i < 3; i++) { + switch (v[i]) { + case 'r': sindex_search_modmask |= modes[i * 3]; break; + case 'w': sindex_search_modmask |= modes[i * 3 + 1]; break; + case 'm': sindex_search_modmask |= modes[i * 3 + 2]; break; + case '-': break; + default: sindex_error(1, 0, + "unknown modificator in the mode value" + " (`r', `w', `m' or `-' expected): %c", v[i]); + } + } +} + +static void parse_cmdline(int argc, char **argv) +{ + static const struct option long_options[] = { + { "database", required_argument, NULL, 'D' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + int c; + char *env; + + if ((env = getenv("SINDEX_DATABASE")) != NULL) + sindex_dbfile = env; + + while ((c = getopt_long(argc, argv, "+D:vh", long_options, NULL)) != -1) { + switch (c) { + case 'D': + sindex_dbfile = optarg; + break; + case 'v': + sindex_verbose++; + break; + case 'h': + show_help(0); + } + } + + if (optind == argc) { + message("command required"); + show_usage(); + } +} + +static void parse_cmdline_add(int argc, char **argv) +{ + static const struct option long_options[] = { + { "include-local-syms", no_argument, NULL, 1 }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + int c; + + opterr = 0; + + while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) { + switch (c) { + case 1: + sindex_include_local_syms = 1; + break; + case 'v': + sindex_verbose++; + break; + case 'h': + show_help_add(0); + case '?': + goto done; + } + } +done: + if (optind == argc) { + message("more arguments required"); + show_usage(); + } + + // enforce tabstop + tabstop = 1; + + // step back since sparse_initialize will ignore argv[0]. + optind--; + + sparse_initialize(argc - optind, argv + optind, &sindex_filelist); +} + +static void parse_cmdline_rm(int argc, char **argv) +{ + static const struct option long_options[] = { + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + int c; + + while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) { + switch (c) { + case 'v': + sindex_verbose++; + break; + case 'h': + show_help_rm(0); + } + } + + if (optind == argc) { + message("more arguments required"); + show_usage(); + } +} + +static void parse_cmdline_search(int argc, char **argv) +{ + static const struct option long_options[] = { + { "explain", no_argument, NULL, 'e' }, + { "format", required_argument, NULL, 'f' }, + { "path", required_argument, NULL, 'p' }, + { "location", no_argument, NULL, 'l' }, + { "mode", required_argument, NULL, 'm' }, + { "kind", required_argument, NULL, 'k' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { NULL } + }; + int c; + + while ((c = getopt_long(argc, argv, "+ef:m:k:p:lvh", long_options, NULL)) != -1) { + switch (c) { + case 'e': + sindex_search_by_location = EXPLAIN_LOCATION; + break; + case 'l': + sindex_search_by_location = USAGE_BY_LOCATION; + break; + case 'f': + sindex_search_format = optarg; + break; + case 'm': + set_search_modmask(optarg); + break; + case 'k': + sindex_search_kind = tolower(optarg[0]); + break; + case 'p': + sindex_search_path = optarg; + break; + case 'v': + sindex_verbose++; + break; + case 'h': + show_help_search(0); + } + } + + if (sindex_search_by_location) { + char *str; + + if (optind == argc) + sindex_error(1, 0, "one argument required"); + + str = argv[optind]; + + while (str) { + char *ptr; + + if ((ptr = strchr(str, ':')) != NULL) + *ptr++ = '\0'; + + if (*str != '\0') { + if (!sindex_search_filename) { + sindex_search_filename = str; + } else if (!sindex_search_line) { + sindex_search_line = atoi(str); + } else if (!sindex_search_column) { + sindex_search_column = atoi(str); + } + } + str = ptr; + } + } else if (optind < argc) + sindex_search_symbol = argv[optind++]; +} + +static int query_appendf(sqlite3_str *query, const char *fmt, ...) +{ + int status; + va_list args; + + va_start(args, fmt); + sqlite3_str_vappendf(query, fmt, args); + va_end(args); + + if ((status = sqlite3_str_errcode(query)) == SQLITE_OK) + return 0; + + if (status == SQLITE_NOMEM) + message("not enough memory"); + + if (status == SQLITE_TOOBIG) + message("string too big"); + + return -1; +} + +static inline void sqlite_bind_text(sqlite3_stmt *stmt, const char *field, const char *var, int len) +{ + if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, field), var, len, SQLITE_STATIC) != SQLITE_OK) + sindex_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(sindex_db)); +} + +static inline void sqlite_bind_int64(sqlite3_stmt *stmt, const char *field, long long var) +{ + if (sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, field), var) != SQLITE_OK) + sindex_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(sindex_db)); +} + +static inline void sqlite_prepare(const char *sql, sqlite3_stmt **stmt) +{ + int ret; + do { + ret = sqlite3_prepare_v2(sindex_db, sql, -1, stmt, NULL); + if (ret != SQLITE_OK && ret != SQLITE_BUSY) + sindex_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(sindex_db), sql); + } while (ret == SQLITE_BUSY); +} + +static inline void sqlite_prepare_persistent(const char *sql, sqlite3_stmt **stmt) +{ + int ret; + do { + ret = sqlite3_prepare_v3(sindex_db, sql, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL); + if (ret != SQLITE_OK && ret != SQLITE_BUSY) + sindex_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(sindex_db), sql); + } while (ret == SQLITE_BUSY); +} + +static inline void sqlite_reset_stmt(sqlite3_stmt *stmt) +{ + // Contrary to the intuition of many, sqlite3_reset() does not reset the + // bindings on a prepared statement. Use this routine to reset all host + // parameters to NULL. + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); +} + +static int sqlite_run(sqlite3_stmt *stmt) +{ + int ret = sqlite3_step(stmt); + if (ret != SQLITE_DONE && ret != SQLITE_ROW) + sindex_error(1, 0, "unable to process query: %s: %s", sqlite3_errmsg(sindex_db), sqlite3_sql(stmt)); + return ret; +} + +static void sqlite_command(const char *sql) +{ + sqlite3_stmt *stmt; + sqlite_prepare(sql, &stmt); + sqlite_run(stmt); + sqlite3_finalize(stmt); +} + +static sqlite3_int64 get_db_version(void) +{ + sqlite3_stmt *stmt; + sqlite3_int64 dbversion; + + sqlite_prepare("PRAGMA user_version", &stmt); + sqlite_run(stmt); + dbversion = sqlite3_column_int64(stmt, 0); + sqlite3_finalize(stmt); + + return dbversion; +} + +static void set_db_version(void) +{ + char *sql; + sqlite3_str *query = sqlite3_str_new(sindex_db); + + if (query_appendf(query, "PRAGMA user_version = %d", SINDEX_DATABASE_VERSION) < 0) + exit(1); + + sql = sqlite3_str_finish(query); + sqlite_command(sql); + sqlite3_free(sql); +} + +static void open_temp_database(void) +{ + static const char *database_schema[] = { + "ATTACH ':memory:' AS tempdb", + "CREATE TABLE tempdb.sindex (" + " file INTEGER NOT NULL," + " line INTEGER NOT NULL," + " column INTEGER NOT NULL," + " symbol TEXT NOT NULL," + " kind INTEGER NOT NULL," + " context TEXT," + " mode INTEGER NOT NULL" + ")", + NULL, + }; + + for (int i = 0; database_schema[i]; i++) + sqlite_command(database_schema[i]); +} + +static void open_database(const char *filename, int flags) +{ + static const char *database_schema[] = { + "CREATE TABLE file (" + " id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT UNIQUE NOT NULL," + " mtime INTEGER NOT NULL" + ")", + "CREATE TABLE sindex (" + " file INTEGER NOT NULL REFERENCES file(id) ON DELETE CASCADE," + " line INTEGER NOT NULL," + " column INTEGER NOT NULL," + " symbol TEXT NOT NULL," + " kind INTEGER NOT NULL," + " context TEXT," + " mode INTEGER NOT NULL" + ")", + "CREATE UNIQUE INDEX sindex_0 ON sindex (symbol, kind, mode, file, line, column)", + "CREATE INDEX sindex_1 ON sindex (file)", + NULL, + }; + + int exists = !access(filename, R_OK); + + if (sqlite3_open_v2(filename, &sindex_db, flags, NULL) != SQLITE_OK) + sindex_error(1, 0, "unable to open database: %s: %s", filename, sqlite3_errmsg(sindex_db)); + + sqlite_command("PRAGMA journal_mode = WAL"); + sqlite_command("PRAGMA synchronous = OFF"); + sqlite_command("PRAGMA secure_delete = FAST"); + sqlite_command("PRAGMA busy_timeout = 2147483647"); + sqlite_command("PRAGMA foreign_keys = ON"); + + if (exists) { + if (get_db_version() < SINDEX_DATABASE_VERSION) + sindex_error(1, 0, "%s: Database too old. Please rebuild it.", filename); + return; + } + + set_db_version(); + + for (int i = 0; database_schema[i]; i++) + sqlite_command(database_schema[i]); +} + +struct index_record { + const char *context; + int ctx_len; + + const char *symbol; + int sym_len; + + int kind; + unsigned int mode; + long long mtime; + sqlite3_int64 file; + int line; + int col; +}; + +static void insert_record(struct index_record *rec) +{ + sqlite_bind_text(insert_rec_stmt, "@context", rec->context, rec->ctx_len); + sqlite_bind_text(insert_rec_stmt, "@symbol", rec->symbol, rec->sym_len); + sqlite_bind_int64(insert_rec_stmt, "@kind", rec->kind); + sqlite_bind_int64(insert_rec_stmt, "@mode", rec->mode); + sqlite_bind_int64(insert_rec_stmt, "@file", rec->file); + sqlite_bind_int64(insert_rec_stmt, "@line", rec->line); + sqlite_bind_int64(insert_rec_stmt, "@column", rec->col); + sqlite_run(insert_rec_stmt); + sqlite_reset_stmt(insert_rec_stmt); +} + +static void update_stream(void) +{ + if (sindex_streams_nr >= input_stream_nr) + return; + + sindex_streams = realloc(sindex_streams, input_stream_nr * sizeof(struct sindex_streams)); + if (!sindex_streams) + sindex_error(1, errno, "realloc"); + + sqlite_run(lock_stmt); + + for (int i = sindex_streams_nr; i < input_stream_nr; i++) { + struct stat st; + const char *filename; + char fullname[PATH_MAX]; + sqlite3_int64 cur_mtime = 0; + + if (input_streams[i].fd != -1) { + /* + * FIXME: Files in the input_streams may be duplicated. + */ + if (stat(input_streams[i].name, &st) < 0) + sindex_error(1, errno, "stat: %s", input_streams[i].name); + + cur_mtime = st.st_mtime; + + if (!realpath(input_streams[i].name, fullname)) + sindex_error(1, errno, "realpath: %s", input_streams[i].name); + + if (!strncmp(fullname, cwd, n_cwd) && fullname[n_cwd] == '/') { + filename = fullname + n_cwd + 1; + sindex_streams[i].id = 0; + } else { + sindex_streams[i].id = -1; + continue; + } + } else { + sindex_streams[i].id = -1; + continue; + } + + if (sindex_verbose > 1) + message("filename: %s", filename); + + sqlite_bind_text(select_file_stmt, "@name", filename, -1); + + if (sqlite_run(select_file_stmt) == SQLITE_ROW) { + sqlite3_int64 old_mtime; + + sindex_streams[i].id = sqlite3_column_int64(select_file_stmt, 0); + old_mtime = sqlite3_column_int64(select_file_stmt, 1); + + sqlite_reset_stmt(select_file_stmt); + + if (cur_mtime == old_mtime) + continue; + + sqlite_bind_text(delete_file_stmt, "@name", filename, -1); + sqlite_run(delete_file_stmt); + sqlite_reset_stmt(delete_file_stmt); + } + + sqlite_reset_stmt(select_file_stmt); + + sqlite_bind_text(insert_file_stmt, "@name", filename, -1); + sqlite_bind_int64(insert_file_stmt, "@mtime", cur_mtime); + sqlite_run(insert_file_stmt); + sqlite_reset_stmt(insert_file_stmt); + + sindex_streams[i].id = sqlite3_last_insert_rowid(sindex_db); + } + + sqlite_run(unlock_stmt); + + sindex_streams_nr = input_stream_nr; +} + +static void r_symbol(unsigned mode, struct position *pos, struct symbol *sym) +{ + static struct ident null; + struct ident *ctx = &null; + struct index_record rec; + + update_stream(); + + if (sindex_streams[pos->stream].id == -1) + return; + + if (!sindex_include_local_syms && sym_is_local(sym)) + return; + + if (!sym->ident) { + warning(*pos, "empty ident"); + return; + } + + if (dissect_ctx) + ctx = dissect_ctx->ident; + + rec.context = ctx->name; + rec.ctx_len = ctx->len; + rec.symbol = sym->ident->name; + rec.sym_len = sym->ident->len; + rec.kind = sym->kind; + rec.mode = mode; + rec.file = sindex_streams[pos->stream].id; + rec.line = pos->line; + rec.col = pos->pos; + + insert_record(&rec); +} + +static void r_member(unsigned mode, struct position *pos, struct symbol *sym, struct symbol *mem) +{ + static struct ident null; + static char memname[1024]; + struct ident *ni, *si, *mi; + struct ident *ctx = &null; + struct index_record rec; + + update_stream(); + + if (sindex_streams[pos->stream].id == -1) + return; + + if (!sindex_include_local_syms && sym_is_local(sym)) + return; + + ni = built_in_ident("?"); + si = sym->ident ?: ni; + /* mem == NULL means entire struct accessed */ + mi = mem ? (mem->ident ?: ni) : built_in_ident("*"); + + if (dissect_ctx) + ctx = dissect_ctx->ident; + + snprintf(memname, sizeof(memname), "%.*s.%.*s", si->len, si->name, mi->len, mi->name); + + rec.context = ctx->name; + rec.ctx_len = ctx->len; + rec.symbol = memname; + rec.sym_len = si->len + mi->len + 1; + rec.kind = 'm'; + rec.mode = mode; + rec.file = sindex_streams[pos->stream].id; + rec.line = pos->line; + rec.col = pos->pos; + + insert_record(&rec); +} + +static void r_symdef(struct symbol *sym) +{ + r_symbol(U_DEF, &sym->pos, sym); +} + +static void r_memdef(struct symbol *sym, struct symbol *mem) +{ + r_member(U_DEF, &mem->pos, sym, mem); +} + +static void command_add(int argc, char **argv) +{ + static struct reporter reporter = { + .r_symdef = r_symdef, + .r_symbol = r_symbol, + .r_memdef = r_memdef, + .r_member = r_member, + }; + + open_temp_database(); + + sqlite_prepare_persistent( + "BEGIN IMMEDIATE", + &lock_stmt); + + sqlite_prepare_persistent( + "COMMIT", + &unlock_stmt); + + sqlite_prepare_persistent( + "INSERT OR IGNORE INTO tempdb.sindex " + "(context, symbol, kind, mode, file, line, column) " + "VALUES (@context, @symbol, @kind, @mode, @file, @line, @column)", + &insert_rec_stmt); + + sqlite_prepare_persistent( + "SELECT id, mtime FROM file WHERE name == @name", + &select_file_stmt); + + sqlite_prepare_persistent( + "INSERT INTO file (name, mtime) VALUES (@name, @mtime)", + &insert_file_stmt); + + sqlite_prepare_persistent( + "DELETE FROM file WHERE name == @name", + &delete_file_stmt); + + dissect(&reporter, sindex_filelist); + + sqlite_run(lock_stmt); + sqlite_command("INSERT OR IGNORE INTO sindex SELECT * FROM tempdb.sindex"); + sqlite_run(unlock_stmt); + + sqlite3_finalize(insert_rec_stmt); + sqlite3_finalize(select_file_stmt); + sqlite3_finalize(insert_file_stmt); + sqlite3_finalize(delete_file_stmt); + sqlite3_finalize(lock_stmt); + sqlite3_finalize(unlock_stmt); + free(sindex_streams); +} + +static void command_rm(int argc, char **argv) +{ + sqlite3_stmt *stmt; + + sqlite_command("BEGIN IMMEDIATE"); + sqlite_prepare("DELETE FROM file WHERE name GLOB @file", &stmt); + + if (sindex_verbose > 1) + message("SQL: %s", sqlite3_sql(stmt)); + + for (int i = 0; i < argc; i++) { + sqlite_bind_text(stmt, "@file", argv[i], -1); + sqlite_run(stmt); + sqlite_reset_stmt(stmt); + } + + sqlite3_finalize(stmt); + sqlite_command("COMMIT"); +} + +static inline void print_mode(char *value) +{ + char str[3]; + int v = atoi(value); + + if (v == U_DEF) { + printf("def"); + return; + } + +#define U(m) "-rwm"[(v / m) & 3] + str[0] = U(U_R_AOF); + str[1] = U(U_R_VAL); + str[2] = U(U_R_PTR); + + printf("%.3s", str); +#undef U +} + +static char *sindex_file_name; +static FILE *sindex_file_fd; +static int sindex_file_lnum; +static char *sindex_line; +static size_t sindex_line_buflen; +static int sindex_line_len; + +static void print_file_line(const char *name, int lnum) +{ + /* + * All files are sorted by name and line number. So, we can reopen + * the file and read it line by line. + */ + if (!sindex_file_name || strcmp(sindex_file_name, name)) { + if (sindex_file_fd) { + fclose(sindex_file_fd); + free(sindex_file_name); + } + + sindex_file_name = strdup(name); + + if (!sindex_file_name) + sindex_error(1, errno, "strdup"); + + sindex_file_fd = fopen(name, "r"); + + if (!sindex_file_fd) + sindex_error(1, errno, "fopen: %s", name); + + sindex_file_lnum = 0; + } + + do { + if (sindex_file_lnum == lnum) { + if (sindex_line[sindex_line_len-1] == '\n') + sindex_line_len--; + printf("%.*s", sindex_line_len, sindex_line); + break; + } + sindex_file_lnum++; + errno = 0; + } while((sindex_line_len = getline(&sindex_line, &sindex_line_buflen, sindex_file_fd)) != -1); + + if (errno && errno != EOF) + sindex_error(1, errno, "getline"); +} + +static int search_query_callback(void *data, int argc, char **argv, char **colname) +{ + char *fmt = (char *) sindex_search_format; + char buf[32]; + int quote = 0; + int n = 0; + + while (*fmt != '\0') { + char c = *fmt; + + if (quote) { + quote = 0; + switch (c) { + case 't': c = '\t'; break; + case 'r': c = '\r'; break; + case 'n': c = '\n'; break; + } + } else if (c == '%') { + int colnum = 0; + char *pos = ++fmt; + + c = *fmt; + + if (c == '\0') + sindex_error(1, 0, "unexpected end of format string"); + + switch (c) { + case 'f': colnum = 0; goto print_string; + case 'l': colnum = 1; goto print_string; + case 'c': colnum = 2; goto print_string; + case 'C': colnum = 3; goto print_string; + case 'n': colnum = 4; goto print_string; + case 'm': + if (n) { + printf("%.*s", n, buf); + n = 0; + } + print_mode(argv[5]); + fmt++; + break; + case 'k': + if (n) { + printf("%.*s", n, buf); + n = 0; + } + printf("%c", atoi(argv[6])); + fmt++; + break; + case 's': + if (n) { + printf("%.*s", n, buf); + n = 0; + } + print_file_line(argv[0], atoi(argv[1])); + fmt++; + break; + case -1: +print_string: + if (n) { + printf("%.*s", n, buf); + n = 0; + } + printf("%s", argv[colnum]); + fmt++; + break; + } + + if (pos == fmt) + sindex_error(1, 0, "invalid format specification: %%%c", c); + + continue; + } else if (c == '\\') { + quote = 1; + fmt++; + continue; + } + + if (n == sizeof(buf)) { + printf("%.*s", n, buf); + n = 0; + } + + buf[n++] = c; + fmt++; + } + + if (n) + printf("%.*s", n, buf); + printf("\n"); + + return 0; +} + +static void command_search(int argc, char **argv) +{ + char *sql; + char *dberr = NULL; + sqlite3_str *query = sqlite3_str_new(sindex_db); + + if (query_appendf(query, + "SELECT" + " file.name," + " sindex.line," + " sindex.column," + " sindex.context," + " sindex.symbol," + " sindex.mode," + " sindex.kind " + "FROM sindex, file " + "WHERE sindex.file == file.id") < 0) + goto fail; + + if (sindex_search_kind) { + if (query_appendf(query, " AND sindex.kind == %d", sindex_search_kind) < 0) + goto fail; + } + + if (sindex_search_symbol) { + int ret; + + if (query_appendf(query, " AND ") < 0) + goto fail; + + if (strpbrk(sindex_search_symbol, "*?[]")) + ret = query_appendf(query, "sindex.symbol GLOB %Q", sindex_search_symbol); + else + ret = query_appendf(query, "sindex.symbol == %Q", sindex_search_symbol); + + if (ret < 0) + goto fail; + } + + if (sindex_search_modmask_defined) { + if (!sindex_search_modmask) { + if (query_appendf(query, " AND sindex.mode == %d", sindex_search_modmask) < 0) + goto fail; + } else if (query_appendf(query, " AND (sindex.mode & %d) != 0", sindex_search_modmask) < 0) + goto fail; + } + + if (sindex_search_path) { + if (query_appendf(query, " AND file.name GLOB %Q", sindex_search_path) < 0) + goto fail; + } + + if (sindex_search_by_location == EXPLAIN_LOCATION) { + if (query_appendf(query, " AND file.name == %Q", sindex_search_filename) < 0) + goto fail; + if (sindex_search_line && + query_appendf(query, " AND sindex.line == %d", sindex_search_line) < 0) + goto fail; + if (sindex_search_column && + query_appendf(query, " AND sindex.column == %d", sindex_search_column) < 0) + goto fail; + } else if (sindex_search_by_location == USAGE_BY_LOCATION) { + if (query_appendf(query, " AND sindex.symbol IN (") < 0) + goto fail; + if (query_appendf(query, + "SELECT sindex.symbol FROM sindex, file WHERE" + " sindex.file == file.id AND" + " file.name == %Q", sindex_search_filename) < 0) + goto fail; + if (sindex_search_line && + query_appendf(query, " AND sindex.line == %d", sindex_search_line) < 0) + goto fail; + if (sindex_search_column && + query_appendf(query, " AND sindex.column == %d", sindex_search_column) < 0) + goto fail; + if (query_appendf(query, ")") < 0) + goto fail; + } + + if (query_appendf(query, " ORDER BY file.name, sindex.line, sindex.column ASC", sindex_search_path) < 0) + goto fail; + + sql = sqlite3_str_value(query); + + if (sindex_verbose > 1) + message("SQL: %s", sql); + + sqlite3_exec(sindex_db, sql, search_query_callback, NULL, &dberr); + if (dberr) + sindex_error(1, 0, "sql query failed: %s", dberr); +fail: + sql = sqlite3_str_finish(query); + sqlite3_free(sql); + + if (sindex_file_fd) { + fclose(sindex_file_fd); + free(sindex_file_name); + } + free(sindex_line); +} + + +int main(int argc, char **argv) +{ + static const struct command commands[] = { + { + .name = "add", + .dbflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + .parse_cmdline = parse_cmdline_add, + .handler = command_add + }, + { + .name = "rm", + .dbflags = SQLITE_OPEN_READWRITE, + .parse_cmdline = parse_cmdline_rm, + .handler = command_rm + }, + { + .name = "search", + .dbflags = SQLITE_OPEN_READONLY, + .parse_cmdline = parse_cmdline_search, + .handler = command_search + }, + { .name = NULL }, + }; + const struct command *cmd; + + if (!(progname = rindex(argv[0], '/'))) + progname = argv[0]; + else + progname++; + + if (!realpath(".", cwd)) + sindex_error(1, errno, "unable to get current directory"); + n_cwd = strlen(cwd); + + parse_cmdline(argc, argv); + + for (cmd = commands; cmd->name && strcmp(argv[optind], cmd->name); cmd++); + if (!cmd->name) + sindex_error(1, 0, "unknown command: %s", argv[optind]); + optind++; + + sindex_command = cmd->name; + + if (cmd->parse_cmdline) + cmd->parse_cmdline(argc, argv); + + open_database(sindex_dbfile, cmd->dbflags); + cmd->handler(argc - optind, argv + optind); + + sqlite3_close(sindex_db); + + return 0; +} @@ -192,6 +192,10 @@ static struct symbol * examine_struct_union_type(struct symbol *sym, int advance fn = advance ? lay_out_struct : lay_out_union; FOR_EACH_PTR(sym->symbol_list, member) { + if (member->ctype.base_type == &autotype_ctype) { + sparse_error(member->pos, "member '%s' has __auto_type", show_ident(member->ident)); + member->ctype.base_type = &incomplete_ctype; + } fn(member, &info); } END_FOR_EACH_PTR(member); @@ -210,6 +214,19 @@ static struct symbol *examine_base_type(struct symbol *sym) { struct symbol *base_type; + if (sym->ctype.base_type == &autotype_ctype) { + struct symbol *type = evaluate_expression(sym->initializer); + if (!type) + type = &bad_ctype; + if (is_bitfield_type(type)) { + warning(sym->pos, "__auto_type on bitfield"); + if (type->type == SYM_NODE) + type = type->ctype.base_type; + type = type->ctype.base_type; + } + sym->ctype.base_type = type; + } + /* Check the base type */ base_type = examine_symbol_type(sym->ctype.base_type); if (!base_type || base_type->type == SYM_PTR) @@ -734,6 +751,7 @@ struct symbol bool_ctype, void_ctype, type_ctype, string_ctype, ptr_ctype, lazy_ptr_ctype, incomplete_ctype, label_ctype, bad_ctype, null_ctype; +struct symbol autotype_ctype; struct symbol int_ptr_ctype, uint_ptr_ctype; struct symbol long_ptr_ctype, ulong_ptr_ctype; struct symbol llong_ptr_ctype, ullong_ptr_ctype; @@ -792,6 +810,7 @@ static const struct ctype_declare { { &void_ctype, T_BASETYPE }, { &type_ctype, T_BASETYPE }, { &incomplete_ctype, T_BASETYPE }, + { &autotype_ctype, T_BASETYPE }, { &bad_ctype, T_BASETYPE }, { &char_ctype, T__INT(-2, char) }, @@ -78,7 +78,7 @@ enum keyword { KW_MODIFIER = 1 << 1, KW_QUALIFIER = 1 << 2, KW_ATTRIBUTE = 1 << 3, - // KW UNUSED = 1 << 4, + KW_BUILTIN = 1 << 4, KW_ASM = 1 << 5, KW_MODE = 1 << 6, // KW UNUSED = 1 << 7, @@ -110,13 +110,18 @@ struct decl_state { unsigned long f_modifiers; // function attributes unsigned char prefer_abstract, is_inline, storage_class, is_tls; unsigned char is_ext_visible; + unsigned char autotype; }; +struct pseudo; +struct entrypoint; + struct symbol_op { enum keyword type; int (*evaluate)(struct expression *); int (*expand)(struct expression *, int); int (*args)(struct expression *); + struct pseudo *(*linearize)(struct entrypoint *, struct expression *); /* keywords */ struct token *(*declarator)(struct token *token, struct decl_state *ctx); @@ -277,6 +282,7 @@ extern struct symbol bool_ctype, void_ctype, type_ctype, string_ctype, ptr_ctype, lazy_ptr_ctype, incomplete_ctype, label_ctype, bad_ctype, null_ctype; +extern struct symbol autotype_ctype; extern struct symbol int_ptr_ctype, uint_ptr_ctype; extern struct symbol long_ptr_ctype, ulong_ptr_ctype; extern struct symbol llong_ptr_ctype, ullong_ptr_ctype; @@ -308,6 +314,7 @@ extern struct symbol *lookup_symbol(struct ident *, enum namespace); extern struct symbol *create_symbol(int stream, const char *name, int type, int namespace); extern void init_symbols(void); extern void init_builtins(int stream); +extern void init_linearized_builtins(int stream); extern void declare_builtins(void); extern void init_ctype(void); extern struct symbol *alloc_symbol(struct position, int type); diff --git a/test-dissect.c b/test-dissect.c index e725eec5..58b3e633 100644 --- a/test-dissect.c +++ b/test-dissect.c @@ -1,18 +1,5 @@ #include "dissect.h" -static unsigned dotc_stream; - -static inline char storage(struct symbol *sym) -{ - int t = sym->type; - unsigned m = sym->ctype.modifiers; - - if (m & MOD_INLINE || t == SYM_STRUCT || t == SYM_UNION /*|| t == SYM_ENUM*/) - return sym->pos.stream == dotc_stream ? 's' : 'g'; - - return (m & MOD_STATIC) ? 's' : (m & MOD_NONLOCAL) ? 'g' : 'l'; -} - static inline const char *show_mode(unsigned mode) { static char str[3]; @@ -32,14 +19,30 @@ static inline const char *show_mode(unsigned mode) static void print_usage(struct position *pos, struct symbol *sym, unsigned mode) { static unsigned curr_stream = -1; + static struct ident null; + struct ident *ctx = &null; if (curr_stream != pos->stream) { curr_stream = pos->stream; printf("\nFILE: %s\n\n", stream_name(curr_stream)); } - printf("%4d:%-3d %c %-5.3s", - pos->line, pos->pos, storage(sym), show_mode(mode)); + if (dissect_ctx) + ctx = dissect_ctx->ident; + + printf("%4d:%-3d %-16.*s %s ", + pos->line, pos->pos, ctx->len, ctx->name, show_mode(mode)); + +} + +static char symscope(struct symbol *sym) +{ + if (sym_is_local(sym)) { + if (!dissect_ctx) + warning(sym->pos, "no context"); + return '.'; + } + return ' '; } static void r_symbol(unsigned mode, struct position *pos, struct symbol *sym) @@ -49,25 +52,52 @@ static void r_symbol(unsigned mode, struct position *pos, struct symbol *sym) if (!sym->ident) sym->ident = built_in_ident("__asm__"); - printf("%-32.*s %s\n", - sym->ident->len, sym->ident->name, + printf("%c %c %-32.*s %s\n", + symscope(sym), sym->kind, sym->ident->len, sym->ident->name, show_typename(sym->ctype.base_type)); + + switch (sym->kind) { + case 's': + if (sym->type == SYM_STRUCT || sym->type == SYM_UNION) + break; + goto err; + + case 'f': + if (sym->type != SYM_BAD && sym->ctype.base_type->type != SYM_FN) + goto err; + case 'v': + if (sym->type == SYM_NODE || sym->type == SYM_BAD) + break; + goto err; + default: + goto err; + } + + return; +err: + warning(*pos, "r_symbol bad sym type=%d kind=%d", sym->type, sym->kind); } static void r_member(unsigned mode, struct position *pos, struct symbol *sym, struct symbol *mem) { - struct ident *si, *mi; + struct ident *ni, *si, *mi; print_usage(pos, sym, mode); - si = sym->ident ?: built_in_ident("?"); + ni = built_in_ident("?"); + si = sym->ident ?: ni; /* mem == NULL means entire struct accessed */ - mi = mem ? mem->ident : built_in_ident("*"); + mi = mem ? (mem->ident ?: ni) : built_in_ident("*"); - printf("%.*s.%-*.*s %s\n", - si->len, si->name, + printf("%c m %.*s.%-*.*s %s\n", + symscope(sym), si->len, si->name, 32-1 - si->len, mi->len, mi->name, show_typename(mem ? mem->ctype.base_type : sym)); + + if (sym->ident && sym->kind != 's') + warning(*pos, "r_member bad sym type=%d kind=%d", sym->type, sym->kind); + if (mem && mem->kind != 'm') + warning(*pos, "r_member bad mem->kind = %d", mem->kind); } static void r_symdef(struct symbol *sym) @@ -88,15 +118,10 @@ int main(int argc, char **argv) .r_symbol = r_symbol, .r_member = r_member, }; - struct string_list *filelist = NULL; - char *file; + struct string_list *filelist = NULL; sparse_initialize(argc, argv, &filelist); - - FOR_EACH_PTR(filelist, file) { - dotc_stream = input_stream_nr; - dissect(__sparse(file), &reporter); - } END_FOR_EACH_PTR(file); + dissect(&reporter, filelist); return 0; } diff --git a/validation/autotype-ko.c b/validation/autotype-ko.c new file mode 100644 index 00000000..5b6cd708 --- /dev/null +++ b/validation/autotype-ko.c @@ -0,0 +1,45 @@ +__auto_type u; // KO: no initializer +__auto_type r[2] = { 0, 1 }; // KO: not a plain identifier +__auto_type foo(void) { } // KO: not a plain identifier +__auto_type v = 0, w = 1; // KO: in list +struct { __auto_type x; } s; // KO: not valid for struct/union +__auto_type self = self; // KO: self-declared +__auto_type undc = this; // KO: undeclared + +int i = 1; +double f = 1.0; +__auto_type i = 2; // KO: redecl, same type +__auto_type f = 2.0f; // KO: redecl, diff type + + +static int foo(int a, const int *ptr) +{ + __auto_type i = a; + __auto_type c = *ptr; + + c += 1; + return i; +} + +/* + * check-name: autotype-ko + * check-command: sparse -Wno-decl $file + * + * check-error-start +autotype-ko.c:1:13: error: __auto_type without initializer +autotype-ko.c:2:13: error: __auto_type on non-identifier +autotype-ko.c:3:13: error: 'foo()' has __auto_type return type +autotype-ko.c:4:13: error: __auto_type on declaration list +autotype-ko.c:6:13: error: __auto_type on self-init var +autotype-ko.c:2:20: error: invalid initializer +autotype-ko.c:5:22: error: member 'x' has __auto_type +autotype-ko.c:7:20: error: undefined identifier 'this' +autotype-ko.c:11:13: error: symbol 'i' has multiple initializers (originally initialized at autotype-ko.c:9) +autotype-ko.c:12:13: error: symbol 'f' has multiple initializers (originally initialized at autotype-ko.c:10) +autotype-ko.c:12:13: error: symbol 'f' redeclared with different type (different type sizes): +autotype-ko.c:12:13: float [addressable] [toplevel] f +autotype-ko.c:10:8: note: previously declared as: +autotype-ko.c:10:8: double [addressable] [toplevel] f +autotype-ko.c:20:9: error: assignment to const expression + * check-error-end + */ diff --git a/validation/autotype.c b/validation/autotype.c new file mode 100644 index 00000000..98683c93 --- /dev/null +++ b/validation/autotype.c @@ -0,0 +1,55 @@ +#ifdef __CHECKER__ +#define is_type(X, T) _Static_assert([typeof(X)] == [T], "") +#else +#define is_type(X, T) _Static_assert(1, "") +#endif + +struct s { + int x; + int bf:3; +}; + +extern char ch; +extern const int ci; + +__auto_type i = 0; is_type(i, int); +__auto_type m = 1UL; is_type(m, unsigned long); +__auto_type l = (int)0L; is_type(l, int); +__auto_type c = (char)'\n'; is_type(c, char); +__auto_type p = &i; is_type(p, int *); +__auto_type f = 0.0; is_type(f, double); +__auto_type s = (struct s){0}; is_type(s, struct s); +__auto_type pci = &ci; is_type(pci, const int *); + +// ~~: not valid for bitfield +__auto_type b = (struct s){0}.bf; is_type(b, int); + +static __auto_type si = 0; is_type(si, int); +const __auto_type ci = 0; is_type(ci, const int); +__auto_type ch = (char) '\n'; is_type(ch, char); + +static int foo(int a) +{ + __auto_type i = a; is_type(i, int); + __auto_type c = ch; is_type(c, char); + __auto_type ct = ci; is_type(&ct, const int *); + + return ct += i + c; +} + + + +#define __as __attribute__((address_space(42))) +extern int __as aa; + +__auto_type pa = &aa; is_type(pa, int __as *); + +/* + * check-name: autotype + * check-command: sparse -Wno-decl $file + * + * check-error-start +autotype.c:25:13: warning: __auto_type on bitfield +autotype.c:37:16: error: assignment to const expression + * check-error-end + */ diff --git a/validation/context-unreachable.c b/validation/context-unreachable.c new file mode 100644 index 00000000..8664962e --- /dev/null +++ b/validation/context-unreachable.c @@ -0,0 +1,15 @@ +int fun(void); + +static void foo(void) +{ + __context__(1); + if (!fun()) { + __builtin_unreachable(); + return; + } + __context__(-1); +} + +/* + * check-name: context-unreachable + */ diff --git a/validation/expand/builtin_constant_inline0.c b/validation/expand/builtin_constant_inline0.c new file mode 100644 index 00000000..a0057f20 --- /dev/null +++ b/validation/expand/builtin_constant_inline0.c @@ -0,0 +1,23 @@ +static inline int is_const(long size) +{ + return __builtin_constant_p(size) ? size : 0; +} + +int foo(void) +{ + return is_const(42); +} + +/* + * check-name: builtin_constant_inline0 + * check-command: test-linearize -Wno-decl $file + * + * check-output-start +foo: +.L0: + <entry-point> + ret.32 $42 + + + * check-output-end + */ diff --git a/validation/expand/builtin_constant_inline1.c b/validation/expand/builtin_constant_inline1.c new file mode 100644 index 00000000..da08f960 --- /dev/null +++ b/validation/expand/builtin_constant_inline1.c @@ -0,0 +1,24 @@ +static inline void fun(void) { } + +#define EXPR ({ fun(); 42; }) + +int bar(void) +{ + // GCC doesn't consider EXPR as a constant + return __builtin_constant_p(EXPR); +} + +/* + * check-name: builtin_constant_inline1 + * check-command: test-linearize -Wno-decl $file + * check-known-to-fail + * + * check-output-start +bar: +.L0: + <entry-point> + ret.32 $0 + + + * check-output-end + */ diff --git a/validation/inline_base0.c b/validation/inline_base0.c new file mode 100644 index 00000000..517ee972 --- /dev/null +++ b/validation/inline_base0.c @@ -0,0 +1,47 @@ +static inline int add(int a, int b) +{ + return a + b; +} + +int foo0(int x, int y) +{ + return add(x, y); +} + +int foo1(int x) +{ + return add(x, 1); +} + +int foo2(void) +{ + return add(1, 2); +} + +/* + * check-name: inline_base0 + * check-command: test-linearize -Wno-decl $file + * + * check-output-start +foo0: +.L0: + <entry-point> + add.32 %r5 <- %arg1, %arg2 + ret.32 %r5 + + +foo1: +.L3: + <entry-point> + add.32 %r10 <- %arg1, $1 + ret.32 %r10 + + +foo2: +.L6: + <entry-point> + ret.32 $3 + + + * check-output-end + */ diff --git a/validation/inline_self.c b/validation/inline_self.c new file mode 100644 index 00000000..14c2b0b2 --- /dev/null +++ b/validation/inline_self.c @@ -0,0 +1,13 @@ +static inline void foo(void) +{ + foo(); +} + +static void baz(void) +{ + foo(); +} + +/* + * check-name: inline_self + */ diff --git a/validation/linear/builtin_unreachable0.c b/validation/linear/builtin_unreachable0.c new file mode 100644 index 00000000..911ed7f9 --- /dev/null +++ b/validation/linear/builtin_unreachable0.c @@ -0,0 +1,27 @@ +int foo(int p) +{ + if (p == 3) + __builtin_unreachable(); + return p; +} + +/* + * check-name: builtin_unreachable0 + * check-command: test-linearize -Wno-decl $file + * + * check-output-start +foo: +.L0: + <entry-point> + seteq.32 %r2 <- %arg1, $3 + cbr %r2, .L1, .L3 + +.L1: + unreachable + +.L3: + ret.32 %arg1 + + + * check-output-end + */ diff --git a/validation/linear/builtin_unreachable.c b/validation/linear/builtin_unreachable1.c index 4f13b892..70f6674c 100644 --- a/validation/linear/builtin_unreachable.c +++ b/validation/linear/builtin_unreachable1.c @@ -1,31 +1,30 @@ -void function_that_never_returns(void); +void die(void); int foo(int c) { if (c) return 1; - function_that_never_returns(); + die(); __builtin_unreachable(); } /* - * check-name: __builtin_unreachable() + * check-name: builtin_unreachable1 * check-command: test-linearize -Wno-decl $file * - * check-known-to-fail * check-output-start foo: .L0: <entry-point> cbr %arg1, .L3, .L2 -.L2: - call function_that_never_returns - unreach - .L3: ret.32 $1 +.L2: + call die + unreachable + * check-output-end */ diff --git a/validation/linear/noreturn-unreachable0.c b/validation/linear/noreturn-unreachable0.c new file mode 100644 index 00000000..9bcd605f --- /dev/null +++ b/validation/linear/noreturn-unreachable0.c @@ -0,0 +1,22 @@ +extern void die(void) __attribute__((noreturn)); + +int foo(void) +{ + die(); + return 0; +} + +/* + * check-name: noreturn-unreachable0 + * check-command: test-linearize -Wno-decl $file + * + * check-output-start +foo: +.L0: + <entry-point> + call die + unreachable + + + * check-output-end + */ diff --git a/validation/preprocessor/directive-within-macro.c b/validation/preprocessor/directive-within-macro.c new file mode 100644 index 00000000..4562078a --- /dev/null +++ b/validation/preprocessor/directive-within-macro.c @@ -0,0 +1,36 @@ +#define f(x) x + +f(1 +#if 1 // OK + a +#elif 2 // OK + b +#else // OK + c +#endif // OK +#ifdef f // OK + d +#endif // OK +#ifndef f // OK + e +#endif // OK + 3) + +f(1 +#define x y // KO + 3) + +/* + * check-name: directive-within-macro + * check-command: sparse -E $file + * + * check-output-start + +1 a d 3 +1 3 + * check-output-end + * + * check-error-start +preprocessor/directive-within-macro.c:20:1: warning: directive in macro's argument list + * check-error-end + */ diff --git a/validation/preprocessor/expand-and-nl.c b/validation/preprocessor/expand-and-nl.c new file mode 100644 index 00000000..1a5f2265 --- /dev/null +++ b/validation/preprocessor/expand-and-nl.c @@ -0,0 +1,13 @@ +#define M(X) X-X + +M(a +b) +/* + * check-name: expand-and-nl + * check-command: sparse -E $file + * + * check-output-start + +a b-a b + * check-output-end + */ diff --git a/validation/preprocessor/expand-redef.c b/validation/preprocessor/expand-redef.c new file mode 100644 index 00000000..0ccf0d4c --- /dev/null +++ b/validation/preprocessor/expand-redef.c @@ -0,0 +1,20 @@ +#define f(x) x x +f(1 +#undef f +#define f 2 + f) + +/* + * check-name: expand-redef + * check-command: sparse -E $file + * + * check-output-start + +1 2 1 2 + * check-output-end + * + * check-error-start +preprocessor/expand-redef.c:3:1: warning: directive in macro's argument list +preprocessor/expand-redef.c:4:1: warning: directive in macro's argument list + * check-error-end + */ diff --git a/validation/preprocessor/preprocessor22.c b/validation/preprocessor/preprocessor22.c index fb28daaa..0d13c5f8 100644 --- a/validation/preprocessor/preprocessor22.c +++ b/validation/preprocessor/preprocessor22.c @@ -17,19 +17,17 @@ define_struct(a, { * check-description: Directives are not allowed within a macro argument list, * although cpp deals with it to treat macro more like C functions. * - * check-command: sparse -E $file + * check-command: sparse -pedantic -E $file * * check-error-start -preprocessor/preprocessor22.c:6:1: error: directive in macro's argument list -preprocessor/preprocessor22.c:8:1: error: directive in macro's argument list -preprocessor/preprocessor22.c:10:1: error: directive in macro's argument list -preprocessor/preprocessor22.c:12:1: error: directive in macro's argument list +preprocessor/preprocessor22.c:6:1: warning: directive in macro's argument list +preprocessor/preprocessor22.c:8:1: warning: directive in macro's argument list +preprocessor/preprocessor22.c:10:1: warning: directive in macro's argument list +preprocessor/preprocessor22.c:12:1: warning: directive in macro's argument list * check-error-end * * check-output-start -struct { -int b; -} a;; +struct { int b; } a;; * check-output-end */ |