Enhance file_analysis preprocessor a little more, use in idempotent test.
authorRusty Russell <rusty@rustcorp.com.au>
Sun, 29 Mar 2009 11:30:35 +0000 (22:00 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Sun, 29 Mar 2009 11:30:35 +0000 (22:00 +1030)
tools/ccanlint/ccanlint.h
tools/ccanlint/file_analysis.c
tools/ccanlint/idempotent.c
tools/ccanlint/test/run-file_analysis.c

index 804f80249d9204b362032e592c29a52d5e7b66b6..16a410a98aecb3b3fa864d865515854f0e7ec572 100644 (file)
@@ -108,11 +108,16 @@ enum line_compiled {
        MAYBE_COMPILED,
 };
 
-/* Simple evaluator: if this pre-processor symbol is defined to this
- * value, is this line compiled? (Other symbols assumed undefined) */
+/* Simple evaluator.  If symbols are set this way, is this condition true?
+ * NULL values mean undefined, NULL symbol terminates. */
 enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
                                    const char *symbol,
-                                   unsigned int value);
+                                   const unsigned int *value, ...);
+
+/* Get token if it's equal to token. */
+bool get_token(const char **line, const char *token);
+/* Talloc copy of symbol token, or NULL.  Increment line. */
+char *get_symbol_token(void *ctx, const char **line);
 
 /* Similarly for ->doc_sections */
 struct list_head *get_ccan_file_docs(struct ccan_file *f);
index 2ede1f5b8cea7a1d59216e54266c74b2f1eb9fbc..dc23eb964b2f5ba3c49bf03a3e52dd3de1311d1b 100644 (file)
@@ -13,6 +13,7 @@
 #include <errno.h>
 #include <dirent.h>
 #include <ctype.h>
+#include <stdarg.h>
 
 char **get_ccan_file_lines(struct ccan_file *f)
 {
@@ -57,6 +58,7 @@ static void add_files(struct manifest *m, const char *dir)
 
                f = talloc(m, struct ccan_file);
                f->lines = NULL;
+               f->line_info = NULL;
                f->doc_sections = NULL;
                f->name = talloc_asprintf(f, "%s%s", dir, ent->d_name);
                if (lstat(f->name, &st) != 0)
@@ -242,7 +244,7 @@ static bool continues(const char *line)
 }
 
 /* Get token if it's equal to token. */
-static bool get_token(const char **line, const char *token)
+bool get_token(const char **line, const char *token)
 {
        unsigned int toklen;
 
@@ -261,7 +263,7 @@ static bool get_token(const char **line, const char *token)
        return false;
 }
 
-static char *get_symbol_token(void *ctx, const char **line)
+char *get_symbol_token(void *ctx, const char **line)
 {
        unsigned int toklen;
        char *ret;
@@ -421,47 +423,102 @@ struct line_info *get_ccan_line_info(struct ccan_file *f)
        return f->line_info;
 }
 
-enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
-                                   const char *symbol,
-                                   unsigned int value)
+struct symbol {
+       struct list_node list;
+       const char *name;
+       const unsigned int *value;
+};
+
+static struct symbol *find_symbol(struct list_head *syms, const char *sym)
 {
-       enum line_compiled ret;
+       struct symbol *i;
+
+       list_for_each(syms, i, list)
+               if (streq(sym, i->name))
+                       return i;
+       return NULL;
+}
+
+static enum line_compiled get_pp(struct pp_conditions *cond,
+                                struct list_head *syms)
+{
+       struct symbol *sym;
+       unsigned int val;
+       enum line_compiled parent, ret;
 
        /* No conditions?  Easy. */
        if (!cond)
                return COMPILED;
 
        /* Check we get here at all. */
-       ret = get_ccan_line_pp(cond->parent, symbol, value);
-       if (ret != COMPILED)
-               return ret;
+       parent = get_pp(cond->parent, syms);
+       if (parent == NOT_COMPILED)
+               return NOT_COMPILED;
+
+       if (cond->type == PP_COND_UNKNOWN)
+               return MAYBE_COMPILED;
+
+       sym = find_symbol(syms, cond->symbol);
+       if (!sym)
+               return MAYBE_COMPILED;
 
        switch (cond->type) {
        case PP_COND_IF:
-               if (streq(cond->symbol, symbol)) {
-                       if (!value == cond->inverse)
-                               return COMPILED;
-                       else
-                               return NOT_COMPILED;
-               }
-               /* Unknown symbol, will be 0. */
-               if (cond->inverse)
-                       return COMPILED;
-               return NOT_COMPILED;
+               /* Undefined is 0. */
+               val = sym->value ? *sym->value : 0;
+               if (!val == cond->inverse)
+                       ret = COMPILED;
+               else
+                       ret = NOT_COMPILED;
+               break;
 
        case PP_COND_IFDEF:
-               if (streq(cond->symbol, symbol)) {
-                       if (cond->inverse)
-                               return NOT_COMPILED;
-                       else
-                               return COMPILED;
-               }
-               /* Unknown symbol, assume undefined. */
-               if (cond->inverse)
-                       return COMPILED;
-               return NOT_COMPILED;
-               
-       default: /* Unknown. */
-               return MAYBE_COMPILED;
+               if (cond->inverse == !sym->value)
+                       ret = COMPILED;
+               else
+                       ret = NOT_COMPILED;
+               break;
+
+       default:
+               abort();
        }
+
+       /* If parent didn't know, NO == NO, but YES == MAYBE. */
+       if (parent == MAYBE_COMPILED && ret == COMPILED)
+               ret = MAYBE_COMPILED;
+       return ret;
 }
+
+static void add_symbol(struct list_head *head,
+                      const char *symbol, const unsigned int *value)
+{
+       struct symbol *sym = talloc(head, struct symbol);
+       sym->name = symbol;
+       sym->value = value;
+       list_add(head, &sym->list);
+}
+       
+enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
+                                   const char *symbol,
+                                   const unsigned int *value,
+                                   ...)
+{
+       enum line_compiled ret;
+       struct list_head *head;
+       va_list ap;
+
+       head = talloc(NULL, struct list_head);
+       list_head_init(head);
+
+       va_start(ap, value);
+       add_symbol(head, symbol, value);
+
+       while ((symbol = va_arg(ap, const char *)) != NULL) {
+               value = va_arg(ap, const unsigned int *);
+               add_symbol(head, symbol, value);
+       }
+       ret = get_pp(cond, head);
+       talloc_free(head);
+       return ret;
+}
+
index 38d48d8319676e9044ca631a423559d7657e3d11..ffe3d7380483bc3e4ee7b9fc1faf01ffd1cdbe9f 100644 (file)
@@ -22,81 +22,92 @@ static const char explain[]
   "...\n"
   "#endif /* MY_HEADER_H */\n";
 
