From owner-svn-src-user@freebsd.org Tue May 29 21:37:04 2018 Return-Path: Delivered-To: svn-src-user@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id BB42AF77E52 for ; Tue, 29 May 2018 21:37:03 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 5BF0A80410; Tue, 29 May 2018 21:37:03 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 16AB921939; Tue, 29 May 2018 21:37:03 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id w4TLb2Zo080172; Tue, 29 May 2018 21:37:02 GMT (envelope-from asomers@FreeBSD.org) Received: (from asomers@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id w4TLb2PF080170; Tue, 29 May 2018 21:37:02 GMT (envelope-from asomers@FreeBSD.org) Message-Id: <201805292137.w4TLb2PF080170@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: asomers set sender to asomers@FreeBSD.org using -f From: Alan Somers Date: Tue, 29 May 2018 21:37:02 +0000 (UTC) To: src-committers@freebsd.org, svn-src-user@freebsd.org Subject: svn commit: r334355 - in user/asomers: . style9 X-SVN-Group: user X-SVN-Commit-Author: asomers X-SVN-Commit-Paths: in user/asomers: . style9 X-SVN-Commit-Revision: 334355 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-user@freebsd.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: "SVN commit messages for the experimental " user" src tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 29 May 2018 21:37:04 -0000 Author: asomers Date: Tue May 29 21:37:02 2018 New Revision: 334355 URL: https://svnweb.freebsd.org/changeset/base/334355 Log: user/asomers/style9: automatic style verifier This python script can automatically detect many common style(9) violations. Sponsored by: Spectra Logic Corp Added: user/asomers/ user/asomers/style9/ user/asomers/style9/badstyle.c (contents, props changed) user/asomers/style9/style9.py (contents, props changed) Added: user/asomers/style9/badstyle.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ user/asomers/style9/badstyle.c Tue May 29 21:37:02 2018 (r334355) @@ -0,0 +1,73 @@ +/* + * this is a comment + * with text on the last line */ + +/* func on same line as type; OK in a prototype */ +int foo(); + + +/* func on same line as type */ +int foo(){ +} +static const struct blargle *myfunc(int x, char *y, struct foo *f){ +} +/* this one is really long */ +static int zfs_close(vnode_t *vp, int flag, int count, offset_t offset, + cred_t *cr, caller_context_t *ct) + +/* Omit the space after some keywords */ +int +foo(){ + if(1) { + while(7){ + if (foo){ + } + } + } +} + +// A C++ style comment +int foo; //C++ style inline comment + + +/* Solo brace after control block */ +long +bar(){ + if (x) + { + 1; + } +} + +/* Empty loop with space before the semicolon */ +float +baz() +{ + for (i=0; i<10; i++) ; +} + + +/* bad inline function */ +inline static int zero(void); + +/* long control block without braces around single statement body */ +if (this + && that + && the other) + do_stuff(); + + +/* Return statement without parens around the argument */ +int +foo() +{ + return 0; +} + +/* Void return statement without parens. This is ok */ +void +voidfoo() +{ + return ; +} + Added: user/asomers/style9/style9.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ user/asomers/style9/style9.py Tue May 29 21:37:02 2018 (r334355) @@ -0,0 +1,352 @@ +#! /usr/bin/env python +import optparse +import re +import string +import sys + +class Line(str): + """String that carries a line number with it""" + @property + def lineNumber(self): + "Line number where this line first occurred in the file" + try: + return self._lineNumber + except AttributeError: + return -1 + + @lineNumber.setter + def lineNumber(self, value): + self._lineNumber = value + + +class FileScanner(object): + """Scans a file for a regex""" + antipattern = '' + strip_comments = False + flags = 0 + def __init__(self): + self._reobj = re.compile(self.pattern, self.flags) + if self.antipattern: + self.__antipattern_obj = re.compile(self.antipattern, self.flags) + self.__comment_reobj = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE + ) + + def _readlines(self, filename): + """Return an iterable that will yield each line in file, stripping + comments if self.strip_comments is set""" + f = open(filename, 'r') + if self.strip_comments: + reader = self._strip_comments(f.read()).split('\n') + else: + reader = f + n = 1 + for l in reader: + line = Line(l) + line.lineNumber = n + yield line + n += 1 + + def __replacer(self, match): + s = match.group(0) + if s.startswith('/'): + return "" + "\n" * s.count('\n') + else: + return s + + def _strip_comments(self, text): + #Thanks to MizardX on stackoverflow.com + return re.sub(self.__comment_reobj, self.__replacer, text) + + def finditer(self, filename): + """Returns a generator yielding every matching line in the file""" + f = self._readlines(filename) + for line in f: + mo = self._reobj.search(line) + if mo: + if not self.antipattern or not self.__antipattern_obj.search(line): + yield line + + def has_match(self, filename): + """Returns True iff at least one line in the file matches the pattern""" + f = self._readlines(filename) + for line in f: + mo = self._reobj.search(line) + if mo: + if not self.antipattern or not self.__antipattern_obj.search(line): + return True + break + return False + + +class Attribute(FileScanner): + """Use of __attribute__ outside of compiler-specific macro""" + pattern = '__attribute__' + + +class BraceSolo(FileScanner): + """Puts the opening brace of a control statement on its own line""" + strip_comments = True + pattern = r'^\s+{\s*$' + + +class BraceWithoutSpace(FileScanner): + """Omits a space before the { of a control block""" + strip_comments = True + pattern = r'[^a-zA-Z0-9_](if|while|for|switch)(\(|[^a-zA-Z0-9_;(]\S*\s*\().*\){' + + +class CppComment(FileScanner): + """C++ style comments""" + # Assume that if the first non-whitespace character of a line is '*', then + # the line is probably a continuation of a C-style comment + pattern = r'//' + antipattern = r'^\s*/?\*' + + +class EmptyLoopSemicolon(FileScanner): + """Puts space before the ; of a loop with no body""" + strip_comments = True + pattern = r'[^a-zA-Z0-9_](while|for)\s*\(.*\)\s+;' + + +class FuncInDeclaration(FileScanner): + """Calls a function in a variable declaration""" + strip_comments = True + pattern = r'^(\s+[a-zA-Z0-9_]+){2,}\s*=.*[a-zA-Z0-9_]\(' + + +class FuncOnSameLine(FileScanner): + """Function names on the same line as their return types in a definition""" + strip_comments = True + flags = re.MULTILINE | re.DOTALL + pattern = r'^[a-zA-Z0-9_][a-zA-Z0-9_ \t\r\f\v*]+[ \t\r\f\v]+\*?[a-zA-Z0-9_]+\([^)]*\)[{\t\r\f\v]*$' + + def finditer(self, filename): + """Returns a generator yielding every matching line in the file""" + file_as_str = self._strip_comments(open(filename, 'r').read()) + for mo in re.finditer(self._reobj, file_as_str): + line = Line(mo.group(0)) + line.lineNumber = file_as_str[0:mo.start(0)].count('\n') + 1 + yield line + + def has_match(self, filename): + """Returns True iff at least one line in the file matches the pattern""" + file_as_str = self._strip_comments(open(filename, 'r').read()) + mo = self._reobj.search(file_as_str) + if mo: + return True + else: + return False + + +class InlineStatic(FileScanner): + """Uses "inline static" instead of "static inline" """ + strip_comments = True + pattern = r'inline static' + + +class LongLines(FileScanner): + """Lines over 80 characters""" + def __init__(self): + pass + + def has_match(self, filename): + """Returns True iff at least one line in the file matches the pattern""" + f = open(filename, 'r') + for line in f: + expanded_line = string.expandtabs(line, 8) #re.sub('\t', ' ', line) + if len(expanded_line) > 81: #allow one character for the newline + return True + break + return False + + def finditer(self, filename): + """Returns a generator yielding every matching line in the file""" + for line in self._readlines(filename): + expanded_line = string.expandtabs(line, 8) #re.sub('\t', ' ', line) + if len(expanded_line) > 81: #allow one character for the newline + yield line + + +class MultilineFirst(FileScanner): + """Multiline comments with text on the first line""" + pattern = r'/\*.*\S+.*[^/\n]$' + + +class MultilineLast(FileScanner): + """Multiline comments with text on the last line""" + pattern = r'[a-zA-Z0-9_]+.*\*/' + antipattern = r'/\*' + + +class ReturnParens(FileScanner): + """Return statements must enclose their values in parenthesis""" + strip_comments = True + pattern = r'[^a-zA-Z0-9_]return\s*[^(; \t\n]' + +class RequireBraces(FileScanner): + """Always uses braces after a control statement""" + strip_comments = True + pattern = '^((.*\s+((if|else|elseif|for|do|switch))|([^}]*\s+while))\s*\(.*)$' + + def finditer(self, filename): + f = self._readlines(filename) + for line in f: + ctlStart = re.search(self.pattern, line) + if ctlStart: + curLine = ctlStart.group(0) + lParen = curLine.count('(') + rParen = curLine.count(')') + while lParen != rParen: + curLine = f.next() + lParen += curLine.count('(') + rParen += curLine.count(')') + if not '{' in curLine: + yield line + + def has_match(self, filename): + while self.finditer(filename): + return True + return False + + +class SpaceAfterKeyword(FileScanner): + """Omit a space after flow control keywords""" + strip_comments = True + pattern = r'[^a-zA-Z0-9_](if|while|for|return|switch|case)[^a-zA-Z0-9_ \t\r\f\v;]' + + +class TrailingWhitespace(FileScanner): + """Trailing whitespace""" + pattern = r'[ \t\r\f\v]$' + + +class UnnecessaryBraces(FileScanner): + """ Braces around a single line statement following a control block """ + pattern = '\s+(if|else|elseif|for|while|do|switch)\s*\(.*{' + ENDPATTERN = '^\s+}' + + def __init__(self): + self.strip_comments = True + self.ctlBlockLinesStack = [] + self.ctlBlockLines = 0 + super(UnnecessaryBraces, self).__init__() + + def finditer(self, filename): + f = self._readlines(filename) + for line in f: + if re.search(self.pattern, line): + # Started a new level of control block + self.ctlBlockLinesStack.append(self.ctlBlockLines) + self.ctlBlockLines = 0 + + elif re.search(self.ENDPATTERN, line): + # Ended a control block + if self.ctlBlockLines == 1: + yield line + if self.ctlBlockLinesStack: + self.ctlBlockLines += self.ctlBlockLinesStack.pop() + else: + # Just a regular line + self.ctlBlockLines += 1 + + def has_match(self, filename): + while self.finditer(filename): + return True + return False + +def main(): + usage = "usage: %prog [OPTIONS] FILE [FILE ...]" + parser = optparse.OptionParser(usage) + parser.add_option('-a', '--all', action="store_true", default=False, + help="Run all checks"); + parser.add_option('-v', '--verbose', action="store_true", default=False, + help="Print matching lines as well as filenames"); + + parser.add_option('--attribute', action="store_true", default=False, + help="Check for " + Attribute.__doc__); + parser.add_option('--brace-solo', action="store_true", default=False, + help="Check for " + BraceSolo.__doc__); + parser.add_option('--brace-without-space', action="store_true", + default=False, help="Check for " + BraceWithoutSpace.__doc__); + parser.add_option('--cpp-comments', action="store_true", default=False, + help="Check for " + CppComment.__doc__); + parser.add_option('--empty-loop-semicolon', action="store_true", + default=False, help="Check for " + EmptyLoopSemicolon.__doc__); + parser.add_option('--func-in-declaration', action="store_true", + default=False, help="Check for " + FuncInDeclaration.__doc__); + parser.add_option('--func-on-same-line', action="store_true", + default=False, help="Check for " + FuncOnSameLine.__doc__); + parser.add_option('--inline-static', action="store_true", default=False, + help="Check for " + InlineStatic.__doc__); + parser.add_option('--long-lines', action="store_true", default=False, + help="Check for " + LongLines.__doc__); + parser.add_option('--multiline-first', action="store_true", default=False, + help="Check for " + MultilineFirst.__doc__); + parser.add_option('--multiline-last', action="store_true", default=False, + help="Check for " + MultilineLast.__doc__); + parser.add_option('--return-parens', action="store_true", + default=False, help="Check for " + ReturnParens.__doc__); + parser.add_option('--space-after-keyword', action="store_true", + default=False, help="Check for " + SpaceAfterKeyword.__doc__); + parser.add_option('--trailing-whitespace', action="store_true", + default=False, help="Check for " + TrailingWhitespace.__doc__); + parser.add_option('--unnecessary-braces', action="store_true", + default=False, help="Check for " + UnnecessaryBraces.__doc__); + parser.add_option('--require-braces', action="store_true", + default=False, help="Check for " + RequireBraces.__doc__); + + (options, args) = parser.parse_args() + if len(args) < 1: + parser.error("Incorrect number of arguments") + + checks = [] + if options.all or options.attribute: + checks.append(Attribute()) + if options.all or options.brace_solo: + checks.append(BraceSolo()) + if options.all or options.brace_without_space: + checks.append(BraceWithoutSpace()) + if options.all or options.cpp_comments: + checks.append(CppComment()) + if options.all or options.empty_loop_semicolon: + checks.append(EmptyLoopSemicolon()) + if options.all or options.func_in_declaration: + checks.append(FuncInDeclaration()) + if options.all or options.inline_static: + checks.append(FuncOnSameLine()) + if options.all or options.inline_static: + checks.append(InlineStatic()) + if options.all or options.long_lines: + checks.append(LongLines()) + if options.all or options.multiline_first: + checks.append(MultilineFirst()) + if options.all or options.multiline_last: + checks.append(MultilineLast()) + if options.all or options.return_parens: + checks.append(ReturnParens()) + if options.all or options.space_after_keyword: + checks.append(SpaceAfterKeyword()) + if options.all or options.trailing_whitespace: + checks.append(TrailingWhitespace()) + if (options.all or options.unnecessary_braces) and not options.require_braces: + checks.append(UnnecessaryBraces()) + if options.require_braces: + checks.append(RequireBraces()) + + for check in checks: + #TODO: don't print check.desc if it has no matches + print "*** " + check.__doc__ + " ***" + for filename in args: + if options.verbose: + for line in check.finditer(filename): + print "%s:%d:%s" % (filename, line.lineNumber, line) + else: + if check.has_match(filename): + print filename + + +if __name__ == '__main__': + main()