// // quickie - a small fast C++ Wiki Wiki // Copyright (C) 2005 Peter Miller // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. // // MANIFEST: functions to impliment the check class // #pragma implementation "check" #include #include #include #include #include #include #include #include static rcstring calc_user_name() { struct passwd *pw = getpwuid(getuid()); if (!pw) quitter.fatal_error("uid %d not found", getuid()); const char *cp = strchr(pw->pw_gecos, ','); if (!cp) return pw->pw_gecos; return rcstring(pw->pw_gecos, size_t(cp - pw->pw_gecos)); } rcstring check::user_name(calc_user_name()); static rcstring calc_year() { time_t now; time(&now); struct tm *tmp = localtime(&now); char buffer[10]; strftime(buffer, sizeof(buffer), "%Y", tmp); return buffer; } rcstring check::year(calc_year()); check::~check() { if (fp && fclose(fp)) quitter.fatal_error_with_errno("close %s", fn.c_str()); } check::check(const rcstring &arg) : warning(false), limit(80), number_of_blank_lines(0), number_of_errors(0), line_number(0), dos_format(false), binary_format(false), fp(0), fn(arg), isa_c_file(false), isa_cxx_file(false), isa_h_file(false), cxx_warning(false), unprintable_ok(false), state(state_normal), has_copyright(false) { fp = fopen(fn.c_str(), "r"); if (!fp) quitter.fatal_error_with_errno("open %s", fn.c_str()); } void check::check_c_comment() { if (isa_h_file && !isa_c_file) { isa_h_file = false; isa_c_file = true; } if (isa_cxx_file) { quitter.message ( "%s: %d: C comment in a C++ file", fn.c_str(), line_number ); ++number_of_errors; } } void check::run_state_machine(int c) { if (!isa_c_file && !isa_cxx_file && !isa_h_file) return; switch (state) { case state_normal: // // This state is for the body of a C or C++ file. We aren't in // a string, or a character onstant, or any kind of comment. // switch (c) { case '/': state = state_slash; break; case '\'': state = state_single_quote; break; case '"': state = state_double_quote; break; default: break; } break; case state_slash: // // In this state we have seen a slash. It could be the start // of a C or C++ commant, or just one of the division operators. // switch (c) { case '/': // // We have seen the start of a C++ comment. // state = state_cxx_comment; if (isa_h_file && !isa_cxx_file) { isa_h_file = 0; isa_cxx_file = 1; } if (isa_c_file) { quitter.message ( "%s: %d: C++ comment in a C file", fn.c_str(), line_number ); ++number_of_errors; } break; case '*': // // We have seen the start of a C comment, but it could be // a Doxygen introducer, so we can't whine if it's a C++ // file just yet. // state = state_c_comment_begin; break; default: // // One of the division operators. // No need to change state. // state = state_normal; break; } break; case state_double_quote: // // In this state we have seen a double quote, and possibly // some content. We are waiting for the closing double quote. // switch (c) { case '\\': // // Start of an escape sequence. // state = state_double_quote_escape; break; case '"': case '\n': // // Normal and abnormal string constant termination. // state = state_normal; break; default: // // Still in the string. No need to change state. // break; } break; case state_double_quote_escape: // // We throw away the character immediately following the // backslash. Escape sequences can be longer than this, but // are uninteresting to the state machine. The only sequences // which can confuse the state machine are escaped backslash, // escaped double quote and escaped newline. // state = state_double_quote; break; case state_single_quote: // // In this state we have seen a single quote, and possibly // some content. We are waiting for the closing single quote. // switch (c) { case '\\': // // Start of an escape sequence. // state = state_single_quote_escape; break; case '\'': case '\n': // // Normal and abnormal character constant termination. // state = state_normal; break; default: // // Still in the character constant. No need to change state. // break; } break; case state_single_quote_escape: // // We throw away the character immediately following the // backslash. Escape sequences can be longer than this, but // are uninteresting to the state machine. The only sequences // which can confuse the state machine are escaped backslash, // escaped single quote and escaped newline. // state = state_single_quote; break; case state_cxx_comment: // // We ahve seen '/', '/', and possubly some content. // We are waiting for the newline which finishes the comment. // if (c == '\n') state = state_normal; break; case state_c_comment_begin: // // We have seen '/'and '*'. We are waiting for '*' which could // start a Doxygen comment, or anything else which indicates // the start of a normal C comment. // if (c == '*') state = state_c_comment_doxygen; else { check_c_comment(); state = state_c_comment; } break; case state_c_comment_doxygen: // // We have seen '/', '*' and '*'. // switch (c) { case '/': // // This is the end of a very short normal C comment. // check_c_comment(); state = state_normal; break; case '*': // // This is the start of a very ugly normal C comment. // check_c_comment(); state = state_c_comment_star; break; default: // // This is a Doxygen comment. It is allowed in both C and // C++ files, due to the limitations of Doxygen. Sigh. // state = state_c_comment_star; break; } break; case state_c_comment: // // We are in the body of a C comment. We are waiting for a '*' // which could start the comment terminator. // if (c == '*') state = state_c_comment_star; break; case state_c_comment_star: // // We have seen a '*' which could preceed a '/' to finish a C comment. // switch (c) { case '/': // // C comment terminator. // state = state_normal; break; case '*': // // Almost. The next '/' will end the comment. // break; default: // // No, back to the body of the comment. // state = state_c_comment; break; } break; } } bool check::check_copyright(const rcstring &line) { const char *cp = line.c_str(); cp = strstr(cp, "Copyright (C)"); if (!cp) return false; cp = strstr(cp, year.c_str()); if (!cp) return (0 != strstr(line.c_str(), "Free Software Foundation")); cp = strstr(cp, user_name.c_str()); return (cp != 0); } bool check::check_one_line() { ++line_number; int pos = 0; int unprintable = 0; bool white_space = false; bool line_contains_white_space = false; rcstring_accumulator sa; for (;;) { int c = getc(fp); if (c == EOF) { if (ferror(fp)) quitter.fatal_error_with_errno("read %s", fn.c_str()); if (pos) { quitter.message ( "%s: %d: last line has no newline", fn.c_str(), line_number ); ++number_of_errors; goto done; } return false; } sa.push_back(c); run_state_machine(c); switch (c) { case '\f': ++pos; break; case '\r': c = getc(fp); if (c == EOF) { c = '\r'; ++unprintable; ++pos; break; } if (c != '\n') { ungetc(c, fp); ++unprintable; ++pos; white_space = true; break; } ++dos_format; // fall through... case '\n': done: if (unprintable && !unprintable_ok) { quitter.message ( "%s: %d: line contains %d unprintable character%s", fn.c_str(), line_number, unprintable, (unprintable == 1 ? "" : "s") ); ++number_of_errors; } if (white_space) { quitter.message ( "%s: %d: white space at end of line", fn.c_str(), line_number ); ++number_of_errors; } if (pos > limit && line_contains_white_space) { quitter.warning ( "%s: %d: line too long (by %d)", fn.c_str(), line_number, pos - limit ); ++number_of_errors; } // // See whether the line contains a copyright notice. // if (check_copyright(sa.mkstr())) has_copyright = true; sa.clear(); if (pos) number_of_blank_lines = 0; else ++number_of_blank_lines; return true; case '\t': pos = (pos + 8) & ~7; white_space = true; line_contains_white_space = true; break; case ' ': ++pos; white_space = true; line_contains_white_space = true; break; default: if (c == 0) binary_format = true; //assert(c != EOF); if (!isprint((unsigned char)c)) ++unprintable; ++pos; white_space = false; break; } } } static rcstring remove_baseline(const rcstring &arg) { const char *cp = arg.c_str(); while (cp[0] == 'b' && cp[1] == 'l') cp += 2; if (*cp == '/') return rcstring(cp + 1); return arg; } void check::run() { // // Skip over leading baseline symlinks. // rcstring sfn(remove_baseline(fn)); isa_c_file = sfn.ends_with(".c") || sfn.ends_with(".C"); isa_cxx_file = sfn.ends_with(".cc") || sfn.ends_with(".CC") || sfn.ends_with(".cpp") || sfn.ends_with(".CPP"); isa_h_file = sfn.ends_with(".h") || sfn.ends_with(".H"); unprintable_ok = false; if (sfn.starts_with("test/") && sfn.ends_with(".sh")) { limit = 510; unprintable_ok = true; } bool copyright_required = (sfn == fn) && (0 == strstr(sfn.c_str(), "/template/")); state = state_normal; number_of_errors = 0; number_of_blank_lines = 0; line_number = 0; dos_format = false; binary_format = false; while (check_one_line()) ; if (copyright_required && !has_copyright) { quitter.message ( "%s: no \"Copyright (C) %s %s\" message found", fn.c_str(), year.c_str(), user_name.c_str() ); ++number_of_errors; } if (dos_format) { quitter.message ( "%s: file in DOS format (must use UNIX format)", fn.c_str() ); ++number_of_errors; } if (number_of_blank_lines) { quitter.message ( "%s: found %d blank line%s at the end of the file", fn.c_str(), number_of_blank_lines, (number_of_blank_lines == 1 ? "" : "s") ); ++number_of_errors; } if (binary_format) { quitter.message ( "%s: file appears to be binary, so this file does not need to " "be changed, but you may want to consider replacing it with a " "plain-text file", fn.c_str() ); } else if (number_of_errors && !warning) { quitter.fatal_error ( "%s: found %d fatal error%s", fn.c_str(), number_of_errors, (number_of_errors == 1 ? "" : "s") ); } if (fclose(fp)) quitter.fatal_error_with_errno("close %s", fn.c_str()); fn.clear(); fp = 0; }