-static char *get_ifndef_sym(char *line)
+static char *report_idem(struct ccan_file *f, char *sofar)
 {
-       line += strspn(line, SPACE_CHARS);
-       if (line[0] == '#')
-       {
-               line++;
-               line += strspn(line, SPACE_CHARS);
-               if (strstarts(line, "ifndef") && isspace(line[6]))
-                       return line+6+strspn(line+6, SPACE_CHARS);
-               else if (strstarts(line, "if"))
-               {
-                       line += 2;
-                       line += strspn(line, SPACE_CHARS);
-                       if (line[0] == '!')
-                       {
-                               line++;
-                               line += strspn(line, SPACE_CHARS);
-                               if (strstarts(line, "defined"))
-                               {
-                                       line += 7;
-                                       line += strspn(line, SPACE_CHARS);
-                                       if (line[0] == '(')
-                                       {
-                                               line++;
-                                               line += strspn(line,
-                                                       SPACE_CHARS);
-                                       }
-                                       return line;
-                               }
-                       }
-               }
+       struct line_info *line_info;
+       unsigned int i, first_preproc_line;
+       const char *line, *sym;
+
+       line_info = get_ccan_line_info(f);
+       if (f->num_lines < 3)
+               /* FIXME: We assume small headers probably uninteresting. */
+               return sofar;
+
+       for (i = 0; i < f->num_lines; i++) {
+               if (line_info[i].type == DOC_LINE
+                   || line_info[i].type == COMMENT_LINE)
+                       continue;
+               if (line_info[i].type == CODE_LINE)
+                       return talloc_asprintf_append(sofar,
+                             "%s:%u:expect first non-comment line to be #ifndef.\n", f->name, i+1);
+               else if (line_info[i].type == PREPROC_LINE)
+                       break;
        }
-       return NULL;
-}
 
-static int is_define(char *line, char *id, size_t id_len)
-{
-       line += strspn(line, SPACE_CHARS);
-       if (line[0] == '#')
-       {
-               line++;
-               line += strspn(line, SPACE_CHARS);
-               if (strstarts(line, "define") && isspace(line[6]))
-               {
-                       line += 6;
-                       line += strspn(line, SPACE_CHARS);
-                       if (strspn(line, IDENT_CHARS) == id_len &&
-                           memcmp(id, line, id_len) == 0)
-                               return 1;
-               }
+       /* No code at all?  Don't complain. */
+       if (i == f->num_lines)
+               return sofar;
+
+       first_preproc_line = i;
+       for (i = first_preproc_line+1; i < f->num_lines; i++) {
+               if (line_info[i].type == DOC_LINE
+                   || line_info[i].type == COMMENT_LINE)
+                       continue;
+               if (line_info[i].type == CODE_LINE)
+                       return talloc_asprintf_append(sofar,
+                             "%s:%u:expect second line to be #define.\n", f->name, i+1);
+               else if (line_info[i].type == PREPROC_LINE)
+                       break;
        }
-       return 0;
-}
 
-static char *report_idem(struct ccan_file *f, char *sofar)
-{
-       char **lines;
-       char *id;
-       size_t id_len;
+       /* No code at all?  Weird. */
+       if (i == f->num_lines)
+               return sofar;
 
-       lines = get_ccan_file_lines(f);
-       if (f->num_lines < 3)
-               /* FIXME: We assume small headers probably uninteresting. */
-               return NULL;
+       /* We expect a condition on this line. */
+       if (!line_info[i].cond) {
+               return talloc_asprintf_append(sofar,
+                                             "%s:%u:expected #ifndef.\n",
+                                             f->name, first_preproc_line+1);
+       }
+
+       line = f->lines[i];
 
-       id = get_ifndef_sym(lines[0]);
-       if (!id)
+       /* We expect the condition to be ! IFDEF <symbol>. */
+       if (line_info[i].cond->type != PP_COND_IFDEF
+           || !line_info[i].cond->inverse) {
                return talloc_asprintf_append(sofar,
-                       "%s:1:expect first line to be #ifndef.\n", f->name);
-       id_len = strspn(id, IDENT_CHARS);
+                                             "%s:%u:expected #ifndef.\n",
+                                             f->name, first_preproc_line+1);
+       }
 
-       if (!is_define(lines[1], id, id_len))
+       /* And this to be #define <symbol> */
+       if (!get_token(&line, "#"))
+               abort();
+       if (!get_token(&line, "define")) {
                return talloc_asprintf_append(sofar,
-                       "%s:2:expect second line to be '#define %.*s'.\n",
-                       f->name, (int)id_len, id);
+                             "%s:%u:expected '#define %s'.\n",
+                             f->name, i+1, line_info[i].cond->symbol);
+       }
+       sym = get_symbol_token(f, &line);
+       if (!sym || !streq(sym, line_info[i].cond->symbol)) {
+               return talloc_asprintf_append(sofar,
+                             "%s:%u:expected '#define %s'.\n",
+                             f->name, i+1, line_info[i].cond->symbol);
+       }
+
+       /* Rest of code should all be covered by that conditional. */
+       for (i++; i < f->num_lines; i++) {
+               unsigned int val = 0;
+               if (line_info[i].type == DOC_LINE
+                   || line_info[i].type == COMMENT_LINE)
+                       continue;
+               if (get_ccan_line_pp(line_info[i].cond, sym, &val)
+                   != NOT_COMPILED)
+                       return talloc_asprintf_append(sofar,
+                             "%s:%u:code outside idempotent region.\n",
+                             f->name, i+1);
+       }
 
        return sofar;
 }
