• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * semind - semantic indexer for C.
3  *
4  * Copyright (C) 2020  Alexey Gladkov
5  */
6 
7 #define _GNU_SOURCE
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 
11 #include <unistd.h>
12 #include <limits.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <getopt.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <sqlite3.h>
20 
21 #include "dissect.h"
22 
23 #define U_DEF (0x100 << U_SHIFT)
24 #define SINDEX_DATABASE_VERSION 1
25 
26 #define message(fmt, ...) semind_error(0, 0, (fmt), ##__VA_ARGS__)
27 
28 static const char *progname;
29 static const char *semind_command = NULL;
30 
31 // common options
32 static const char *semind_dbfile = "semind.sqlite";
33 static int semind_verbose = 0;
34 static char cwd[PATH_MAX];
35 static size_t n_cwd;
36 
37 // 'add' command options
38 static struct string_list *semind_filelist = NULL;
39 static int semind_include_local_syms = 0;
40 
41 struct semind_streams {
42 	sqlite3_int64 id;
43 };
44 
45 static struct semind_streams *semind_streams = NULL;
46 static int semind_streams_nr = 0;
47 
48 // 'search' command options
49 static int semind_search_modmask;
50 static int semind_search_modmask_defined = 0;
51 static int semind_search_kind = 0;
52 static char *semind_search_path = NULL;
53 static char *semind_search_symbol = NULL;
54 static const char *semind_search_format = "(%m) %f\t%l\t%c\t%C\t%s";
55 
56 #define EXPLAIN_LOCATION 1
57 #define USAGE_BY_LOCATION 2
58 static int semind_search_by_location;
59 static char *semind_search_filename;
60 static int semind_search_line;
61 static int semind_search_column;
62 
63 static sqlite3 *semind_db = NULL;
64 static sqlite3_stmt *lock_stmt = NULL;
65 static sqlite3_stmt *unlock_stmt = NULL;
66 static sqlite3_stmt *insert_rec_stmt = NULL;
67 static sqlite3_stmt *select_file_stmt = NULL;
68 static sqlite3_stmt *insert_file_stmt = NULL;
69 static sqlite3_stmt *delete_file_stmt = NULL;
70 
71 struct command {
72 	const char *name;
73 	int dbflags;
74 	void (*parse_cmdline)(int argc, char **argv);
75 	void (*handler)(int argc, char **argv);
76 };
77 
show_usage(void)78 static void show_usage(void)
79 {
80 	if (semind_command)
81 		printf("Try '%s %s --help' for more information.\n",
82 		       progname, semind_command);
83 	else
84 		printf("Try '%s --help' for more information.\n",
85 		       progname);
86 	exit(1);
87 }
88 
show_help(int ret)89 static void show_help(int ret)
90 {
91 	printf(
92 	    "Usage: %1$s [options]\n"
93 	    "   or: %1$s [options] add    [command options] [--] [compiler options] [files...]\n"
94 	    "   or: %1$s [options] rm     [command options] pattern\n"
95 	    "   or: %1$s [options] search [command options] pattern\n"
96 	    "\n"
97 	    "These are common %1$s commands used in various situations:\n"
98 	    "  add      Generate or updates semantic index file for c-source code;\n"
99 	    "  rm       Remove files from the index by pattern;\n"
100 	    "  search   Make index queries.\n"
101 	    "\n"
102 	    "Options:\n"
103 	    "  -D, --database=FILE    Specify database file (default: %2$s);\n"
104 	    "  -B, --basedir=DIR      Define project top directory (default is the current directory);\n"
105 	    "  -v, --verbose          Show information about what is being done;\n"
106 	    "  -h, --help             Show this text and exit.\n"
107 	    "\n"
108 	    "Environment:\n"
109 	    "  SINDEX_DATABASE        Database file location.\n"
110 	    "  SINDEX_BASEDIR         Project top directory.\n"
111 	    "\n"
112 	    "Report bugs to authors.\n"
113 	    "\n",
114 	    progname, semind_dbfile);
115 	exit(ret);
116 }
117 
show_help_add(int ret)118 static void show_help_add(int ret)
119 {
120 	printf(
121 	    "Usage: %1$s add [options] [--] [compiler options] files...\n"
122 	    "\n"
123 	    "Utility creates or updates a symbol index.\n"
124 	    "\n"
125 	    "Options:\n"
126 	    "  --include-local-syms   Include into the index local symbols;\n"
127 	    "  -v, --verbose          Show information about what is being done;\n"
128 	    "  -h, --help             Show this text and exit.\n"
129 	    "\n"
130 	    "Report bugs to authors.\n"
131 	    "\n",
132 	    progname);
133 	exit(ret);
134 
135 }
136 
show_help_rm(int ret)137 static void show_help_rm(int ret)
138 {
139 	printf(
140 	    "Usage: %1$s rm [options] pattern\n"
141 	    "\n"
142 	    "Utility removes source files from the index.\n"
143 	    "The pattern is a glob(7) wildcard pattern.\n"
144 	    "\n"
145 	    "Options:\n"
146 	    "  -v, --verbose          Show information about what is being done;\n"
147 	    "  -h, --help             Show this text and exit.\n"
148 	    "\n"
149 	    "Report bugs to authors.\n"
150 	    "\n",
151 	    progname);
152 	exit(ret);
153 }
154 
show_help_search(int ret)155 static void show_help_search(int ret)
156 {
157 	printf(
158 	    "Usage: %1$s search [options] [pattern]\n"
159 	    "   or: %1$s search [options] (-e|-l) filename[:linenr[:column]]\n"
160 	    "\n"
161 	    "Utility searches information about symbol by pattern.\n"
162 	    "The pattern is a glob(7) wildcard pattern.\n"
163 	    "\n"
164 	    "Options:\n"
165 	    "  -f, --format=STRING    Specify an output format;\n"
166 	    "  -p, --path=PATTERN     Search symbols only in specified directories;\n"
167 	    "  -m, --mode=MODE        Search only the specified type of access;\n"
168 	    "  -k, --kind=KIND        Specify a kind of symbol;\n"
169 	    "  -e, --explain          Show what happens in the specified file position;\n"
170 	    "  -l, --location         Show usage of symbols from a specific file position;\n"
171 	    "  -v, --verbose          Show information about what is being done;\n"
172 	    "  -h, --help             Show this text and exit.\n"
173 	    "\n"
174 	    "The KIND can be one of the following: `s', `f', `v', `m'.\n"
175 	    "\n"
176 	    "Report bugs to authors.\n"
177 	    "\n",
178 	    progname);
179 	exit(ret);
180 }
181 
semind_print_progname(void)182 static void semind_print_progname(void)
183 {
184 	fprintf(stderr, "%s: ", progname);
185 	if (semind_command)
186 		fprintf(stderr, "%s: ", semind_command);
187 }
188 
semind_error(int status,int errnum,const char * fmt,...)189 static void semind_error(int status, int errnum, const char *fmt, ...)
190 {
191 	va_list ap;
192 	semind_print_progname();
193 
194 	va_start(ap, fmt);
195 	vfprintf(stderr, fmt, ap);
196 	va_end(ap);
197 
198 	if (errnum > 0)
199 		fprintf(stderr, ": %s", strerror(errnum));
200 
201 	fprintf(stderr, "\n");
202 
203 	if (status)
204 		exit(status);
205 }
206 
set_search_modmask(const char * v)207 static void set_search_modmask(const char *v)
208 {
209 	size_t n = strlen(v);
210 
211 	if (n != 1 && n != 3)
212 		semind_error(1, 0, "the length of mode value must be 1 or 3: %s", v);
213 
214 	semind_search_modmask_defined = 1;
215 	semind_search_modmask = 0;
216 
217 	if (n == 1) {
218 		switch (v[0]) {
219 			case 'r': v = "rrr"; break;
220 			case 'w': v = "ww-"; break;
221 			case 'm': v = "mmm"; break;
222 			case '-': v = "---"; break;
223 			default: semind_error(1, 0, "unknown modificator: %s", v);
224 		}
225 	} else if (!strcmp(v, "def")) {
226 		semind_search_modmask = U_DEF;
227 		return;
228 	}
229 
230 	static const int modes[] = {
231 		U_R_AOF, U_W_AOF, U_R_AOF | U_W_AOF,
232 		U_R_VAL, U_W_VAL, U_R_VAL | U_W_VAL,
233 		U_R_PTR, U_W_PTR, U_R_PTR | U_W_PTR,
234 	};
235 
236 	for (int i = 0; i < 3; i++) {
237 		switch (v[i]) {
238 			case 'r': semind_search_modmask |= modes[i * 3];     break;
239 			case 'w': semind_search_modmask |= modes[i * 3 + 1]; break;
240 			case 'm': semind_search_modmask |= modes[i * 3 + 2]; break;
241 			case '-': break;
242 			default:  semind_error(1, 0,
243 			                "unknown modificator in the mode value"
244 			                " (`r', `w', `m' or `-' expected): %c", v[i]);
245 		}
246 	}
247 }
248 
parse_cmdline(int argc,char ** argv)249 static void parse_cmdline(int argc, char **argv)
250 {
251 	static const struct option long_options[] = {
252 		{ "database", required_argument, NULL, 'D' },
253 		{ "basedir", required_argument, NULL, 'B' },
254 		{ "verbose", no_argument, NULL, 'v' },
255 		{ "help", no_argument, NULL, 'h' },
256 		{ NULL }
257 	};
258 	int c;
259 	char *basedir = getenv("SINDEX_BASEDIR");
260 	char *env;
261 
262 	if ((env = getenv("SINDEX_DATABASE")) != NULL)
263 		semind_dbfile = env;
264 
265 	while ((c = getopt_long(argc, argv, "+B:D:vh", long_options, NULL)) != -1) {
266 		switch (c) {
267 			case 'D':
268 				semind_dbfile = optarg;
269 				break;
270 			case 'B':
271 				basedir = optarg;
272 				break;
273 			case 'v':
274 				semind_verbose++;
275 				break;
276 			case 'h':
277 				show_help(0);
278 		}
279 	}
280 
281 	if (optind == argc) {
282 		message("command required");
283 		show_usage();
284 	}
285 
286 	if (basedir) {
287 		if (!realpath(basedir, cwd))
288 			semind_error(1, errno, "unable to get project base directory");
289 		n_cwd = strlen(cwd);
290 	}
291 }
292 
parse_cmdline_add(int argc,char ** argv)293 static void parse_cmdline_add(int argc, char **argv)
294 {
295 	static const struct option long_options[] = {
296 		{ "include-local-syms", no_argument, NULL, 1 },
297 		{ "verbose", no_argument, NULL, 'v' },
298 		{ "help", no_argument, NULL, 'h' },
299 		{ NULL }
300 	};
301 	int c;
302 
303 	opterr = 0;
304 
305 	while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) {
306 		switch (c) {
307 			case 1:
308 				semind_include_local_syms = 1;
309 				break;
310 			case 'v':
311 				semind_verbose++;
312 				break;
313 			case 'h':
314 				show_help_add(0);
315 			case '?':
316 				goto done;
317 		}
318 	}
319 done:
320 	if (optind == argc) {
321 		message("more arguments required");
322 		show_usage();
323 	}
324 
325 	// enforce tabstop
326 	tabstop = 1;
327 
328 	// step back since sparse_initialize will ignore argv[0].
329 	optind--;
330 
331 	sparse_initialize(argc - optind, argv + optind, &semind_filelist);
332 }
333 
parse_cmdline_rm(int argc,char ** argv)334 static void parse_cmdline_rm(int argc, char **argv)
335 {
336 	static const struct option long_options[] = {
337 		{ "verbose", no_argument, NULL, 'v' },
338 		{ "help", no_argument, NULL, 'h' },
339 		{ NULL }
340 	};
341 	int c;
342 
343 	while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) {
344 		switch (c) {
345 			case 'v':
346 				semind_verbose++;
347 				break;
348 			case 'h':
349 				show_help_rm(0);
350 		}
351 	}
352 
353 	if (optind == argc) {
354 		message("more arguments required");
355 		show_usage();
356 	}
357 }
358 
parse_cmdline_search(int argc,char ** argv)359 static void parse_cmdline_search(int argc, char **argv)
360 {
361 	static const struct option long_options[] = {
362 		{ "explain", no_argument, NULL, 'e' },
363 		{ "format", required_argument, NULL, 'f' },
364 		{ "path", required_argument, NULL, 'p' },
365 		{ "location", no_argument, NULL, 'l' },
366 		{ "mode", required_argument, NULL, 'm' },
367 		{ "kind", required_argument, NULL, 'k' },
368 		{ "verbose", no_argument, NULL, 'v' },
369 		{ "help", no_argument, NULL, 'h' },
370 		{ NULL }
371 	};
372 	int c;
373 
374 	while ((c = getopt_long(argc, argv, "+ef:m:k:p:lvh", long_options, NULL)) != -1) {
375 		switch (c) {
376 			case 'e':
377 				semind_search_by_location = EXPLAIN_LOCATION;
378 				break;
379 			case 'l':
380 				semind_search_by_location = USAGE_BY_LOCATION;
381 				break;
382 			case 'f':
383 				semind_search_format = optarg;
384 				break;
385 			case 'm':
386 				set_search_modmask(optarg);
387 				break;
388 			case 'k':
389 				semind_search_kind = tolower(optarg[0]);
390 				break;
391 			case 'p':
392 				semind_search_path = optarg;
393 				break;
394 			case 'v':
395 				semind_verbose++;
396 				break;
397 			case 'h':
398 				show_help_search(0);
399 		}
400 	}
401 
402 	if (semind_search_by_location) {
403 		char *str;
404 
405 		if (optind == argc)
406 			semind_error(1, 0, "one argument required");
407 
408 		str = argv[optind];
409 
410 		while (str) {
411 			char *ptr;
412 
413 			if ((ptr = strchr(str, ':')) != NULL)
414 				*ptr++ = '\0';
415 
416 			if (*str != '\0') {
417 				if (!semind_search_filename) {
418 					semind_search_filename = str;
419 				} else if (!semind_search_line) {
420 					semind_search_line = atoi(str);
421 				} else if (!semind_search_column) {
422 					semind_search_column = atoi(str);
423 				}
424 			}
425 			str = ptr;
426 		}
427 	} else if (optind < argc)
428 		semind_search_symbol = argv[optind++];
429 }
430 
query_appendf(sqlite3_str * query,const char * fmt,...)431 static int query_appendf(sqlite3_str *query, const char *fmt, ...)
432 {
433 	int status;
434 	va_list args;
435 
436 	va_start(args, fmt);
437 	sqlite3_str_vappendf(query, fmt, args);
438 	va_end(args);
439 
440 	if ((status = sqlite3_str_errcode(query)) == SQLITE_OK)
441 		return 0;
442 
443 	if (status == SQLITE_NOMEM)
444 		message("not enough memory");
445 
446 	if (status == SQLITE_TOOBIG)
447 		message("string too big");
448 
449 	return -1;
450 }
451 
sqlite_bind_text(sqlite3_stmt * stmt,const char * field,const char * var,int len)452 static inline void sqlite_bind_text(sqlite3_stmt *stmt, const char *field, const char *var, int len)
453 {
454 	if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, field), var, len, SQLITE_STATIC) != SQLITE_OK)
455 		semind_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(semind_db));
456 }
457 
sqlite_bind_int64(sqlite3_stmt * stmt,const char * field,long long var)458 static inline void sqlite_bind_int64(sqlite3_stmt *stmt, const char *field, long long var)
459 {
460 	if (sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, field), var) != SQLITE_OK)
461 		semind_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(semind_db));
462 }
463 
sqlite_prepare(const char * sql,sqlite3_stmt ** stmt)464 static inline void sqlite_prepare(const char *sql, sqlite3_stmt **stmt)
465 {
466 	int ret;
467 	do {
468 		ret = sqlite3_prepare_v2(semind_db, sql, -1, stmt, NULL);
469 		if (ret != SQLITE_OK && ret != SQLITE_BUSY)
470 			semind_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(semind_db), sql);
471 	} while (ret == SQLITE_BUSY);
472 }
473 
sqlite_prepare_persistent(const char * sql,sqlite3_stmt ** stmt)474 static inline void sqlite_prepare_persistent(const char *sql, sqlite3_stmt **stmt)
475 {
476 	int ret;
477 	do {
478 		ret = sqlite3_prepare_v3(semind_db, sql, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL);
479 		if (ret != SQLITE_OK && ret != SQLITE_BUSY)
480 			semind_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(semind_db), sql);
481 	} while (ret == SQLITE_BUSY);
482 }
483 
sqlite_reset_stmt(sqlite3_stmt * stmt)484 static inline void sqlite_reset_stmt(sqlite3_stmt *stmt)
485 {
486 	// Contrary to the intuition of many, sqlite3_reset() does not reset the
487 	// bindings on a prepared statement. Use this routine to reset all host
488 	// parameters to NULL.
489 	sqlite3_clear_bindings(stmt);
490 	sqlite3_reset(stmt);
491 }
492 
sqlite_run(sqlite3_stmt * stmt)493 static int sqlite_run(sqlite3_stmt *stmt)
494 {
495 	int ret = sqlite3_step(stmt);
496 	if (ret != SQLITE_DONE && ret != SQLITE_ROW)
497 		semind_error(1, 0, "unable to process query: %s: %s", sqlite3_errmsg(semind_db), sqlite3_sql(stmt));
498 	return ret;
499 }
500 
sqlite_command(const char * sql)501 static void sqlite_command(const char *sql)
502 {
503 	sqlite3_stmt *stmt;
504 	sqlite_prepare(sql, &stmt);
505 	sqlite_run(stmt);
506 	sqlite3_finalize(stmt);
507 }
508 
get_db_version(void)509 static sqlite3_int64 get_db_version(void)
510 {
511 	sqlite3_stmt *stmt;
512 	sqlite3_int64 dbversion;
513 
514 	sqlite_prepare("PRAGMA user_version", &stmt);
515 	sqlite_run(stmt);
516 	dbversion = sqlite3_column_int64(stmt, 0);
517 	sqlite3_finalize(stmt);
518 
519 	return dbversion;
520 }
521 
set_db_version(void)522 static void set_db_version(void)
523 {
524 	char *sql;
525 	sqlite3_str *query = sqlite3_str_new(semind_db);
526 
527 	if (query_appendf(query, "PRAGMA user_version = %d", SINDEX_DATABASE_VERSION) < 0)
528 		exit(1);
529 
530 	sql = sqlite3_str_finish(query);
531 	sqlite_command(sql);
532 	sqlite3_free(sql);
533 }
534 
open_temp_database(void)535 static void open_temp_database(void)
536 {
537 	static const char *database_schema[] = {
538 		"ATTACH ':memory:' AS tempdb",
539 		"CREATE TABLE tempdb.semind ("
540 			" file INTEGER NOT NULL,"
541 			" line INTEGER NOT NULL,"
542 			" column INTEGER NOT NULL,"
543 			" symbol TEXT NOT NULL,"
544 			" kind INTEGER NOT NULL,"
545 			" context TEXT,"
546 			" mode INTEGER NOT NULL"
547 		")",
548 		NULL,
549 	};
550 
551 	for (int i = 0; database_schema[i]; i++)
552 		sqlite_command(database_schema[i]);
553 }
554 
open_database(const char * filename,int flags)555 static void open_database(const char *filename, int flags)
556 {
557 	static const char *database_schema[] = {
558 		"CREATE TABLE file ("
559 			" id INTEGER PRIMARY KEY AUTOINCREMENT,"
560 			" name TEXT UNIQUE NOT NULL,"
561 			" mtime INTEGER NOT NULL"
562 		")",
563 		"CREATE TABLE semind ("
564 			" file INTEGER NOT NULL REFERENCES file(id) ON DELETE CASCADE,"
565 			" line INTEGER NOT NULL,"
566 			" column INTEGER NOT NULL,"
567 			" symbol TEXT NOT NULL,"
568 			" kind INTEGER NOT NULL,"
569 			" context TEXT,"
570 			" mode INTEGER NOT NULL"
571 		")",
572 		"CREATE UNIQUE INDEX semind_0 ON semind (symbol, kind, mode, file, line, column)",
573 		"CREATE INDEX semind_1 ON semind (file)",
574 		NULL,
575 	};
576 
577 	int exists = !access(filename, R_OK);
578 
579 	if (sqlite3_open_v2(filename, &semind_db, flags, NULL) != SQLITE_OK)
580 		semind_error(1, 0, "unable to open database: %s: %s", filename, sqlite3_errmsg(semind_db));
581 
582 	sqlite_command("PRAGMA journal_mode = WAL");
583 	sqlite_command("PRAGMA synchronous = OFF");
584 	sqlite_command("PRAGMA secure_delete = FAST");
585 	sqlite_command("PRAGMA busy_timeout = 2147483647");
586 	sqlite_command("PRAGMA foreign_keys = ON");
587 
588 	if (exists) {
589 		if (get_db_version() < SINDEX_DATABASE_VERSION)
590 			semind_error(1, 0, "%s: Database too old. Please rebuild it.", filename);
591 		return;
592 	}
593 
594 	set_db_version();
595 
596 	for (int i = 0; database_schema[i]; i++)
597 		sqlite_command(database_schema[i]);
598 }
599 
600 struct index_record {
601 	const char *context;
602 	int ctx_len;
603 
604 	const char *symbol;
605 	int sym_len;
606 
607 	int kind;
608 	unsigned int mode;
609 	long long mtime;
610 	sqlite3_int64 file;
611 	int line;
612 	int col;
613 };
614 
insert_record(struct index_record * rec)615 static void insert_record(struct index_record *rec)
616 {
617 	sqlite_bind_text(insert_rec_stmt,  "@context", rec->context, rec->ctx_len);
618 	sqlite_bind_text(insert_rec_stmt,  "@symbol",  rec->symbol, rec->sym_len);
619 	sqlite_bind_int64(insert_rec_stmt, "@kind",    rec->kind);
620 	sqlite_bind_int64(insert_rec_stmt, "@mode",    rec->mode);
621 	sqlite_bind_int64(insert_rec_stmt, "@file",    rec->file);
622 	sqlite_bind_int64(insert_rec_stmt, "@line",    rec->line);
623 	sqlite_bind_int64(insert_rec_stmt, "@column",  rec->col);
624 	sqlite_run(insert_rec_stmt);
625 	sqlite_reset_stmt(insert_rec_stmt);
626 }
627 
update_stream(void)628 static void update_stream(void)
629 {
630 	if (semind_streams_nr >= input_stream_nr)
631 		return;
632 
633 	semind_streams = realloc(semind_streams, input_stream_nr * sizeof(struct semind_streams));
634 	if (!semind_streams)
635 		semind_error(1, errno, "realloc");
636 
637 	sqlite_run(lock_stmt);
638 
639 	for (int i = semind_streams_nr; i < input_stream_nr; i++) {
640 		struct stat st;
641 		const char *filename;
642 		char fullname[PATH_MAX];
643 		sqlite3_int64 cur_mtime = 0;
644 
645 		if (input_streams[i].fd != -1) {
646 			/*
647 			 * FIXME: Files in the input_streams may be duplicated.
648 			 */
649 			if (stat(input_streams[i].name, &st) < 0)
650 				semind_error(1, errno, "stat: %s", input_streams[i].name);
651 
652 			cur_mtime = st.st_mtime;
653 
654 			if (!realpath(input_streams[i].name, fullname))
655 				semind_error(1, errno, "realpath: %s", input_streams[i].name);
656 
657 			if (!strncmp(fullname, cwd, n_cwd) && fullname[n_cwd] == '/') {
658 				filename = fullname + n_cwd + 1;
659 				semind_streams[i].id = 0;
660 			} else {
661 				semind_streams[i].id = -1;
662 				continue;
663 			}
664 		} else {
665 			semind_streams[i].id = -1;
666 			continue;
667 		}
668 
669 		if (semind_verbose > 1)
670 			message("filename: %s", filename);
671 
672 		sqlite_bind_text(select_file_stmt, "@name", filename, -1);
673 
674 		if (sqlite_run(select_file_stmt) == SQLITE_ROW) {
675 			sqlite3_int64 old_mtime;
676 
677 			semind_streams[i].id = sqlite3_column_int64(select_file_stmt, 0);
678 			old_mtime = sqlite3_column_int64(select_file_stmt, 1);
679 
680 			sqlite_reset_stmt(select_file_stmt);
681 
682 			if (cur_mtime == old_mtime)
683 				continue;
684 
685 			sqlite_bind_text(delete_file_stmt, "@name", filename, -1);
686 			sqlite_run(delete_file_stmt);
687 			sqlite_reset_stmt(delete_file_stmt);
688 		}
689 
690 		sqlite_reset_stmt(select_file_stmt);
691 
692 		sqlite_bind_text(insert_file_stmt,  "@name",  filename, -1);
693 		sqlite_bind_int64(insert_file_stmt, "@mtime", cur_mtime);
694 		sqlite_run(insert_file_stmt);
695 		sqlite_reset_stmt(insert_file_stmt);
696 
697 		semind_streams[i].id = sqlite3_last_insert_rowid(semind_db);
698 	}
699 
700 	sqlite_run(unlock_stmt);
701 
702 	semind_streams_nr = input_stream_nr;
703 }
704 
r_symbol(unsigned mode,struct position * pos,struct symbol * sym)705 static void r_symbol(unsigned mode, struct position *pos, struct symbol *sym)
706 {
707 	static struct ident null;
708 	struct ident *ctx = &null;
709 	struct index_record rec;
710 
711 	update_stream();
712 
713 	if (semind_streams[pos->stream].id == -1)
714 		return;
715 
716 	if (!semind_include_local_syms && sym_is_local(sym))
717 		return;
718 
719 	if (!sym->ident) {
720 		warning(*pos, "empty ident");
721 		return;
722 	}
723 
724 	if (dissect_ctx)
725 		ctx = dissect_ctx->ident;
726 
727 	rec.context = ctx->name;
728 	rec.ctx_len = ctx->len;
729 	rec.symbol  = sym->ident->name;
730 	rec.sym_len = sym->ident->len;
731 	rec.kind    = sym->kind;
732 	rec.mode    = mode;
733 	rec.file    = semind_streams[pos->stream].id;
734 	rec.line    = pos->line;
735 	rec.col     = pos->pos;
736 
737 	insert_record(&rec);
738 }
739 
r_member(unsigned mode,struct position * pos,struct symbol * sym,struct symbol * mem)740 static void r_member(unsigned mode, struct position *pos, struct symbol *sym, struct symbol *mem)
741 {
742 	static struct ident null;
743 	static char memname[1024];
744 	struct ident *ni, *si, *mi;
745 	struct ident *ctx = &null;
746 	struct index_record rec;
747 
748 	update_stream();
749 
750 	if (semind_streams[pos->stream].id == -1)
751 		return;
752 
753 	if (!semind_include_local_syms && sym_is_local(sym))
754 		return;
755 
756 	ni = built_in_ident("?");
757 	si = sym->ident ?: ni;
758 	/* mem == NULL means entire struct accessed */
759 	mi = mem ? (mem->ident ?: ni) : built_in_ident("*");
760 
761 	if (dissect_ctx)
762 		ctx = dissect_ctx->ident;
763 
764 	snprintf(memname, sizeof(memname), "%.*s.%.*s", si->len, si->name, mi->len, mi->name);
765 
766 	rec.context = ctx->name;
767 	rec.ctx_len = ctx->len;
768 	rec.symbol  = memname;
769 	rec.sym_len = si->len + mi->len + 1;
770 	rec.kind    = 'm';
771 	rec.mode    = mode;
772 	rec.file    = semind_streams[pos->stream].id;
773 	rec.line    = pos->line;
774 	rec.col     = pos->pos;
775 
776 	insert_record(&rec);
777 }
778 
r_symdef(struct symbol * sym)779 static void r_symdef(struct symbol *sym)
780 {
781 	r_symbol(U_DEF, &sym->pos, sym);
782 }
783 
r_memdef(struct symbol * sym,struct symbol * mem)784 static void r_memdef(struct symbol *sym, struct symbol *mem)
785 {
786 	r_member(U_DEF, &mem->pos, sym, mem);
787 }
788 
command_add(int argc,char ** argv)789 static void command_add(int argc, char **argv)
790 {
791 	static struct reporter reporter = {
792 		.r_symdef = r_symdef,
793 		.r_symbol = r_symbol,
794 		.r_memdef = r_memdef,
795 		.r_member = r_member,
796 	};
797 
798 	open_temp_database();
799 
800 	sqlite_prepare_persistent(
801 		"BEGIN IMMEDIATE",
802 		&lock_stmt);
803 
804 	sqlite_prepare_persistent(
805 		"COMMIT",
806 		&unlock_stmt);
807 
808 	sqlite_prepare_persistent(
809 		"INSERT OR IGNORE INTO tempdb.semind "
810 		"(context, symbol, kind, mode, file, line, column) "
811 		"VALUES (@context, @symbol, @kind, @mode, @file, @line, @column)",
812 		&insert_rec_stmt);
813 
814 	sqlite_prepare_persistent(
815 		"SELECT id, mtime FROM file WHERE name == @name",
816 		&select_file_stmt);
817 
818 	sqlite_prepare_persistent(
819 		"INSERT INTO file (name, mtime) VALUES (@name, @mtime)",
820 		&insert_file_stmt);
821 
822 	sqlite_prepare_persistent(
823 		"DELETE FROM file WHERE name == @name",
824 		&delete_file_stmt);
825 
826 	dissect(&reporter, semind_filelist);
827 
828 	sqlite_run(lock_stmt);
829 	sqlite_command("INSERT OR IGNORE INTO semind SELECT * FROM tempdb.semind");
830 	sqlite_run(unlock_stmt);
831 
832 	sqlite3_finalize(insert_rec_stmt);
833 	sqlite3_finalize(select_file_stmt);
834 	sqlite3_finalize(insert_file_stmt);
835 	sqlite3_finalize(delete_file_stmt);
836 	sqlite3_finalize(lock_stmt);
837 	sqlite3_finalize(unlock_stmt);
838 	free(semind_streams);
839 }
840 
command_rm(int argc,char ** argv)841 static void command_rm(int argc, char **argv)
842 {
843 	sqlite3_stmt *stmt;
844 
845 	sqlite_command("BEGIN IMMEDIATE");
846 	sqlite_prepare("DELETE FROM file WHERE name GLOB @file", &stmt);
847 
848 	if (semind_verbose > 1)
849 		message("SQL: %s", sqlite3_sql(stmt));
850 
851 	for (int i = 0; i < argc; i++) {
852 		sqlite_bind_text(stmt, "@file",  argv[i], -1);
853 		sqlite_run(stmt);
854 		sqlite_reset_stmt(stmt);
855 	}
856 
857 	sqlite3_finalize(stmt);
858 	sqlite_command("COMMIT");
859 }
860 
print_mode(char * value)861 static inline void print_mode(char *value)
862 {
863 	char str[3];
864 	int v = atoi(value);
865 
866 	if (v == U_DEF) {
867 		printf("def");
868 		return;
869 	}
870 
871 #define U(m) "-rwm"[(v / m) & 3]
872 	str[0] = U(U_R_AOF);
873 	str[1] = U(U_R_VAL);
874 	str[2] = U(U_R_PTR);
875 
876 	printf("%.3s", str);
877 #undef U
878 }
879 
880 static char *semind_file_name;
881 static FILE *semind_file_fd;
882 static int semind_file_lnum;
883 static char *semind_line;
884 static size_t semind_line_buflen;
885 static int semind_line_len;
886 
print_file_line(const char * name,int lnum)887 static void print_file_line(const char *name, int lnum)
888 {
889 	/*
890 	 * All files are sorted by name and line number. So, we can reopen
891 	 * the file and read it line by line.
892 	 */
893 	if (!semind_file_name || strcmp(semind_file_name, name)) {
894 		if (semind_file_fd) {
895 			fclose(semind_file_fd);
896 			free(semind_file_name);
897 		}
898 
899 		semind_file_name = strdup(name);
900 
901 		if (!semind_file_name)
902 			semind_error(1, errno, "strdup");
903 
904 		semind_file_fd = fopen(name, "r");
905 
906 		if (!semind_file_fd)
907 			semind_error(1, errno, "fopen: %s", name);
908 
909 		semind_file_lnum = 0;
910 	}
911 
912 	do {
913 		if (semind_file_lnum == lnum) {
914 			if (semind_line[semind_line_len-1] == '\n')
915 				semind_line_len--;
916 			printf("%.*s", semind_line_len, semind_line);
917 			break;
918 		}
919 		semind_file_lnum++;
920 		errno = 0;
921 	} while((semind_line_len = getline(&semind_line, &semind_line_buflen, semind_file_fd)) != -1);
922 
923 	if (errno && errno != EOF)
924 		semind_error(1, errno, "getline");
925 }
926 
search_query_callback(void * data,int argc,char ** argv,char ** colname)927 static int search_query_callback(void *data, int argc, char **argv, char **colname)
928 {
929 	char *fmt = (char *) semind_search_format;
930 	char buf[32];
931 	int quote = 0;
932 	int n = 0;
933 
934 	while (*fmt != '\0') {
935 		char c = *fmt;
936 
937 		if (quote) {
938 			quote = 0;
939 			switch (c) {
940 				case 't': c = '\t'; break;
941 				case 'r': c = '\r'; break;
942 				case 'n': c = '\n'; break;
943 			}
944 		} else if (c == '%') {
945 			int colnum = 0;
946 			char *pos = ++fmt;
947 
948 			c = *fmt;
949 
950 			if (c == '\0')
951 				semind_error(1, 0, "unexpected end of format string");
952 
953 			switch (c) {
954 				case 'f': colnum = 0; goto print_string;
955 				case 'l': colnum = 1; goto print_string;
956 				case 'c': colnum = 2; goto print_string;
957 				case 'C': colnum = 3; goto print_string;
958 				case 'n': colnum = 4; goto print_string;
959 				case 'm':
960 					if (n) {
961 						printf("%.*s", n, buf);
962 						n = 0;
963 					}
964 					print_mode(argv[5]);
965 					fmt++;
966 					break;
967 				case 'k':
968 					if (n) {
969 						printf("%.*s", n, buf);
970 						n = 0;
971 					}
972 					printf("%c", atoi(argv[6]));
973 					fmt++;
974 					break;
975 				case 's':
976 					if (n) {
977 						printf("%.*s", n, buf);
978 						n = 0;
979 					}
980 					print_file_line(argv[0], atoi(argv[1]));
981 					fmt++;
982 					break;
983 
984 				print_string:
985 					if (n) {
986 						printf("%.*s", n, buf);
987 						n = 0;
988 					}
989 					printf("%s", argv[colnum]);
990 					fmt++;
991 					break;
992 				default:
993 					break;
994 
995 			}
996 
997 			if (pos == fmt)
998 				semind_error(1, 0, "invalid format specification: %%%c", c);
999 
1000 			continue;
1001 		} else if (c == '\\') {
1002 			quote = 1;
1003 			fmt++;
1004 			continue;
1005 		}
1006 
1007 		if (n == sizeof(buf)) {
1008 			printf("%.*s", n, buf);
1009 			n = 0;
1010 		}
1011 
1012 		buf[n++] = c;
1013 		fmt++;
1014 	}
1015 
1016 	if (n)
1017 		printf("%.*s", n, buf);
1018 	printf("\n");
1019 
1020 	return 0;
1021 }
1022 
command_search(int argc,char ** argv)1023 static void command_search(int argc, char **argv)
1024 {
1025 	char *sql;
1026 	char *dberr = NULL;
1027 	sqlite3_str *query = sqlite3_str_new(semind_db);
1028 
1029 	if (chdir(cwd) < 0)
1030 		semind_error(1, errno, "unable to change directory: %s", cwd);
1031 
1032 	if (query_appendf(query,
1033 	                  "SELECT"
1034 	                  " file.name,"
1035 	                  " semind.line,"
1036 	                  " semind.column,"
1037 	                  " semind.context,"
1038 	                  " semind.symbol,"
1039 	                  " semind.mode,"
1040 	                  " semind.kind "
1041 	                  "FROM semind, file "
1042 	                  "WHERE semind.file == file.id") < 0)
1043 		goto fail;
1044 
1045 	if (semind_search_kind) {
1046 		if (query_appendf(query, " AND semind.kind == %d", semind_search_kind) < 0)
1047 			goto fail;
1048 	}
1049 
1050 	if (semind_search_symbol) {
1051 		int ret;
1052 
1053 		if (query_appendf(query, " AND ") < 0)
1054 			goto fail;
1055 
1056 		if (strpbrk(semind_search_symbol, "*?[]"))
1057 			ret = query_appendf(query, "semind.symbol GLOB %Q", semind_search_symbol);
1058 		else
1059 			ret = query_appendf(query, "semind.symbol == %Q", semind_search_symbol);
1060 
1061 		if (ret < 0)
1062 			goto fail;
1063 	}
1064 
1065 	if (semind_search_modmask_defined) {
1066 		if (!semind_search_modmask) {
1067 			if (query_appendf(query, " AND semind.mode == %d", semind_search_modmask) < 0)
1068 				goto fail;
1069 		} else if (query_appendf(query, " AND (semind.mode & %d) != 0", semind_search_modmask) < 0)
1070 			goto fail;
1071 	}
1072 
1073 	if (semind_search_path) {
1074 		if (query_appendf(query, " AND file.name GLOB %Q", semind_search_path) < 0)
1075 			goto fail;
1076 	}
1077 
1078 	if (semind_search_by_location == EXPLAIN_LOCATION) {
1079 		if (query_appendf(query, " AND file.name == %Q", semind_search_filename) < 0)
1080 			goto fail;
1081 		if (semind_search_line &&
1082 		    query_appendf(query, " AND semind.line == %d", semind_search_line) < 0)
1083 			goto fail;
1084 		if (semind_search_column &&
1085 		    query_appendf(query, " AND semind.column == %d", semind_search_column) < 0)
1086 			goto fail;
1087 	} else if (semind_search_by_location == USAGE_BY_LOCATION) {
1088 		if (query_appendf(query, " AND semind.symbol IN (") < 0)
1089 			goto fail;
1090 		if (query_appendf(query,
1091 		                 "SELECT semind.symbol FROM semind, file WHERE"
1092 				 " semind.file == file.id AND"
1093 		                 " file.name == %Q", semind_search_filename) < 0)
1094 			goto fail;
1095 		if (semind_search_line &&
1096 		    query_appendf(query, " AND semind.line == %d", semind_search_line) < 0)
1097 			goto fail;
1098 		if (semind_search_column &&
1099 		    query_appendf(query, " AND semind.column == %d", semind_search_column) < 0)
1100 			goto fail;
1101 		if (query_appendf(query, ")") < 0)
1102 			goto fail;
1103 	}
1104 
1105 	if (query_appendf(query, " ORDER BY file.name, semind.line, semind.column ASC", semind_search_path) < 0)
1106 		goto fail;
1107 
1108 	sql = sqlite3_str_value(query);
1109 
1110 	if (semind_verbose > 1)
1111 		message("SQL: %s", sql);
1112 
1113 	sqlite3_exec(semind_db, sql, search_query_callback, NULL, &dberr);
1114 	if (dberr)
1115 		semind_error(1, 0, "sql query failed: %s", dberr);
1116 fail:
1117 	sql = sqlite3_str_finish(query);
1118 	sqlite3_free(sql);
1119 
1120 	if (semind_file_fd) {
1121 		fclose(semind_file_fd);
1122 		free(semind_file_name);
1123 	}
1124 	free(semind_line);
1125 }
1126 
1127 
main(int argc,char ** argv)1128 int main(int argc, char **argv)
1129 {
1130 	static const struct command commands[] = {
1131 		{
1132 			.name          = "add",
1133 			.dbflags       = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
1134 			.parse_cmdline = parse_cmdline_add,
1135 			.handler       = command_add
1136 		},
1137 		{
1138 			.name          = "rm",
1139 			.dbflags       = SQLITE_OPEN_READWRITE,
1140 			.parse_cmdline = parse_cmdline_rm,
1141 			.handler       = command_rm
1142 		},
1143 		{
1144 			.name          = "search",
1145 			.dbflags       = SQLITE_OPEN_READONLY,
1146 			.parse_cmdline = parse_cmdline_search,
1147 			.handler       = command_search
1148 		},
1149 		{ .name = NULL },
1150 	};
1151 	const struct command *cmd;
1152 
1153 	if (!(progname = rindex(argv[0], '/')))
1154 		progname = argv[0];
1155 	else
1156 		progname++;
1157 
1158 	if (!realpath(".", cwd))
1159 		semind_error(1, errno, "unable to get current directory");
1160 	n_cwd = strlen(cwd);
1161 
1162 	parse_cmdline(argc, argv);
1163 
1164 	for (cmd = commands; cmd->name && strcmp(argv[optind], cmd->name); cmd++);
1165 	if (!cmd->name)
1166 		semind_error(1, 0, "unknown command: %s", argv[optind]);
1167 	optind++;
1168 
1169 	semind_command = cmd->name;
1170 
1171 	if (cmd->parse_cmdline)
1172 		cmd->parse_cmdline(argc, argv);
1173 
1174 	open_database(semind_dbfile, cmd->dbflags);
1175 	cmd->handler(argc - optind, argv + optind);
1176 
1177 	sqlite3_close(semind_db);
1178 
1179 	return 0;
1180 }
1181