tools: use tal instead of talloc.
[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, f->num_lines);
222
223         for (i = 0; i < f->num_lines; continued = continues(f->lines[i++])) {
224                 char *p;
225                 bool still_doc_line;
226
227                 /* Current conditions apply to this line. */
228                 f->line_info[i].cond = cond;
229                 f->line_info[i].continued = continued;
230
231                 if (continued) {
232                         /* Same as last line. */
233                         f->line_info[i].type = f->line_info[i-1].type;
234                         /* Update in_comment. */
235                         remove_comments(f, f->lines[i], in_comment, &in_comment);
236                         continue;
237                 }
238
239                 /* Preprocessor directive? */
240                 if (!in_comment
241                     && f->lines[i][strspn(f->lines[i], " \t")] == '#') {
242                         f->line_info[i].type = PREPROC_LINE;
243                         cond = analyze_directive(f, f->lines[i], cond);
244                         continue;
245                 }
246
247                 still_doc_line = (in_comment
248                                   && f->line_info[i-1].type == DOC_LINE);
249
250                 p = remove_comments(f, f->lines[i], in_comment, &in_comment);
251                 if (is_empty(p)) {
252                         if (strstarts(f->lines[i], "/**") || still_doc_line)
253                                 f->line_info[i].type = DOC_LINE;
254                         else
255                                 f->line_info[i].type = COMMENT_LINE;
256                 } else
257                         f->line_info[i].type = CODE_LINE;
258                 tal_free(p);
259         }
260         return f->line_info;
261 }
262
263 struct symbol {
264         struct list_node list;
265         const char *name;
266         const unsigned int *value;
267 };
268
269 static struct symbol *find_symbol(struct list_head *syms, const char *sym)
270 {
271         struct symbol *i;
272
273         list_for_each(syms, i, list)
274                 if (streq(sym, i->name))
275                         return i;
276         return NULL;
277 }
278
279 static enum line_compiled get_pp(struct pp_conditions *cond,
280                                  struct list_head *syms)
281 {
282         struct symbol *sym;
283         unsigned int val;
284         enum line_compiled parent, ret;
285
286         /* No conditions?  Easy. */
287         if (!cond)
288                 return COMPILED;
289
290         /* Check we get here at all. */
291         parent = get_pp(cond->parent, syms);
292         if (parent == NOT_COMPILED)
293                 return NOT_COMPILED;
294
295         if (cond->type == PP_COND_UNKNOWN)
296                 return MAYBE_COMPILED;
297
298         sym = find_symbol(syms, cond->symbol);
299         if (!sym)
300                 return MAYBE_COMPILED;
301
302         switch (cond->type) {
303         case PP_COND_IF:
304                 /* Undefined is 0. */
305                 val = sym->value ? *sym->value : 0;
306                 if (!val == cond->inverse)
307                         ret = COMPILED;
308                 else
309                         ret = NOT_COMPILED;
310                 break;
311
312         case PP_COND_IFDEF:
313                 if (cond->inverse == !sym->value)
314                         ret = COMPILED;
315                 else
316                         ret = NOT_COMPILED;
317                 break;
318
319         default:
320                 abort();
321         }
322
323         /* If parent didn't know, NO == NO, but YES == MAYBE. */
324         if (parent == MAYBE_COMPILED && ret == COMPILED)
325                 ret = MAYBE_COMPILED;
326         return ret;
327 }
328
329 static void add_symbol(struct list_head *head,
330                        const char *symbol, const unsigned int *value)
331 {
332         struct symbol *sym = tal(head, struct symbol);
333         sym->name = symbol;
334         sym->value = value;
335         list_add(head, &sym->list);
336 }
337         
338 enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
339                                     const char *symbol,
340                                     const unsigned int *value,
341                                     ...)
342 {
343         enum line_compiled ret;
344         struct list_head *head;
345         va_list ap;
346
347         head = tal(NULL, struct list_head);
348         list_head_init(head);
349
350         va_start(ap, value);
351         add_symbol(head, symbol, value);
352
353         while ((symbol = va_arg(ap, const char *)) != NULL) {
354                 value = va_arg(ap, const unsigned int *);
355                 add_symbol(head, symbol, value);
356         }
357         ret = get_pp(cond, head);
358         tal_free(head);
359         return ret;
360 }
361
362 void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
363                       const char *errorfmt, ...)
364 {
365         va_list ap;
366
367         struct file_error *fe = tal(score, struct file_error);
368         fe->file = f;
369         fe->line = line;
370         list_add_tail(&score->per_file_errors, &fe->list);
371
372         if (!score->error)
373                 score->error = tal_strdup(score, "");
374         
375         if (verbose < 2 && strcount(score->error, "\n") > 5) {
376                 if (!strends(score->error,
377                              "... more (use -vv to see them all)\n")) {
378                         score->error = tal_strcat(score,
379                                                   take(score->error),
380                                                   "... more (use -vv to see"
381                                                   " them all)\n");
382                 }
383                 return;
384         }
385
386         if (line)
387                 tal_append_fmt(&score->error, "%s:%u:", f->fullname, line);
388         else
389                 tal_append_fmt(&score->error, "%s:", f->fullname);
390
391         va_start(ap, errorfmt);
392         tal_append_vfmt(&score->error, errorfmt, ap);
393         va_end(ap);
394         score->error = tal_strcat(score, take(score->error),"\n");
395 }
396
397 char *get_or_compile_info(const void *ctx, const char *dir)
398 {
399         struct manifest *m = get_manifest(NULL, dir);
400
401         if (!m->info_file->compiled[COMPILE_NORMAL])
402                 m->info_file->compiled[COMPILE_NORMAL] = compile_info(m, dir);
403
404         return m->info_file->compiled[COMPILE_NORMAL];
405 }