ccanlint: Add -k option to keep results.
[ccan] / tools / ccanlint / tests / idempotent.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <tools/tools.h>
3 #include <ccan/talloc/talloc.h>
4 #include <ccan/str/str.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8 #include <unistd.h>
9 #include <limits.h>
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <err.h>
14 #include <string.h>
15 #include <ctype.h>
16
17 static const char explain[] 
18 = "Headers usually start with the C preprocessor lines to prevent multiple\n"
19   "inclusions.  These look like the following:\n"
20   "#ifndef MY_HEADER_H\n"
21   "#define MY_HEADER_H\n"
22   "...\n"
23   "#endif /* MY_HEADER_H */\n";
24
25 static char *report_idem(struct ccan_file *f, char *sofar)
26 {
27         struct line_info *line_info;
28         unsigned int i, first_preproc_line;
29         const char *line, *sym;
30
31         line_info = get_ccan_line_info(f);
32         if (f->num_lines < 3)
33                 /* FIXME: We assume small headers probably uninteresting. */
34                 return sofar;
35
36         for (i = 0; i < f->num_lines; i++) {
37                 if (line_info[i].type == DOC_LINE
38                     || line_info[i].type == COMMENT_LINE)
39                         continue;
40                 if (line_info[i].type == CODE_LINE)
41                         return talloc_asprintf_append(sofar,
42                               "%s:%u:expect first non-comment line to be #ifndef.\n", f->name, i+1);
43                 else if (line_info[i].type == PREPROC_LINE)
44                         break;
45         }
46
47         /* No code at all?  Don't complain. */
48         if (i == f->num_lines)
49                 return sofar;
50
51         first_preproc_line = i;
52         for (i = first_preproc_line+1; i < f->num_lines; i++) {
53                 if (line_info[i].type == DOC_LINE
54                     || line_info[i].type == COMMENT_LINE)
55                         continue;
56                 if (line_info[i].type == CODE_LINE)
57                         return talloc_asprintf_append(sofar,
58                               "%s:%u:expect second line to be #define.\n", f->name, i+1);
59                 else if (line_info[i].type == PREPROC_LINE)
60                         break;
61         }
62
63         /* No code at all?  Weird. */
64         if (i == f->num_lines)
65                 return sofar;
66
67         /* We expect a condition on this line. */
68         if (!line_info[i].cond) {
69                 return talloc_asprintf_append(sofar,
70                                               "%s:%u:expected #ifndef.\n",
71                                               f->name, first_preproc_line+1);
72         }
73
74         line = f->lines[i];
75
76         /* We expect the condition to be ! IFDEF <symbol>. */
77         if (line_info[i].cond->type != PP_COND_IFDEF
78             || !line_info[i].cond->inverse) {
79                 return talloc_asprintf_append(sofar,
80                                               "%s:%u:expected #ifndef.\n",
81                                               f->name, first_preproc_line+1);
82         }
83
84         /* And this to be #define <symbol> */
85         if (!get_token(&line, "#"))
86                 abort();
87         if (!get_token(&line, "define")) {
88                 return talloc_asprintf_append(sofar,
89                               "%s:%u:expected '#define %s'.\n",
90                               f->name, i+1, line_info[i].cond->symbol);
91         }
92         sym = get_symbol_token(f, &line);
93         if (!sym || !streq(sym, line_info[i].cond->symbol)) {
94                 return talloc_asprintf_append(sofar,
95                               "%s:%u:expected '#define %s'.\n",
96                               f->name, i+1, line_info[i].cond->symbol);
97         }
98
99         /* Rest of code should all be covered by that conditional. */
100         for (i++; i < f->num_lines; i++) {
101                 unsigned int val = 0;
102                 if (line_info[i].type == DOC_LINE
103                     || line_info[i].type == COMMENT_LINE)
104                         continue;
105                 if (get_ccan_line_pp(line_info[i].cond, sym, &val, NULL)
106                     != NOT_COMPILED)
107                         return talloc_asprintf_append(sofar,
108                               "%s:%u:code outside idempotent region.\n",
109                               f->name, i+1);
110         }
111
112         return sofar;
113 }
114
115 static void *check_idempotent(struct manifest *m,
116                               bool keep,
117                               unsigned int *timeleft)
118 {
119         struct ccan_file *f;
120         char *report = NULL;
121
122         list_for_each(&m->h_files, f, list)
123                 report = report_idem(f, report);
124
125         return report;
126 }
127
128 static const char *describe_idempotent(struct manifest *m, void *check_result)
129 {
130         return talloc_asprintf(check_result, 
131                                "Some headers not idempotent:\n"
132                                "%s\n%s", (char *)check_result,
133                                explain);
134 }
135
136 struct ccanlint idempotent = {
137         .key = "idempotent",
138         .name = "Module headers are #ifndef/#define wrapped",
139         .total_score = 1,
140         .check = check_idempotent,
141         .describe = describe_idempotent,
142 };
143
144 REGISTER_TEST(idempotent, NULL);