]> git.ozlabs.org Git - ccan/blob - tools/ccanlint/file_analysis.c
tools: use rbuf instead of grab_file.
[ccan] / tools / ccanlint / file_analysis.c
1 #include "config.h"
2 #include "ccanlint.h"
3 #include <ccan/talloc/talloc.h>
4 #include <ccan/str/str.h>
5 #include <ccan/str_talloc/str_talloc.h>
6 #include <ccan/talloc_link/talloc_link.h>
7 #include <ccan/hash/hash.h>
8 #include <ccan/htable/htable_type.h>
9 #include <ccan/noerr/noerr.h>
10 #include <ccan/foreach/foreach.h>
11 #include <ccan/asort/asort.h>
12 #include <ccan/array_size/array_size.h>
13 #include "../tools.h"
14 #include <unistd.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <err.h>
19 #include <errno.h>
20 #include <dirent.h>
21 #include <ctype.h>
22 #include <stdarg.h>
23 #include <assert.h>
24
25 struct list_head *get_ccan_file_docs(struct ccan_file *f)
26 {
27         if (!f->doc_sections) {
28                 get_ccan_file_lines(f);
29                 f->doc_sections = extract_doc_sections(f->lines, f->name);
30         }
31         return f->doc_sections;
32 }
33
34
35 /**
36  * remove_comments - strip comments from a line, return copy.
37  * @line: line to copy
38  * @in_comment: are we already within a comment (from prev line).
39  * @unterminated: are we still in a comment for next line.
40  */
41 static char *remove_comments(const char *line, bool in_comment,
42                              bool *unterminated)
43 {
44         char *p, *ret = talloc_array(line, char, strlen(line) + 1);
45
46         p = ret;
47         for (;;) {
48                 if (!in_comment) {
49                         /* Find first comment. */
50                         const char *old_comment = strstr(line, "/*");
51                         const char *new_comment = strstr(line, "//");
52                         const char *comment;
53
54                         if (new_comment && old_comment)
55                                 comment = new_comment < old_comment
56                                         ? new_comment : old_comment;
57                         else if (old_comment)
58                                 comment = old_comment;
59                         else if (new_comment)
60                                 comment = new_comment;
61                         else {
62                                 /* Nothing more. */
63                                 strcpy(p, line);
64                                 *unterminated = false;
65                                 break;
66                         }
67
68                         /* Copy up to comment. */
69                         memcpy(p, line, comment - line);
70                         p += comment - line;
71                         line += comment - line + 2;
72
73                         if (comment == new_comment) {
74                                 /* We're done: goes to EOL. */
75                                 p[0] = '\0';
76                                 *unterminated = false;
77                                 break;
78                         }
79                         in_comment = true;
80                 }
81
82                 if (in_comment) {
83                         const char *end = strstr(line, "*/");
84                         if (!end) {
85                                 *unterminated = true;
86                                 p[0] = '\0';
87                                 break;
88                         }
89                         line = end+2;
90                         in_comment = false;
91                 }
92         }
93         return ret;
94 }
95
96 static bool is_empty(const char *line)
97 {
98         return strspn(line, " \r\t") == strlen(line);
99 }
100
101 static bool continues(const char *line)
102 {
103         /* Technically, any odd number of these.  But who cares? */
104         return strends(line, "\\");
105 }
106
107 static bool parse_hash_if(struct pp_conditions *cond, const char **line)
108 {
109         bool brackets, defined;
110
111         cond->inverse = get_token(line, "!");
112         defined = get_token(line, "defined");
113         brackets = get_token(line, "(");
114         cond->symbol = get_symbol_token(cond, line);
115         if (!cond->symbol)
116                 return false;
117         if (brackets && !get_token(line, ")"))
118                 return false;
119         if (!defined)
120                 cond->type = PP_COND_IF;
121
122         /* FIXME: We just chain them, ignoring operators. */
123         if (get_token(line, "||") || get_token(line, "&&")) {
124                 struct pp_conditions *sub = talloc(cond, struct pp_conditions);
125
126                 sub->parent = cond->parent;
127                 sub->type = PP_COND_IFDEF;
128                 if (parse_hash_if(sub, line))
129                         cond->parent = sub;
130         }
131
132         return true;
133 }
134
135 /* FIXME: Get serious! */
136 static struct pp_conditions *analyze_directive(struct ccan_file *f,
137                                                const char *line,
138                                                struct pp_conditions *parent)
139 {
140         struct pp_conditions *cond = talloc(f, struct pp_conditions);
141         bool unused;
142
143         line = remove_comments(line, false, &unused);
144
145         cond->parent = parent;
146         cond->type = PP_COND_IFDEF;
147
148         if (!get_token(&line, "#"))
149                 abort();
150
151         if (get_token(&line, "if")) {
152                 if (!parse_hash_if(cond, &line))
153                         goto unknown;
154         } else if (get_token(&line, "elif")) {
155                 /* Malformed? */
156                 if (!parent)
157                         return NULL;
158                 cond->parent = parent->parent;
159                 /* FIXME: Not quite true.  This implies !parent, but we don't
160                  * do multiple conditionals yet. */
161                 if (!parse_hash_if(cond, &line))
162                         goto unknown;
163         } else if (get_token(&line, "ifdef")) {
164                 bool brackets;
165                 cond->inverse = false;
166                 brackets = get_token(&line, "(");
167                 cond->symbol = get_symbol_token(cond, &line);
168                 if (!cond->symbol)
169                         goto unknown;
170                 if (brackets && !get_token(&line, ")"))
171                         goto unknown;
172         } else if (get_token(&line, "ifndef")) {
173                 bool brackets;
174                 cond->inverse = true;
175                 brackets = get_token(&line, "(");
176                 cond->symbol = get_symbol_token(cond, &line);
177                 if (!cond->symbol)
178                         goto unknown;
179                 if (brackets && !get_token(&line, ")"))
180                         goto unknown;
181         } else if (get_token(&line, "else")) {
182                 /* Malformed? */
183                 if (!parent)
184                         return NULL;
185
186                 *cond = *parent;
187                 cond->inverse = !cond->inverse;
188                 return cond;
189         } else if (get_token(&line, "endif")) {
190                 talloc_free(cond);
191                 /* Malformed? */
192                 if (!parent)
193                         return NULL;
194                 /* Back up one! */
195                 return parent->parent;
196         } else {
197                 /* Not a conditional. */
198                 talloc_free(cond);
199                 return parent;
200         }
201
202         if (!is_empty(line))
203                 goto unknown;
204         return cond;
205
206 unknown:
207         cond->type = PP_COND_UNKNOWN;
208         return cond;
209 }
210
211 /* This parser is rough, but OK if code is reasonably neat. */
212 struct line_info *get_ccan_line_info(struct ccan_file *f)
213 {
214         bool continued = false, in_comment = false;
215         struct pp_conditions *cond = NULL;
216         unsigned int i;
217
218         if (f->line_info)
219                 return f->line_info;
220
221         get_ccan_file_lines(f);
222         f->line_info = talloc_array(f->lines, struct line_info, f->num_lines);
223
224         for (i = 0; i < f->num_lines; 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->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->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                 talloc_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 = talloc(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 = talloc(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         talloc_free(head);
360         return ret;
361 }
362
363 void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
364                       const char *errorfmt, ...)
365 {
366         va_list ap;
367
368         struct file_error *fe = talloc(score, struct file_error);
369         fe->file = f;
370         fe->line = line;
371         list_add_tail(&score->per_file_errors, &fe->list);
372
373         if (!score->error)
374                 score->error = talloc_strdup(score, "");
375         
376         if (verbose < 2 && strcount(score->error, "\n") > 5)
377                 return;
378
379         if (line)
380                 score->error = talloc_asprintf_append(score->error,
381                                                       "%s:%u:",
382                                                       f->fullname, line);
383         else
384                 score->error = talloc_asprintf_append(score->error,
385                                                       "%s:", f->fullname);
386
387         va_start(ap, errorfmt);
388         score->error = talloc_vasprintf_append(score->error, errorfmt, ap);
389         va_end(ap);
390         score->error = talloc_append_string(score->error, "\n");
391
392         if (verbose < 2 && strcount(score->error, "\n") > 5)
393                 score->error = talloc_append_string(score->error,
394                                     "... more (use -vv to see them all)\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 }