d2fe2a4c1deb621cda477148fa79b4abbe582954
[ccan] / tools / ccanlint / file_analysis.c
1 #include "config.h"
2 #include "ccanlint.h"
3 #include <ccan/str/str.h>
4 #include <ccan/take/take.h>
5 #include <ccan/hash/hash.h>
6 #include <ccan/htable/htable_type.h>
7 #include <ccan/noerr/noerr.h>
8 #include <ccan/foreach/foreach.h>
9 #include <ccan/asort/asort.h>
10 #include <ccan/array_size/array_size.h>
11 #include "../tools.h"
12 #include <unistd.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <err.h>
17 #include <errno.h>
18 #include <dirent.h>
19 #include <ctype.h>
20 #include <stdarg.h>
21 #include <assert.h>
22
23 struct list_head *get_ccan_file_docs(struct ccan_file *f)
24 {
25         if (!f->doc_sections) {
26                 get_ccan_file_lines(f);
27                 f->doc_sections = extract_doc_sections(f->lines, f->name);
28         }
29         return f->doc_sections;
30 }
31
32
33 /**
34  * remove_comments - strip comments from a line, return copy.
35  * @line: line to copy
36  * @in_comment: are we already within a comment (from prev line).
37  * @unterminated: are we still in a comment for next line.
38  */
39 static char *remove_comments(const tal_t *ctx,
40                              const char *line, bool in_comment,
41                              bool *unterminated)
42 {
43         char *p, *ret = tal_arr(ctx, char, strlen(line) + 1);
44
45         p = ret;
46         for (;;) {
47                 if (!in_comment) {
48                         /* Find first comment. */
49                         const char *old_comment = strstr(line, "/*");
50                         const char *new_comment = strstr(line, "//");
51                         const char *comment;
52
53                         if (new_comment && old_comment)
54                                 comment = new_comment < old_comment
55                                         ? new_comment : old_comment;
56                         else if (old_comment)
57                                 comment = old_comment;
58                         else if (new_comment)
59                                 comment = new_comment;
60                         else {
61                                 /* Nothing more. */
62                                 strcpy(p, line);
63                                 *unterminated = false;
64                                 break;
65                         }
66
67                         /* Copy up to comment. */
68                         memcpy(p, line, comment - line);
69                         p += comment - line;
70                         line += comment - line + 2;
71
72                         if (comment == new_comment) {
73                                 /* We're done: goes to EOL. */
74                                 p[0] = '\0';
75                                 *unterminated = false;
76                                 break;
77                         }
78                         in_comment = true;
79                 }
80
81                 if (in_comment) {
82                         const char *end = strstr(line, "*/");
83                         if (!end) {
84                                 *unterminated = true;
85                                 p[0] = '\0';
86                                 break;
87                         }
88                         line = end+2;
89                         in_comment = false;
90                 }
91         }
92         return ret;
93 }
94
95 static bool is_empty(const char *line)
96 {
97         return strspn(line, " \r\t") == strlen(line);
98 }
99
100 static bool continues(const char *line)
101 {
102         /* Technically, any odd number of these.  But who cares? */
103         return strends(line, "\\");
104 }
105
106 static bool parse_hash_if(struct pp_conditions *cond, const char **line)
107 {
108         bool brackets, defined;
109
110         cond->inverse = get_token(line, "!");
111         defined = get_token(line, "defined");
112         brackets = get_token(line, "(");
113         cond->symbol = get_symbol_token(cond, line);
114         if (!cond->symbol)
115                 return false;
116         if (brackets && !get_token(line, ")"))
117                 return false;
118         if (!defined)
119                 cond->type = PP_COND_IF;
120
121         /* FIXME: We just chain them, ignoring operators. */
122         if (get_token(line, "||") || get_token(line, "&&")) {
123                 struct pp_conditions *sub = tal(cond, struct pp_conditions);
124
125                 sub->parent = cond->parent;
126                 sub->type = PP_COND_IFDEF;
127                 if (parse_hash_if(sub, line))
128                         cond->parent = sub;
129         }
130
131         return true;
132 }
133
134 /* FIXME: Get serious! */
135 static struct pp_conditions *analyze_directive(struct ccan_file *f,
136                                                const char *line,
137                                                struct pp_conditions *parent)
138 {
139         struct pp_conditions *cond = tal(f, struct pp_conditions);
140         bool unused;
141
142         line = remove_comments(f, line, false, &unused);
143
144         cond->parent = parent;
145         cond->type = PP_COND_IFDEF;
146
147         if (!get_token(&line, "#"))
148                 abort();
149
150         if (get_token(&line, "if")) {
151                 if (!parse_hash_if(cond, &line))
152                         goto unknown;
153         } else if (get_token(&line, "elif")) {
154                 /* Malformed? */
155                 if (!parent)
156                         return NULL;
157                 cond->parent = parent->parent;
158                 /* FIXME: Not quite true.  This implies !parent, but we don't
159                  * do multiple conditionals yet. */
160                 if (!parse_hash_if(cond, &line))
161                         goto unknown;
162         } else if (get_token(&line, "ifdef")) {
163                 bool brackets;
164                 cond->inverse = false;
165                 brackets = get_token(&line, "(");
166                 cond->symbol = get_symbol_token(cond, &line);
167                 if (!cond->symbol)
168                         goto unknown;
169                 if (brackets && !get_token(&line, ")"))
170                         goto unknown;
171         } else if (get_token(&line, "ifndef")) {
172                 bool brackets;
173                 cond->inverse = true;
174                 brackets = get_token(&line, "(");
175                 cond->symbol = get_symbol_token(cond, &line);
176                 if (!cond->symbol)
177                         goto unknown;
178                 if (brackets && !get_token(&line, ")"))
179                         goto unknown;
180         } else if (get_token(&line, "else")) {
181                 /* Malformed? */
182                 if (!parent)
183                         return NULL;
184
185                 *cond = *parent;
186                 cond->inverse = !cond->inverse;
187                 return cond;
188         } else if (get_token(&line, "endif")) {
189                 tal_free(cond);
190                 /* Malformed? */
191                 if (!parent)
192                         return NULL;
193                 /* Back up one! */
194                 return parent->parent;
195         } else {
196                 /* Not a conditional. */
197                 tal_free(cond);
198                 return parent;
199         }
200
201         if (!is_empty(line))
202                 goto unknown;
203         return cond;
204
205 unknown:
206         cond->type = PP_COND_UNKNOWN;
207         return cond;
208 }
209
210 /* This parser is rough, but OK if code is reasonably neat. */
211 struct line_info *get_ccan_line_info(struct ccan_file *f)
212 {
213         bool continued = false, in_comment = false;
214         struct pp_conditions *cond = NULL;
215         unsigned int i;
216
217         if (f->line_info)
218                 return f->line_info;
219
220         get_ccan_file_lines(f);
221         f->line_info = tal_arr(f->lines, struct line_info,
222                                tal_count(f->lines)-1);
223
224         for (i = 0; f->lines[i]; continued = continues(f->lines[i++])) {
225                 char *p;
226                 bool still_doc_line;
227
228                 /* Current conditions apply to this line. */
229                 f->line_info[i].cond = cond;
230                 f->line_info[i].continued = continued;
231
232                 if (continued) {
233                         /* Same as last line. */
234                         f->line_info[i].type = f->line_info[i-1].type;
235                         /* Update in_comment. */
236                         remove_comments(f, f->lines[i], in_comment, &in_comment);
237                         continue;
238                 }
239
240                 /* Preprocessor directive? */
241                 if (!in_comment
242                     && f->lines[i][strspn(f->lines[i], " \t")] == '#') {
243                         f->line_info[i].type = PREPROC_LINE;
244                         cond = analyze_directive(f, f->lines[i], cond);
245                         continue;
246                 }
247
248                 still_doc_line = (in_comment
249                                   && f->line_info[i-1].type == DOC_LINE);
250
251                 p = remove_comments(f, f->lines[i], in_comment, &in_comment);
252                 if (is_empty(p)) {
253                         if (strstarts(f->lines[i], "/**") || still_doc_line)
254                                 f->line_info[i].type = DOC_LINE;
255                         else
256                                 f->line_info[i].type = COMMENT_LINE;
257                 } else
258                         f->line_info[i].type = CODE_LINE;
259                 tal_free(p);
260         }
261         return f->line_info;
262 }
263
264 struct symbol {
265         struct list_node list;
266         const char *name;
267         const unsigned int *value;
268 };
269
270 static struct symbol *find_symbol(struct list_head *syms, const char *sym)
271 {
272         struct symbol *i;
273
274         list_for_each(syms, i, list)
275                 if (streq(sym, i->name))
276                         return i;
277         return NULL;
278 }
279
280 static enum line_compiled get_pp(struct pp_conditions *cond,
281                                  struct list_head *syms)
282 {
283         struct symbol *sym;
284         unsigned int val;
285         enum line_compiled parent, ret;
286
287         /* No conditions?  Easy. */
288         if (!cond)
289                 return COMPILED;
290
291         /* Check we get here at all. */
292         parent = get_pp(cond->parent, syms);
293         if (parent == NOT_COMPILED)
294                 return NOT_COMPILED;
295
296         if (cond->type == PP_COND_UNKNOWN)
297                 return MAYBE_COMPILED;
298
299         sym = find_symbol(syms, cond->symbol);
300         if (!sym)
301                 return MAYBE_COMPILED;
302
303         switch (cond->type) {
304         case PP_COND_IF:
305                 /* Undefined is 0. */
306                 val = sym->value ? *sym->value : 0;
307                 if (!val == cond->inverse)
308                         ret = COMPILED;
309                 else
310                         ret = NOT_COMPILED;
311                 break;
312
313         case PP_COND_IFDEF:
314                 if (cond->inverse == !sym->value)
315                         ret = COMPILED;
316                 else
317                         ret = NOT_COMPILED;
318                 break;
319
320         default:
321                 abort();
322         }
323
324         /* If parent didn't know, NO == NO, but YES == MAYBE. */
325         if (parent == MAYBE_COMPILED && ret == COMPILED)
326                 ret = MAYBE_COMPILED;
327         return ret;
328 }
329
330 static void add_symbol(struct list_head *head,
331                        const char *symbol, const unsigned int *value)
332 {
333         struct symbol *sym = tal(head, struct symbol);
334         sym->name = symbol;
335         sym->value = value;
336         list_add(head, &sym->list);
337 }
338         
339 enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
340                                     const char *symbol,
341                                     const unsigned int *value,
342                                     ...)
343 {
344         enum line_compiled ret;
345         struct list_head *head;
346         va_list ap;
347
348         head = tal(NULL, struct list_head);
349         list_head_init(head);
350
351         va_start(ap, value);
352         add_symbol(head, symbol, value);
353
354         while ((symbol = va_arg(ap, const char *)) != NULL) {
355                 value = va_arg(ap, const unsigned int *);
356                 add_symbol(head, symbol, value);
357         }
358         ret = get_pp(cond, head);
359         tal_free(head);
360         return ret;
361 }
362
363 static void score_error_vfmt(struct score *score, const char *source,
364                              const char *errorfmt, va_list ap)
365 {
366
367         if (!score->error)
368                 score->error = tal_strdup(score, "");
369         
370         if (verbose < 2 && strcount(score->error, "\n") > 5) {
371                 if (!strends(score->error,
372                              "... more (use -vv to see them all)\n")) {
373                         score->error = tal_strcat(score,
374                                                   take(score->error),
375                                                   "... more (use -vv to see"
376                                                   " them all)\n");
377                 }
378                 return;
379         }
380
381         tal_append_fmt(&score->error, "%s:", source);
382         tal_append_vfmt(&score->error, errorfmt, ap);
383         score->error = tal_strcat(score, take(score->error), "\n");
384 }
385
386
387
388 void score_error(struct score *score, const char *source,
389                  const char *errorfmt, ...)
390 {
391         va_list ap;
392
393         va_start(ap, errorfmt);
394         score_error_vfmt(score, source, errorfmt, ap);
395         va_end(ap);
396 }
397
398 void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
399                       const char *errorfmt, ...)
400 {
401         va_list ap;
402         char *source;
403
404         struct file_error *fe = tal(score, struct file_error);
405         fe->file = f;
406         fe->line = line;
407         list_add_tail(&score->per_file_errors, &fe->list);
408
409         if (line)
410                 source = tal_fmt(score, "%s:%u", f->fullname, line);
411         else
412                 source = tal_fmt(score, "%s", f->fullname);
413
414         va_start(ap, errorfmt);
415         score_error_vfmt(score, source, errorfmt, ap);
416         va_end(ap);
417 }
418
419
420 char *get_or_compile_info(const void *ctx UNNEEDED, const char *dir)
421 {
422         struct manifest *m = get_manifest(NULL, dir);
423
424         if (!m->info_file->compiled[COMPILE_NORMAL])
425                 m->info_file->compiled[COMPILE_NORMAL] = compile_info(m, dir);
426
427         return m->info_file->compiled[COMPILE_NORMAL];
428 }