X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=tools%2Fccanlint%2Ftests%2Fheaders_idempotent.c;fp=tools%2Fccanlint%2Ftests%2Fheaders_idempotent.c;h=340a0698ac6aecbcb8baf85d231ce4e4d8d978fc;hb=051db34fb275491d4d5dfa5bf7970e8e525766d8;hp=0000000000000000000000000000000000000000;hpb=2926cafb52b9d95646d9dafa877d53f2368d8b2c;p=ccan diff --git a/tools/ccanlint/tests/headers_idempotent.c b/tools/ccanlint/tests/headers_idempotent.c new file mode 100644 index 00000000..340a0698 --- /dev/null +++ b/tools/ccanlint/tests/headers_idempotent.c @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char explain[] += "Headers usually start with the C preprocessor lines to prevent multiple\n" + "inclusions. These look like the following:\n" + "#ifndef CCAN__H\n" + "#define CCAN__H\n" + "...\n" + "#endif /* CCAN__H */\n"; + +static void fix_name(char *name) +{ + unsigned int i; + + for (i = 0; name[i]; i++) { + if (isalnum(name[i])) + name[i] = toupper(name[i]); + else + name[i] = '_'; + } +} + +static void handle_idem(struct manifest *m, struct score *score) +{ + struct file_error *e; + + list_for_each(&score->per_file_errors, e, list) { + char *name, *q, *tmpname; + FILE *out; + unsigned int i; + + /* Main header gets CCAN_FOO_H, others CCAN_FOO_XXX_H */ + if (strstarts(e->file->name, m->basename) + || strlen(e->file->name) == strlen(m->basename) + 2) + name = talloc_asprintf(score, "CCAN_%s_H", m->basename); + else + name = talloc_asprintf(score, "CCAN_%s_%s", + m->basename, e->file->name); + fix_name(name); + + q = talloc_asprintf(score, + "Should I wrap %s in #ifndef/#define %s for you?", + e->file->name, name); + if (!ask(q)) + continue; + + tmpname = maybe_temp_file(score, ".h", false, e->file->name); + out = fopen(tmpname, "w"); + if (!out) + err(1, "Opening %s", tmpname); + if (fprintf(out, "#ifndef %s\n#define %s\n", name, name) < 0) + err(1, "Writing %s", tmpname); + + for (i = 0; i < e->file->num_lines; i++) + if (fprintf(out, "%s\n", e->file->lines[i]) < 0) + err(1, "Writing %s", tmpname); + + if (fprintf(out, "#endif /* %s */\n", name) < 0) + err(1, "Writing %s", tmpname); + + if (fclose(out) != 0) + err(1, "Closing %s", tmpname); + + if (!move_file(tmpname, e->file->fullname)) + err(1, "Moving %s to %s", tmpname, e->file->fullname); + } +} + +static bool check_idem(struct ccan_file *f, struct score *score) +{ + 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 true; + + 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) { + score_file_error(score, f, i+1, + "Expect first non-comment line to be" + " #ifndef."); + return false; + } else if (line_info[i].type == PREPROC_LINE) + break; + } + + /* No code at all? Don't complain. */ + if (i == f->num_lines) + return true; + + 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) { + score_file_error(score, f, i+1, + "Expect second non-comment line to be" + " #define."); + return false; + } else if (line_info[i].type == PREPROC_LINE) + break; + } + + /* No code at all? Weird. */ + if (i == f->num_lines) + return true; + + /* We expect a condition on this line. */ + if (!line_info[i].cond) { + score_file_error(score, f, i+1, "Expected #ifndef"); + return false; + } + + line = f->lines[i]; + + /* We expect the condition to be ! IFDEF . */ + if (line_info[i].cond->type != PP_COND_IFDEF + || !line_info[i].cond->inverse) { + score_file_error(score, f, i+1, "Expected #ifndef"); + return false; + } + + /* And this to be #define */ + if (!get_token(&line, "#")) + abort(); + if (!get_token(&line, "define")) { + char *str = talloc_asprintf(score, + "expected '#define %s'", + line_info[i].cond->symbol); + score_file_error(score, f, i+1, str); + return false; + } + sym = get_symbol_token(f, &line); + if (!sym || !streq(sym, line_info[i].cond->symbol)) { + char *str = talloc_asprintf(score, + "expected '#define %s'", + line_info[i].cond->symbol); + score_file_error(score, f, i+1, str); + return false; + } + + /* 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, NULL) + != NOT_COMPILED) { + score_file_error(score, f, i+1, "code outside" + " idempotent region"); + return false; + } + } + + return true; +} + +static void check_idempotent(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + struct ccan_file *f; + + list_for_each(&m->h_files, f, list) { + if (!check_idem(f, score)) + score->error = "Headers are not idempotent"; + } + if (!score->error) { + score->pass = true; + score->score = score->total; + } +} + +struct ccanlint headers_idempotent = { + .key = "headers_idempotent", + .name = "Module headers are #ifndef/#define wrapped", + .check = check_idempotent, + .handle = handle_idem, + .needs = "" +}; + +REGISTER_TEST(headers_idempotent);