index b52d19df5e6954140b5fc8b90fc0b508bd0403e7..4bc5950b85db3efb7609e3fd5ba8d4440a1be017 100644 (file)
@@ -100,7 +100,7 @@ int main(int argc, char *argv[])
        struct line_info *line_info;
        struct ccan_file *f = talloc(NULL, struct ccan_file);
 
-       plan_tests(NUM_LINES * 2 + 2 + 66);
+       plan_tests(NUM_LINES * 2 + 2 + 86);
 
        f->num_lines = NUM_LINES;
        f->line_info = NULL;
@@ -176,26 +176,55 @@ int main(int argc, char *argv[])
 
        /* Now check using interface. */
        for (i = 0; i < f->num_lines; i++) {
+               unsigned int val = 1;
                if (streq(testfile[i].line, "BAR")) {
-                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", 1)
-                           == COMPILED);
-                       ok1(get_ccan_line_pp(line_info[i].cond, "FOO", 1)
-                           == NOT_COMPILED);
+                       /* If we don't know if the TEST_H was undefined,
+                        * best we get is a MAYBE. */
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", &val,
+                                            NULL) == MAYBE_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", NULL,
+                                            NULL) == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", &val,
+                                            "TEST_H", NULL,
+                                            NULL) == COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", NULL,
+                                            "TEST_H", NULL,
+                                            NULL) == NOT_COMPILED);
                } else if (streq(testfile[i].line, "!BAR")) {
-                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", 1)
-                           == NOT_COMPILED);
-                       ok1(get_ccan_line_pp(line_info[i].cond, "FOO", 1)
-                           == COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", &val,
+                                            NULL) == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", NULL,
+                                            NULL) == MAYBE_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", &val,
+                                            "TEST_H", NULL,
+                                            NULL) == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "BAR", NULL,
+                                            "TEST_H", NULL,
+                                            NULL) == COMPILED);
                } else if (streq(testfile[i].line, "HAVE_BAR")) {
-                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_BAR", 1)
-                           == COMPILED);
-                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_BAR", 0)
-                           == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_BAR",
+                                            &val, NULL) == MAYBE_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_BAR",
+                                            &val, "TEST_H", NULL,
+                                            NULL) == COMPILED);
+                       val = 0;
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_BAR",
+                                            &val, NULL) == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_BAR",
+                                            &val, "TEST_H", NULL,
+                                            NULL) == NOT_COMPILED);
                } else if (streq(testfile[i].line, "HAVE_FOO")) {
-                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_FOO", 1)
-                           == COMPILED);
-                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_FOO", 0)
-                           == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_FOO",
+                                            &val, NULL) == MAYBE_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_FOO",
+                                            &val, "TEST_H", NULL,
+                                            NULL) == COMPILED);
+                       val = 0;
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_FOO",
+                                            &val, NULL) == NOT_COMPILED);
+                       ok1(get_ccan_line_pp(line_info[i].cond, "HAVE_FOO",
+                                            &val, "TEST_H", NULL,
+                                            NULL) == NOT_COMPILED);
                }
        }