]> git.ozlabs.org Git - ccan/blob - tools/ccanlint/tests/examples_compile.c
cb71bf8329db7e54227b7c26af94e97cfb2b18e6
[ccan] / tools / ccanlint / tests / examples_compile.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <tools/tools.h>
3 #include <ccan/tal/tal.h>
4 #include <ccan/tal/str/str.h>
5 #include <ccan/take/take.h>
6 #include <ccan/cast/cast.h>
7 #include <ccan/tal/path/path.h>
8 #include <ccan/str/str.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <stdint.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <ctype.h>
16 #include <assert.h>
17 #include <err.h>
18 #include "build.h"
19
20 static const char *can_run(struct manifest *m)
21 {
22         if (safe_mode)
23                 return "Safe mode enabled";
24         if (list_empty(&m->examples))
25                 return "No examples to compile";
26         return NULL;
27 }
28
29 static void add_mod(struct manifest ***deps, struct manifest *m)
30 {
31         unsigned int num = tal_count(*deps);
32         tal_resize(deps, num + 1);
33         (*deps)[num] = m;
34 }
35
36 static bool have_mod(struct manifest *deps[], const char *modname)
37 {
38         unsigned int i;
39
40         for (i = 0; i < tal_count(deps); i++)
41                 if (strcmp(deps[i]->modname, modname) == 0)
42                         return true;
43         return false;
44 }
45
46 static void add_dep(struct manifest ***deps, const char *modname)
47 {
48         unsigned int i;
49         struct manifest *m;
50         char *errstr;
51
52         if (have_mod(*deps, modname))
53                 return;
54
55         m = get_manifest(*deps,
56                          tal_fmt(*deps, "%s/ccan/%s", ccan_dir, modname));
57         errstr = build_submodule(m, cflags, COMPILE_NORMAL);
58         if (errstr)
59                 errx(1, "%s", errstr);
60
61         add_mod(deps, m);
62
63         /* Get that modules depends as well... */
64         assert(!safe_mode);
65         if (m->info_file) {
66                 char **infodeps;
67
68                 infodeps = get_deps(m, m->dir, "depends", false,
69                                     get_or_compile_info);
70
71                 for (i = 0; infodeps[i]; i++) {
72                         if (strstarts(infodeps[i], "ccan/"))
73                                 add_dep(deps, infodeps[i] + strlen("ccan/"));
74                 }
75         }
76 }
77
78 static struct manifest **get_example_deps(struct manifest *m,
79                                           struct ccan_file *f)
80 {
81         char **lines;
82         struct manifest **deps = tal_arr(f, struct manifest *, 0);
83
84         /* This one for a start. */
85         add_dep(&deps, m->modname);
86
87         /* Other modules implied by includes. */
88         for (lines = get_ccan_file_lines(f); *lines; lines++) {
89                 char *modname;
90                 if (tal_strreg(f, *lines,
91                                "^[ \t]*#[ \t]*include[ \t]*[<\"]"
92                                "ccan/+(.+)/+[^/]+\\.h", &modname)) {
93                         if (!have_mod(deps, modname))
94                                 add_dep(&deps, modname);
95                 }
96         }
97
98         return deps;
99 }
100
101 static char *example_obj_list(const void *ctx, struct manifest **deps)
102 {
103         char *list = tal_strdup(ctx, "");
104         unsigned int i;
105
106         for (i = 0; i < tal_count(deps); i++) {
107                 if (deps[i]->compiled[COMPILE_NORMAL])
108                         tal_append_fmt(&list, " %s",
109                                        deps[i]->compiled[COMPILE_NORMAL]);
110         }
111         return list;
112 }
113
114 static char *example_lib_list(const void *ctx, struct manifest **deps)
115 {
116         char *list = tal_strdup(ctx, "");
117         char **libs;
118         unsigned int i, j;
119
120         /* FIXME: This doesn't uniquify. */
121         for (i = 0; i < tal_count(deps); i++) {
122                 libs = get_libs(ctx, deps[i]->dir, NULL, get_or_compile_info);
123                 for (j = 0; libs[j]; j++)
124                         tal_append_fmt(&list, "-l%s ", libs[j]);
125         }
126         return list;
127 }
128
129 static char *cflags_list(const struct manifest *m)
130 {
131         unsigned int i;
132         char *ret = tal_strdup(m, cflags);
133
134         char **flags = get_cflags(m, m->dir, get_or_compile_info);
135         for (i = 0; flags[i]; i++)
136                 tal_append_fmt(&ret, " %s", flags[i]);
137         return ret;
138 }
139
140 /* FIXME: Test with reduced features! */
141 static bool compile(const void *ctx,
142                     struct manifest *m,
143                     struct ccan_file *file,
144                     char **output)
145 {
146         struct manifest **deps = get_example_deps(m, file);
147         const char *flags = cflags_list(m);
148
149         file->compiled[COMPILE_NORMAL] = temp_file(ctx, "", file->fullname);
150         if (!compile_and_link(ctx, file->fullname, ccan_dir,
151                               example_obj_list(file, deps),
152                               compiler, flags,
153                               example_lib_list(file, deps),
154                               file->compiled[COMPILE_NORMAL],
155                               output)) {
156                 /* Don't keep failures, even with --keep */
157                 unlink(file->compiled[COMPILE_NORMAL]);
158                 file->compiled[COMPILE_NORMAL] = NULL;
159                 return false;
160         }
161         return true;
162 }
163
164 static void start_main(char **ret, const char *why)
165 {
166         tal_append_fmt(ret,
167               "/* The example %s, so fake function wrapper inserted */\n"
168               "int main(int argc, char *argv[])\n"
169                "{\n", why);
170 }
171
172 /* We only handle simple function definitions here. */
173 static char *add_func(const tal_t *ctx, char *others, const char *line)
174 {
175         const char *p, *end = strchr(line, '(') - 1;
176         char *use;
177
178         while (cisspace(*end)) {
179                 end--;
180                 if (end == line)
181                         return others;
182         }
183
184         for (p = end; cisalnum(*p) || *p == '_'; p--) {
185                 if (p == line)
186                         return others;
187         }
188
189         use = tal_fmt(ctx, "printf(\"%%p\", %.*s);\n",
190                       (unsigned)(end - p), p+1);
191         if (others)
192                 use = tal_strcat(ctx, take(others), take(use));
193
194         return use;
195 }
196
197 static void strip_leading_whitespace(char **lines)
198 {
199         unsigned int i, min_span = -1U;
200
201         for (i = 0; lines[i]; i++) {
202                 unsigned int span = strspn(lines[i], " \t");
203                 /* All whitespace?  Ignore */
204                 if (!lines[i][span])
205                         continue;
206                 if (span < min_span)
207                         min_span = span;
208         }
209
210         for (i = 0; lines[i]; i++)
211                 if (strlen(lines[i]) >= min_span)
212                         lines[i] += min_span;
213 }
214
215 static bool looks_internal(char **lines, char **why)
216 {
217         unsigned int i;
218         bool last_ended = true; /* Did last line finish a statement? */
219
220         for (i = 0; lines[i]; i++) {
221                 /* Skip leading whitespace. */
222                 const char *line = lines[i] + strspn(lines[i], " \t");
223                 unsigned len = strspn(line, IDENT_CHARS);
224
225                 if (!line[0] || cisspace(line[0]) || strstarts(line, "//")
226                     || strstarts(line, "#line"))
227                         continue;
228
229                 assert(line[strlen(line)-1] != '\n');
230
231                 /* The winners. */
232                 if (strstarts(line, "if") && len == 2) {
233                         *why = cast_const(char *, "starts with if");
234                         return true;
235                 }
236                 if (strstarts(line, "for") && len == 3) {
237                         *why = cast_const(char *, "starts with for");
238                         return true;
239                 }
240                 if (strstarts(line, "while") && len == 5) {
241                         *why = cast_const(char *, "starts with while");
242                         return true;
243                 }
244                 if (strstarts(line, "do") && len == 2) {
245                         *why = cast_const(char *, "starts with do");
246                         return true;
247                 }
248
249                 /* The losers. */
250                 if (strstarts(line, "#include")) {
251                         *why = cast_const(char *, "starts with #include");
252                         return false;
253                 }
254
255                 if (last_ended && strchr(line, '(')) {
256                         if (strstarts(line, "static")) {
257                                 *why = cast_const(char *,
258                                                   "starts with static"
259                                                   " and contains (");
260                                 return false;
261                         }
262                         if (strends(line, ")")) {
263                                 *why = cast_const(char *,
264                                                   "contains ( and ends with )");
265                                 return false;
266                         }
267                 }
268
269                 /* Previously prepended. */
270                 if (strstr(line, "didn't seem to belong inside a function")) {
271                         *why = cast_const(char *, "Comment said so");
272                         return false;
273                 }
274
275                 /* Single identifier then operator == inside function. */
276                 if (last_ended && len
277                     && cispunct(line[len+strspn(line+len, " ")])) {
278                         *why = cast_const(char *, "starts with identifier"
279                                           " then punctuation");
280                         return true;
281                 }
282
283                 last_ended = (strends(line, "}")
284                               || strends(line, ";")
285                               || strends(line, "*/")
286                               || streq(line, "..."));
287         }
288
289         /* No idea... Say yes? */
290         *why = cast_const(char *, "gave no clues");
291         return true;
292 }
293
294 /* Examples will often build on prior ones.  Try combining them. */
295 static char **combine(const void *ctx, char **lines, char **prev)
296 {
297         unsigned int i, lines_total, prev_total, count;
298         char **ret;
299         const char *reasoning;
300         char *why = NULL;
301
302         if (!prev)
303                 return NULL;
304
305         /* If it looks internal, put prev at start. */
306         if (looks_internal(lines, &why)) {
307                 count = 0;
308                 reasoning = "seemed to belong inside a function";
309         } else {
310                 /* Try inserting in first elided position */
311                 for (count = 0; lines[count]; count++) {
312                         if (strcmp(lines[count], "...") == 0)
313                                 break;
314                 }
315                 if (!lines[count]) {
316                         /* Try at start anyway? */
317                         count = 0;
318                         reasoning = "didn't seem to belong inside"
319                                 " a function, so we prepended the previous"
320                                 " example";
321                 } else {
322                         reasoning = "didn't seem to belong inside"
323                                 " a function, so we put the previous example"
324                                 " at the first ...";
325
326                         count++;
327                 }
328         }
329
330         for (i = 0; lines[i]; i++);
331         lines_total = i;
332
333         for (i = 0; prev[i]; i++);
334         prev_total = i;
335
336         ret = tal_arr(ctx, char *, 1 + lines_total + prev_total + 1);
337         ret[0] = tal_fmt(ret, "/* The example %s, thus %s */",
338                          why, reasoning);
339         memcpy(ret+1, lines, count * sizeof(ret[0]));
340         memcpy(ret+1 + count, prev, prev_total * sizeof(ret[0]));
341         memcpy(ret+1 + count + prev_total, lines + count,
342                (lines_total - count + 1) * sizeof(ret[0]));
343         return ret;
344 }
345
346 /* Only handles very simple comments. */
347 static char *strip_comment(const void *ctx, const char *orig_line)
348 {
349         char *p, *ret = tal_strdup(ctx, orig_line);
350
351         p = strstr(ret, "/*");
352         if (!p)
353                 p = strstr(ret, "//");
354         if (p)
355                 *p = '\0';
356         return ret;
357 }
358
359 static char *mangle(struct manifest *m, char **lines)
360 {
361         char *ret, *use_funcs = NULL, *why;
362         bool in_function = false, fake_function = false, has_main = false;
363         unsigned int i;
364
365         ret = tal_fmt(m,
366                       "/* Include header from module. */\n"
367                       "#include <ccan/%s/%s.h>\n"
368                       "/* Prepend a heap of headers. */\n"
369                       "#include <assert.h>\n"
370                       "#include <err.h>\n"
371                       "#include <errno.h>\n"
372                       "#include <fcntl.h>\n"
373                       "#include <limits.h>\n"
374                       "#include <stdbool.h>\n"
375                       "#include <stdint.h>\n"
376                       "#include <stdio.h>\n"
377                       "#include <stdlib.h>\n"
378                       "#include <string.h>\n"
379                       "#include <sys/stat.h>\n"
380                       "#include <sys/types.h>\n"
381                       "#include <unistd.h>\n",
382                       m->modname, m->basename);
383
384         ret = tal_strcat(m, take(ret), "/* Useful dummy functions. */\n"
385                                      "extern int somefunc(void);\n"
386                                      "int somefunc(void) { return 0; }\n"
387                                      "extern char somestring[];\n"
388                                      "char somestring[] = \"hello world\";\n");
389
390         if (looks_internal(lines, &why)) {
391                 /* Wrap it all in main(). */
392                 start_main(&ret, why);
393                 fake_function = true;
394                 in_function = true;
395                 has_main = true;
396         } else
397                 tal_append_fmt(&ret,
398                              "/* The example %s, so didn't wrap in main() */\n",
399                                why);
400
401         /* Primitive, very primitive. */
402         for (i = 0; lines[i]; i++) {
403                 char *line = strip_comment(ret, lines[i]);
404
405                 /* } at start of line ends a function. */
406                 if (in_function) {
407                         if (line[0] == '}')
408                                 in_function = false;
409                 } else {
410                         /* Character at start of line, with ( and no ;
411                          * == function start.  Ignore comments. */
412                         if (!cisspace(line[0])
413                             && strchr(line, '(')
414                             && !strchr(line, ';')
415                             && !strstr(line, "//")) {
416                                 in_function = true;
417                                 if (strncmp(line, "int main", 8) == 0)
418                                         has_main = true;
419                                 if (strncmp(line, "static", 6) == 0) {
420                                         use_funcs = add_func(m, use_funcs,
421                                                              line);
422                                 }
423                         }
424                 }
425                 /* ... means elided code. */
426                 if (strcmp(line, "...") == 0) {
427                         if (!in_function && !has_main
428                             && looks_internal(lines + i + 1, &why)) {
429                                 /* This implies we start a function here. */
430                                 start_main(&ret, why);
431                                 has_main = true;
432                                 fake_function = true;
433                                 in_function = true;
434                         }
435                         ret = tal_strcat(m, take(ret), "/* ... removed */\n");
436                         continue;
437                 }
438                 ret = tal_strcat(m, take(ret), lines[i]);
439                 ret = tal_strcat(m, take(ret), "\n");
440         }
441
442         if (!has_main) {
443                 ret = tal_strcat(m, take(ret),
444                              "/* Need a main to link successfully. */\n"
445                              "int main(void)\n{\n");
446                 fake_function = true;
447         }
448
449         if (use_funcs) {
450                 ret = tal_strcat(m, take(ret),
451                                  "/* Get rid of unused warnings"
452                                  " by printing addresses of"
453                                  " static funcs. */\n");
454                 if (!fake_function) {
455                         ret = tal_strcat(m, take(ret),
456                                          "int use_funcs(void);\n"
457                                          "int use_funcs(void) {\n");
458                         fake_function = true;
459                 }
460                 tal_append_fmt(&ret, "  %s\n", use_funcs);
461         }
462
463         if (fake_function)
464                 ret = tal_strcat(m, take(ret), "return 0;\n}\n");
465         return ret;
466 }
467
468 static struct ccan_file *mangle_example(struct manifest *m,
469                                         struct ccan_file *example,
470                                         char **lines)
471 {
472         char *name, *contents;
473         int fd;
474         struct ccan_file *f;
475
476         name = temp_file(example, ".c",
477                          take(tal_fmt(NULL, "mangled-%s", example->name)));
478         f = new_ccan_file(example,
479                           take(path_dirname(example, name)),
480                           take(path_basename(example, name)));
481         tal_steal(f, name);
482
483         fd = open(f->fullname, O_WRONLY | O_CREAT | O_EXCL, 0600);
484         if (fd < 0)
485                 return NULL;
486
487         contents = mangle(m, lines);
488         if (write(fd, contents, strlen(contents)) != (int)strlen(contents)) {
489                 close(fd);
490                 return NULL;
491         }
492         close(fd);
493         f->contents = tal_steal(f, contents);
494         list_add(&m->mangled_examples, &f->list);
495         return f;
496 }
497
498 /* If an example has expected output, it's complete and should not be
499  * included in future examples. */
500 static bool has_expected_output(char **lines)
501 {
502         unsigned int i;
503
504         for (i = 0; lines[i]; i++) {
505                 char *p = lines[i] + strspn(lines[i], " \t");
506                 if (!strstarts(p, "//"))
507                         continue;
508                 p += strspn(p, "/ ");
509                 if (strncasecmp(p, "given", strlen("given")) == 0)
510                         return true;
511         }
512         return false;
513 }
514
515 static unsigned int try_compiling(struct manifest *m,
516                                   struct ccan_file *i,
517                                   char **prev,
518                                   struct ccan_file *mangled[3],
519                                   bool res[3],
520                                   char *err[3],
521                                   char **lines[3])
522 {
523         unsigned int num;
524
525         /* Try standalone. */
526         mangled[0] = i;
527         res[0] = compile(i, m, mangled[0], &err[0]);
528         lines[0] = get_ccan_file_lines(i);
529         if (res[0] && streq(err[0], ""))
530                 return 1;
531
532         if (prev) {
533                 lines[1] = combine(i, get_ccan_file_lines(i), prev);
534
535                 mangled[1] = mangle_example(m, i, lines[1]);
536                 res[1] = compile(i, m, mangled[1], &err[1]);
537                 if (res[1] && streq(err[1], "")) {
538                         return 2;
539                 }
540                 num = 2;
541         } else
542                 num = 1;
543
544         /* Try standalone. */
545         lines[num] = get_ccan_file_lines(i);
546         mangled[num] = mangle_example(m, i, lines[num]);
547         res[num] = compile(i, m, mangled[num], &err[num]);
548
549         return num+1;
550 }
551
552 static void build_examples(struct manifest *m,
553                            unsigned int *timeleft UNNEEDED, struct score *score)
554 {
555         struct ccan_file *i;
556         char **prev = NULL;
557         bool warnings = false;
558
559         score->total = 0;
560         score->pass = true;
561
562         list_for_each(&m->examples, i, list) {
563                 char *err[3];
564                 struct ccan_file *file[3] = { NULL, NULL, NULL };
565                 bool res[3];
566                 unsigned num, j;
567                 char **lines[3];
568                 const char *error;
569
570                 score->total++;
571
572                 /* Simplify our dumb parsing. */
573                 strip_leading_whitespace(get_ccan_file_lines(i));
574
575                 num = try_compiling(m, i, prev, file, res, err, lines);
576
577                 /* First look for a compile without any warnings. */
578                 for (j = 0; j < num; j++) {
579                         if (res[j] && streq(err[j], "")) {
580                                 if (!has_expected_output(lines[j]))
581                                         prev = lines[j];
582                                 score->score++;
583                                 goto next;
584                         }
585                 }
586
587                 /* Now accept anything which succeeded. */
588                 for (j = 0; j < num; j++) {
589                         if (res[j]) {
590                                 if (!has_expected_output(lines[j]))
591                                         prev = lines[j];
592                                 score->score++;
593                                 warnings = true;
594                                 score_file_error(score, file[j], 0,
595                                                  "Compiling extracted example"
596                                                  " gave warnings:\n"
597                                                  "Example:\n"
598                                                  "%s\n"
599                                                  "Compiler:\n"
600                                                  "%s",
601                                                  get_ccan_file_contents(file[j]),
602                                                  err[j]);
603                                 goto next;
604                         }
605                 }
606
607                 score->pass = false;
608                 if (!verbose) {
609                         if (num == 3)
610                                 error = "Compiling standalone, adding headers, "
611                                         "and including previous "
612                                         "example all failed";
613                         else
614                                 error = "Standalone compile and"
615                                         " adding headers both failed";
616                 } else {
617                         if (num == 3) {
618                                 error = tal_fmt(score,
619                                       "Standalone example:\n"
620                                       "%s\n"
621                                       "Errors: %s\n\n"
622                                       "Combining with previous example:\n"
623                                       "%s\n"
624                                       "Errors: %s\n\n"
625                                       "Adding headers, wrappers:\n"
626                                       "%s\n"
627                                       "Errors: %s\n\n",
628                                       get_ccan_file_contents(file[0]),
629                                       err[0],
630                                       get_ccan_file_contents(file[1]),
631                                       err[1],
632                                       get_ccan_file_contents(file[2]),
633                                       err[2]);
634                         } else {
635                                 error = tal_fmt(score,
636                                       "Standalone example:\n"
637                                       "%s\n"
638                                       "Errors: %s\n\n"
639                                       "Adding headers, wrappers:\n"
640                                       "%s\n"
641                                       "Errors: %s\n\n",
642                                       get_ccan_file_contents(file[0]),
643                                       err[0],
644                                       get_ccan_file_contents(file[1]),
645                                       err[1]);
646                         }
647                 }
648                 score_file_error(score, i, 0, "%s", error);
649                 /* This didn't work, so not a candidate for combining. */
650                 prev = NULL;
651
652         next:
653                 ;
654         }
655
656         /* An extra point if they all compiled without warnings. */
657         if (!list_empty(&m->examples)) {
658                 score->total++;
659                 if (!warnings)
660                         score->score++;
661         }
662 }
663
664 struct ccanlint examples_compile = {
665         .key = "examples_compile",
666         .name = "Module examples compile",
667         .check = build_examples,
668         .can_run = can_run,
669         .needs = "examples_exist module_builds"
670 };
671
672 REGISTER_TEST(examples_compile);