utf8: don't allow NUL in decoded strings.
[ccan] / tools / ccanlint / tests / objects_build_with_stringchecks.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <tools/tools.h>
3 #include <ccan/str/str.h>
4 #include <ccan/foreach/foreach.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 #include "reduce_features.h"
17
18 static const char *uses_stringfuncs(struct manifest *m)
19 {
20         struct list_head *list;
21
22         foreach_ptr(list, &m->c_files, &m->h_files) {
23                 struct ccan_file *i;
24                 char *match;
25
26                 list_for_each(list, i, list) {
27                         if (tal_strreg(m, get_ccan_file_contents(i),
28                                    "(isalnum|isalpha|isascii|isblank|iscntrl"
29                                    "|isdigit|isgraph|islower|isprint|ispunct"
30                                    "|isspace|isupper|isxdigit"
31                                    "|strstr|strchr|strrchr)", &match)) {
32                                 if (verbose > 2)
33                                         printf("Matched '%s' in %s\n",
34                                                match, i->fullname);
35                                 return NULL;
36                         }
37                 }
38         }
39         return "No ctype.h or string functions found";
40 }
41
42 static void write_str(int fd, const char *str)
43 {
44         if (write(fd, str, strlen(str)) != strlen(str))
45                 err(1, "Writing to temporary file");
46 }
47
48 static int start_file(const char *filename)
49 {
50         int fd;
51         fd = open(filename, O_WRONLY|O_CREAT, 0600);
52         write_str(fd, "#define CCAN_STR_DEBUG 1\n#include <ccan/str/str.h>\n");
53         return fd;
54 }
55
56 static void test_compile(struct score *score,
57                          struct ccan_file *file,
58                          const char *filename,
59                          const char *flags,
60                          bool *errors,
61                          bool *warnings)
62 {
63         char *output, *compiled;
64
65         compiled = temp_file(score, "", filename);
66         if (!compile_object(score, filename, ccan_dir, compiler, flags,
67                             compiled, &output)) {
68                 score_file_error(score, file, 0,
69                                  "Compiling object files:\n%s",
70                                  output);
71                 *errors = true;
72         } else if (!streq(output, "")) {
73                 score_file_error(score, file, 0,
74                                  "Compiling object files gave warnings:\n%s",
75                                  output);
76                 *warnings = true;
77         }
78 }
79
80 static struct ccan_file *get_main_header(struct manifest *m)
81 {
82         struct ccan_file *f;
83
84         list_for_each(&m->h_files, f, list) {
85                 if (strstarts(f->name, m->basename)
86                     && strlen(f->name) == strlen(m->basename) + 2) {
87                         return f;
88                 }
89         }
90         /* Should not happen, since we passed main_header_exists! */
91         errx(1, "No main header?");
92 }
93
94 static void build_objects_with_stringchecks(struct manifest *m,
95                                             unsigned int *timeleft UNNEEDED,
96                                             struct score *score)
97 {
98         struct ccan_file *i;
99         bool errors = false, warnings = false;
100         char *tmp, *flags;
101         int tmpfd;
102
103         /* FIXME:: We need -I so local #includes work outside normal dir. */
104         flags = tal_fmt(score, "-I%s %s", m->dir, cflags);
105
106         /* Won't work into macros, but will get inline functions. */
107         if (list_empty(&m->c_files)) {
108                 char *line;
109                 i = get_main_header(m);
110                 tmp = temp_file(score, ".c", i->fullname);
111                 tmpfd = start_file(tmp);
112                 line = tal_fmt(score, "#include <ccan/%s/%s.h>\n",
113                                m->modname, m->basename);
114                 write_str(tmpfd, line);
115                 close(tmpfd);
116                 test_compile(score, i, tmp, flags, &errors, &warnings);
117         } else {
118                 list_for_each(&m->c_files, i, list) {
119                         tmp = temp_file(score, ".c", i->fullname);
120                         tmpfd = start_file(tmp);
121                         write_str(tmpfd, get_ccan_file_contents(i));
122                         close(tmpfd);
123                         test_compile(score, i, tmp, flags, &errors, &warnings);
124                 }
125         }
126
127         /* We don't fail ccanlint for this. */
128         score->pass = true;
129
130         score->total = 1;
131         if (!errors) {
132                 score->score = !warnings;
133         }
134 }
135
136 struct ccanlint objects_build_with_stringchecks = {
137         .key = "objects_build_with_stringchecks",
138         .name = "Module compiles with extra ctype.h and str function checks",
139         .check = build_objects_with_stringchecks,
140         .can_run = uses_stringfuncs,
141         .needs = "objects_build main_header_exists"
142 };
143 REGISTER_TEST(objects_build_with_stringchecks);