#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 (cisalnum(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 void 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; 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; } else if (line_info[i].type == PREPROC_LINE) break; } /* No code at all? Don't complain. */ if (i == f->num_lines) return; 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; } else if (line_info[i].type == PREPROC_LINE) break; } /* No code at all? Weird. */ if (i == f->num_lines) return; /* We expect a condition on this line. */ if (!line_info[i].cond) { score_file_error(score, f, i+1, "Expected #ifndef"); return; } 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; } /* And this to be #define */ if (!get_token(&line, "#")) abort(); if (!get_token(&line, "define")) { score_file_error(score, f, i+1, "expected '#define %s'", line_info[i].cond->symbol); return; } sym = get_symbol_token(f, &line); if (!sym || !streq(sym, line_info[i].cond->symbol)) { score_file_error(score, f, i+1, "expected '#define %s'", line_info[i].cond->symbol); return; } /* 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; } } } 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) { check_idem(f, score); } 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);