]> git.ozlabs.org Git - ccan/blob - tools/ccanlint/file_analysis.c
ccanlint: run tests in alphabetical order
[ccan] / tools / ccanlint / file_analysis.c
1 #include "ccanlint.h"
2 #include <ccan/talloc/talloc.h>
3 #include <ccan/str/str.h>
4 #include <ccan/str_talloc/str_talloc.h>
5 #include <ccan/grab_file/grab_file.h>
6 #include <ccan/noerr/noerr.h>
7 #include <ccan/foreach/foreach.h>
8 #include <ccan/asort/asort.h>
9 #include "../tools.h"
10 #include <unistd.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <err.h>
15 #include <errno.h>
16 #include <dirent.h>
17 #include <ctype.h>
18 #include <stdarg.h>
19 #include <assert.h>
20
21 const char *ccan_dir;
22
23 const char *get_ccan_file_contents(struct ccan_file *f)
24 {
25         if (!f->contents) {
26                 f->contents = grab_file(f, f->fullname, &f->contents_size);
27                 if (!f->contents)
28                         err(1, "Reading file %s", f->fullname);
29         }
30         return f->contents;
31 }
32
33 char **get_ccan_file_lines(struct ccan_file *f)
34 {
35         if (!f->lines)
36                 f->lines = strsplit(f, get_ccan_file_contents(f),
37                                     "\n", &f->num_lines);
38
39         return f->lines;
40 }
41
42 struct list_head *get_ccan_file_docs(struct ccan_file *f)
43 {
44         if (!f->doc_sections) {
45                 get_ccan_file_lines(f);
46                 f->doc_sections = extract_doc_sections(f->lines, f->num_lines);
47         }
48         return f->doc_sections;
49 }
50
51 struct ccan_file *new_ccan_file(const void *ctx, const char *dir, char *name)
52 {
53         struct ccan_file *f;
54
55         assert(dir[0] == '/');
56
57         f = talloc(ctx, struct ccan_file);
58         f->lines = NULL;
59         f->line_info = NULL;
60         f->doc_sections = NULL;
61         f->compiled = NULL;
62         f->name = talloc_steal(f, name);
63         f->fullname = talloc_asprintf(f, "%s/%s", dir, f->name);
64         f->contents = NULL;
65         f->cov_compiled = NULL;
66         return f;
67 }
68
69 static void add_files(struct manifest *m, const char *dir)
70 {
71         DIR *d;
72         struct dirent *ent;
73
74         if (dir[0])
75                 d = opendir(dir);
76         else
77                 d = opendir(".");
78         if (!d)
79                 err(1, "Opening directory %s", dir[0] ? dir : ".");
80
81         while ((ent = readdir(d)) != NULL) {
82                 struct stat st;
83                 struct ccan_file *f;
84                 struct list_head *dest;
85                 bool is_c_src;
86
87                 if (ent->d_name[0] == '.')
88                         continue;
89
90                 f = new_ccan_file(m, m->dir,
91                                   talloc_asprintf(m, "%s%s",
92                                                   dir, ent->d_name));
93                 if (lstat(f->name, &st) != 0)
94                         err(1, "lstat %s", f->name);
95
96                 if (S_ISDIR(st.st_mode)) {
97                         f->name = talloc_append_string(f->name, "/");
98                         add_files(m, f->name);
99                         continue;
100                 }
101                 if (!S_ISREG(st.st_mode)) {
102                         talloc_free(f);
103                         continue;
104                 }
105
106                 if (streq(f->name, "_info")) {
107                         m->info_file = f;
108                         continue;
109                 }
110
111                 is_c_src = strends(f->name, ".c");
112                 if (!is_c_src && !strends(f->name, ".h")) {
113                         dest = &m->other_files;
114                         continue;
115                 }
116
117                 if (!strchr(f->name, '/')) {
118                         if (is_c_src)
119                                 dest = &m->c_files;
120                         else
121                                 dest = &m->h_files;
122                 } else if (strstarts(f->name, "test/")) {
123                         if (is_c_src) {
124                                 if (strstarts(f->name, "test/api"))
125                                         dest = &m->api_tests;
126                                 else if (strstarts(f->name, "test/run"))
127                                         dest = &m->run_tests;
128                                 else if (strstarts(f->name, "test/compile_ok"))
129                                         dest = &m->compile_ok_tests;
130                                 else if (strstarts(f->name, "test/compile_fail"))
131                                         dest = &m->compile_fail_tests;
132                                 else
133                                         dest = &m->other_test_c_files;
134                         } else
135                                 dest = &m->other_test_files;
136                 } else
137                         dest = &m->other_files;
138
139                 list_add(dest, &f->list);
140         }
141         closedir(d);
142 }
143
144 static int cmp_names(struct ccan_file *const *a, struct ccan_file *const *b,
145                      void *unused)
146 {
147         return strcmp((*a)->name, (*b)->name);
148 }
149
150 static void sort_files(struct list_head *list)
151 {
152         struct ccan_file **files = NULL, *f;
153         unsigned int i, num;
154
155         num = 0;
156         while ((f = list_top(list, struct ccan_file, list)) != NULL) {
157                 files = talloc_realloc(NULL, files, struct ccan_file *, num+1);
158                 files[num++] = f;
159                 list_del(&f->list);
160         }
161         asort(files, num, cmp_names, NULL);
162
163         for (i = 0; i < num; i++)
164                 list_add_tail(list, &files[i]->list);
165         talloc_free(files);
166 }
167
168 struct manifest *get_manifest(const void *ctx, const char *dir)
169 {
170         struct manifest *m = talloc(ctx, struct manifest);
171         char *olddir;
172         unsigned int len;
173         struct list_head *list;
174
175         m->info_file = NULL;
176         list_head_init(&m->c_files);
177         list_head_init(&m->h_files);
178         list_head_init(&m->api_tests);
179         list_head_init(&m->run_tests);
180         list_head_init(&m->compile_ok_tests);
181         list_head_init(&m->compile_fail_tests);
182         list_head_init(&m->other_test_c_files);
183         list_head_init(&m->other_test_files);
184         list_head_init(&m->other_files);
185         list_head_init(&m->examples);
186         list_head_init(&m->mangled_examples);
187         list_head_init(&m->dep_dirs);
188
189         olddir = talloc_getcwd(NULL);
190         if (!olddir)
191                 err(1, "Getting current directory");
192
193         if (chdir(dir) != 0)
194                 err(1, "Failed to chdir to %s", dir);
195
196         m->dir = talloc_getcwd(m);
197         if (!m->dir)
198                 err(1, "Getting current directory");
199
200         len = strlen(m->dir);
201         while (len && m->dir[len-1] == '/')
202                 m->dir[--len] = '\0';
203
204         m->basename = strrchr(m->dir, '/');
205         if (!m->basename)
206                 errx(1, "I don't expect to be run from the root directory");
207         m->basename++;
208
209         /* We expect the ccan dir to be two levels above module dir. */
210         if (!ccan_dir) {
211                 char *p;
212                 ccan_dir = talloc_strdup(NULL, m->dir);
213                 p = strrchr(ccan_dir, '/');
214                 *p = '\0';
215                 p = strrchr(ccan_dir, '/');
216                 *p = '\0';
217         }
218
219         add_files(m, "");
220
221         /* Nicer to run tests in a predictable order. */
222         foreach_ptr(list, &m->api_tests, &m->run_tests, &m->compile_ok_tests,
223                     &m->compile_fail_tests)
224                 sort_files(list);
225
226         if (chdir(olddir) != 0)
227                 err(1, "Returning to original directory '%s'", olddir);
228         talloc_free(olddir);
229
230         return m;
231 }
232
233
234 /**
235  * remove_comments - strip comments from a line, return copy.
236  * @line: line to copy
237  * @in_comment: are we already within a comment (from prev line).
238  * @unterminated: are we still in a comment for next line.
239  */
240 static char *remove_comments(const char *line, bool in_comment,
241                              bool *unterminated)
242 {
243         char *p, *ret = talloc_array(line, char, strlen(line) + 1);
244
245         p = ret;
246         for (;;) {
247                 if (!in_comment) {
248                         /* Find first comment. */
249                         const char *old_comment = strstr(line, "/*");
250                         const char *new_comment = strstr(line, "//");
251                         const char *comment;
252
253                         if (new_comment && old_comment)
254                                 comment = new_comment < old_comment
255                                         ? new_comment : old_comment;
256                         else if (old_comment)
257                                 comment = old_comment;
258                         else if (new_comment)
259                                 comment = new_comment;
260                         else {
261                                 /* Nothing more. */
262                                 strcpy(p, line);
263                                 *unterminated = false;
264                                 break;
265                         }
266
267                         /* Copy up to comment. */
268                         memcpy(p, line, comment - line);
269                         p += comment - line;
270                         line += comment - line + 2;
271
272                         if (comment == new_comment) {
273                                 /* We're done: goes to EOL. */
274                                 p[0] = '\0';
275                                 *unterminated = false;
276                                 break;
277                         }
278                         in_comment = true;
279                 }
280
281                 if (in_comment) {
282                         const char *end = strstr(line, "*/");
283                         if (!end) {
284                                 *unterminated = true;
285                                 p[0] = '\0';
286                                 break;
287                         }
288                         line = end+2;
289                         in_comment = false;
290                 }
291         }
292         return ret;
293 }
294
295 static bool is_empty(const char *line)
296 {
297         return strspn(line, " \t") == strlen(line);
298 }
299
300 static bool continues(const char *line)
301 {
302         /* Technically, any odd number of these.  But who cares? */
303         return strends(line, "\\");
304 }
305
306 /* Get token if it's equal to token. */
307 bool get_token(const char **line, const char *token)
308 {
309         unsigned int toklen;
310
311         *line += strspn(*line, " \t");
312         if (isalnum(token[0]) || token[0] == '_')
313                 toklen = strspn(*line, IDENT_CHARS);
314         else {
315                 /* FIXME: real tokenizer handles ++ and other multi-chars.  */
316                 toklen = strlen(token);
317         }
318
319         if (toklen == strlen(token) && !strncmp(*line, token, toklen)) {
320                 *line += toklen;
321                 return true;
322         }
323         return false;
324 }
325
326 char *get_symbol_token(void *ctx, const char **line)
327 {
328         unsigned int toklen;
329         char *ret;
330
331         *line += strspn(*line, " \t");
332         toklen = strspn(*line, IDENT_CHARS);
333         if (!toklen)
334                 return NULL;
335         ret = talloc_strndup(ctx, *line, toklen);
336         *line += toklen;
337         return ret;
338 }
339
340 static bool parse_hash_if(struct pp_conditions *cond, const char **line)
341 {
342         bool brackets, defined;
343
344         cond->inverse = get_token(line, "!");
345         defined = get_token(line, "defined");
346         brackets = get_token(line, "(");
347         cond->symbol = get_symbol_token(cond, line);
348         if (!cond->symbol)
349                 return false;
350         if (brackets && !get_token(line, ")"))
351                 return false;
352         if (!defined)
353                 cond->type = PP_COND_IF;
354         return true;
355 }
356
357 /* FIXME: Get serious! */
358 static struct pp_conditions *analyze_directive(struct ccan_file *f,
359                                                const char *line,
360                                                struct pp_conditions *parent)
361 {
362         struct pp_conditions *cond = talloc(f, struct pp_conditions);
363         bool unused;
364
365         line = remove_comments(line, false, &unused);
366
367         cond->parent = parent;
368         cond->type = PP_COND_IFDEF;
369
370         if (!get_token(&line, "#"))
371                 abort();
372
373         if (get_token(&line, "if")) {
374                 if (!parse_hash_if(cond, &line))
375                         goto unknown;
376         } else if (get_token(&line, "elif")) {
377                 /* Malformed? */
378                 if (!parent)
379                         return NULL;
380                 cond->parent = parent->parent;
381                 /* FIXME: Not quite true.  This implies !parent, but we don't
382                  * do multiple conditionals yet. */
383                 if (!parse_hash_if(cond, &line))
384                         goto unknown;
385         } else if (get_token(&line, "ifdef")) {
386                 bool brackets;
387                 cond->inverse = false;
388                 brackets = get_token(&line, "(");
389                 cond->symbol = get_symbol_token(cond, &line);
390                 if (!cond->symbol)
391                         goto unknown;
392                 if (brackets && !get_token(&line, ")"))
393                         goto unknown;
394         } else if (get_token(&line, "ifndef")) {
395                 bool brackets;
396                 cond->inverse = true;
397                 brackets = get_token(&line, "(");
398                 cond->symbol = get_symbol_token(cond, &line);
399                 if (!cond->symbol)
400                         goto unknown;
401                 if (brackets && !get_token(&line, ")"))
402                         goto unknown;
403         } else if (get_token(&line, "else")) {
404                 /* Malformed? */
405                 if (!parent)
406                         return NULL;
407
408                 *cond = *parent;
409                 cond->inverse = !cond->inverse;
410                 return cond;
411         } else if (get_token(&line, "endif")) {
412                 talloc_free(cond);
413                 /* Malformed? */
414                 if (!parent)
415                         return NULL;
416                 /* Back up one! */
417                 return parent->parent;
418         } else {
419                 /* Not a conditional. */
420                 talloc_free(cond);
421                 return parent;
422         }
423
424         if (!is_empty(line))
425                 goto unknown;
426         return cond;
427
428 unknown:
429         cond->type = PP_COND_UNKNOWN;
430         return cond;
431 }
432
433 /* This parser is rough, but OK if code is reasonably neat. */
434 struct line_info *get_ccan_line_info(struct ccan_file *f)
435 {
436         bool continued = false, in_comment = false;
437         struct pp_conditions *cond = NULL;
438         unsigned int i;
439
440         if (f->line_info)
441                 return f->line_info;
442
443         get_ccan_file_lines(f);
444         f->line_info = talloc_array(f->lines, struct line_info, f->num_lines);
445
446         for (i = 0; i < f->num_lines; continued = continues(f->lines[i++])) {
447                 char *p;
448                 bool still_doc_line;
449
450                 /* Current conditions apply to this line. */
451                 f->line_info[i].cond = cond;
452                 f->line_info[i].continued = continued;
453
454                 if (continued) {
455                         /* Same as last line. */
456                         f->line_info[i].type = f->line_info[i-1].type;
457                         /* Update in_comment. */
458                         remove_comments(f->lines[i], in_comment, &in_comment);
459                         continue;
460                 }
461
462                 /* Preprocessor directive? */
463                 if (!in_comment
464                     && f->lines[i][strspn(f->lines[i], " \t")] == '#') {
465                         f->line_info[i].type = PREPROC_LINE;
466                         cond = analyze_directive(f, f->lines[i], cond);
467                         continue;
468                 }
469
470                 still_doc_line = (in_comment
471                                   && f->line_info[i-1].type == DOC_LINE);
472
473                 p = remove_comments(f->lines[i], in_comment, &in_comment);
474                 if (is_empty(p)) {
475                         if (strstarts(f->lines[i], "/**") || still_doc_line)
476                                 f->line_info[i].type = DOC_LINE;
477                         else
478                                 f->line_info[i].type = COMMENT_LINE;
479                 } else
480                         f->line_info[i].type = CODE_LINE;
481                 talloc_free(p);
482         }
483         return f->line_info;
484 }
485
486 struct symbol {
487         struct list_node list;
488         const char *name;
489         const unsigned int *value;
490 };
491
492 static struct symbol *find_symbol(struct list_head *syms, const char *sym)
493 {
494         struct symbol *i;
495
496         list_for_each(syms, i, list)
497                 if (streq(sym, i->name))
498                         return i;
499         return NULL;
500 }
501
502 static enum line_compiled get_pp(struct pp_conditions *cond,
503                                  struct list_head *syms)
504 {
505         struct symbol *sym;
506         unsigned int val;
507         enum line_compiled parent, ret;
508
509         /* No conditions?  Easy. */
510         if (!cond)
511                 return COMPILED;
512
513         /* Check we get here at all. */
514         parent = get_pp(cond->parent, syms);
515         if (parent == NOT_COMPILED)
516                 return NOT_COMPILED;
517
518         if (cond->type == PP_COND_UNKNOWN)
519                 return MAYBE_COMPILED;
520
521         sym = find_symbol(syms, cond->symbol);
522         if (!sym)
523                 return MAYBE_COMPILED;
524
525         switch (cond->type) {
526         case PP_COND_IF:
527                 /* Undefined is 0. */
528                 val = sym->value ? *sym->value : 0;
529                 if (!val == cond->inverse)
530                         ret = COMPILED;
531                 else
532                         ret = NOT_COMPILED;
533                 break;
534
535         case PP_COND_IFDEF:
536                 if (cond->inverse == !sym->value)
537                         ret = COMPILED;
538                 else
539                         ret = NOT_COMPILED;
540                 break;
541
542         default:
543                 abort();
544         }
545
546         /* If parent didn't know, NO == NO, but YES == MAYBE. */
547         if (parent == MAYBE_COMPILED && ret == COMPILED)
548                 ret = MAYBE_COMPILED;
549         return ret;
550 }
551
552 static void add_symbol(struct list_head *head,
553                        const char *symbol, const unsigned int *value)
554 {
555         struct symbol *sym = talloc(head, struct symbol);
556         sym->name = symbol;
557         sym->value = value;
558         list_add(head, &sym->list);
559 }
560         
561 enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
562                                     const char *symbol,
563                                     const unsigned int *value,
564                                     ...)
565 {
566         enum line_compiled ret;
567         struct list_head *head;
568         va_list ap;
569
570         head = talloc(NULL, struct list_head);
571         list_head_init(head);
572
573         va_start(ap, value);
574         add_symbol(head, symbol, value);
575
576         while ((symbol = va_arg(ap, const char *)) != NULL) {
577                 value = va_arg(ap, const unsigned int *);
578                 add_symbol(head, symbol, value);
579         }
580         ret = get_pp(cond, head);
581         talloc_free(head);
582         return ret;
583 }
584
585 void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
586                       const char *error)
587 {
588         struct file_error *fe = talloc(score, struct file_error);
589         fe->file = f;
590         fe->line = line;
591         fe->error = error;
592         list_add_tail(&score->per_file_errors, &fe->list);
593 }