1*7f2fe78bSCy Schubert# Copyright (C) 2012 by the Massachusetts Institute of Technology. 2*7f2fe78bSCy Schubert# All rights reserved. 3*7f2fe78bSCy Schubert# 4*7f2fe78bSCy Schubert# Export of this software from the United States of America may 5*7f2fe78bSCy Schubert# require a specific license from the United States Government. 6*7f2fe78bSCy Schubert# It is the responsibility of any person or organization contemplating 7*7f2fe78bSCy Schubert# export to obtain such a license before exporting. 8*7f2fe78bSCy Schubert# 9*7f2fe78bSCy Schubert# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 10*7f2fe78bSCy Schubert# distribute this software and its documentation for any purpose and 11*7f2fe78bSCy Schubert# without fee is hereby granted, provided that the above copyright 12*7f2fe78bSCy Schubert# notice appear in all copies and that both that copyright notice and 13*7f2fe78bSCy Schubert# this permission notice appear in supporting documentation, and that 14*7f2fe78bSCy Schubert# the name of M.I.T. not be used in advertising or publicity pertaining 15*7f2fe78bSCy Schubert# to distribution of the software without specific, written prior 16*7f2fe78bSCy Schubert# permission. Furthermore if you modify this software you must label 17*7f2fe78bSCy Schubert# your software as modified software and not distribute it in such a 18*7f2fe78bSCy Schubert# fashion that it might be confused with the original M.I.T. software. 19*7f2fe78bSCy Schubert# M.I.T. makes no representations about the suitability of 20*7f2fe78bSCy Schubert# this software for any purpose. It is provided "as is" without express 21*7f2fe78bSCy Schubert# or implied warranty. 22*7f2fe78bSCy Schubert 23*7f2fe78bSCy Schubert# This program checks for some kinds of MIT krb5 coding style 24*7f2fe78bSCy Schubert# violations in a single file. Checked violations include: 25*7f2fe78bSCy Schubert# 26*7f2fe78bSCy Schubert# Line is too long 27*7f2fe78bSCy Schubert# Tabs violations 28*7f2fe78bSCy Schubert# Trailing whitespace and final blank lines 29*7f2fe78bSCy Schubert# Comment formatting errors 30*7f2fe78bSCy Schubert# Preprocessor statements in function bodies 31*7f2fe78bSCy Schubert# Misplaced braces 32*7f2fe78bSCy Schubert# Space before paren in function call, or no space after if/for/while 33*7f2fe78bSCy Schubert# Parenthesized return expression 34*7f2fe78bSCy Schubert# Space after cast operator, or no space before * in cast operator 35*7f2fe78bSCy Schubert# Line broken before binary operator 36*7f2fe78bSCy Schubert# Lack of spaces around binary operator (sometimes) 37*7f2fe78bSCy Schubert# Assignment at the beginning of an if conditional 38*7f2fe78bSCy Schubert# Use of prohibited string functions 39*7f2fe78bSCy Schubert# Lack of braces around 2+ line flow control body 40*7f2fe78bSCy Schubert# Incorrect indentation as determined by emacs c-mode (if possible) 41*7f2fe78bSCy Schubert# 42*7f2fe78bSCy Schubert# This program does not check for the following: 43*7f2fe78bSCy Schubert# 44*7f2fe78bSCy Schubert# Anything outside of a function body except line length/whitespace 45*7f2fe78bSCy Schubert# Anything non-syntactic (proper cleanup flow control, naming, etc.) 46*7f2fe78bSCy Schubert# UTF-8 violations 47*7f2fe78bSCy Schubert# Implicit tests against NULL or '\0' 48*7f2fe78bSCy Schubert# Inner-scope variable declarations 49*7f2fe78bSCy Schubert# Over- or under-parenthesization 50*7f2fe78bSCy Schubert# Long or deeply nested function bodies 51*7f2fe78bSCy Schubert# Syntax of function calls through pointers 52*7f2fe78bSCy Schubert 53*7f2fe78bSCy Schubertimport os 54*7f2fe78bSCy Schubertimport re 55*7f2fe78bSCy Schubertimport sys 56*7f2fe78bSCy Schubertfrom subprocess import call 57*7f2fe78bSCy Schubertfrom tempfile import NamedTemporaryFile 58*7f2fe78bSCy Schubert 59*7f2fe78bSCy Schubertdef warn(ln, msg): 60*7f2fe78bSCy Schubert print('%5d %s' % (ln, msg)) 61*7f2fe78bSCy Schubert 62*7f2fe78bSCy Schubert 63*7f2fe78bSCy Schubert# If lines[0] indicates the krb5 C style, try to use emacs to reindent 64*7f2fe78bSCy Schubert# a copy of lines. Return None if the file does not use the krb5 C 65*7f2fe78bSCy Schubert# style or if the emacs batch reindent is unsuccessful. 66*7f2fe78bSCy Schubertdef emacs_reindent(lines): 67*7f2fe78bSCy Schubert if 'c-basic-offset: 4; indent-tabs-mode: nil' not in lines[0]: 68*7f2fe78bSCy Schubert return None 69*7f2fe78bSCy Schubert 70*7f2fe78bSCy Schubert util_dir = os.path.dirname(sys.argv[0]) 71*7f2fe78bSCy Schubert cstyle_el = os.path.join(util_dir, 'krb5-c-style.el') 72*7f2fe78bSCy Schubert reindent_el = os.path.join(util_dir, 'krb5-batch-reindent.el') 73*7f2fe78bSCy Schubert with NamedTemporaryFile(suffix='.c', mode='w+') as f: 74*7f2fe78bSCy Schubert f.write(''.join(lines)) 75*7f2fe78bSCy Schubert f.flush() 76*7f2fe78bSCy Schubert args = ['emacs', '-q', '-batch', '-l', cstyle_el, '-l', reindent_el, 77*7f2fe78bSCy Schubert f.name] 78*7f2fe78bSCy Schubert with open(os.devnull, 'w') as devnull: 79*7f2fe78bSCy Schubert try: 80*7f2fe78bSCy Schubert st = call(args, stdin=devnull, stdout=devnull, stderr=devnull) 81*7f2fe78bSCy Schubert if st != 0: 82*7f2fe78bSCy Schubert return None 83*7f2fe78bSCy Schubert except OSError: 84*7f2fe78bSCy Schubert # Fail gracefully if emacs isn't installed. 85*7f2fe78bSCy Schubert return None 86*7f2fe78bSCy Schubert f.seek(0) 87*7f2fe78bSCy Schubert ilines = f.readlines() 88*7f2fe78bSCy Schubert f.close() 89*7f2fe78bSCy Schubert return ilines 90*7f2fe78bSCy Schubert 91*7f2fe78bSCy Schubert 92*7f2fe78bSCy Schubertdef check_length(line, ln): 93*7f2fe78bSCy Schubert if len(line) > 79 and not line.startswith(' * Copyright'): 94*7f2fe78bSCy Schubert warn(ln, 'Length exceeds 79 characters') 95*7f2fe78bSCy Schubert 96*7f2fe78bSCy Schubert 97*7f2fe78bSCy Schubertdef check_tabs(line, ln, allow_tabs, seen_tab): 98*7f2fe78bSCy Schubert if not allow_tabs: 99*7f2fe78bSCy Schubert if '\t' in line: 100*7f2fe78bSCy Schubert warn(ln, 'Tab character in file which does not allow tabs') 101*7f2fe78bSCy Schubert else: 102*7f2fe78bSCy Schubert if ' \t' in line: 103*7f2fe78bSCy Schubert warn(ln, 'Tab character immediately following space') 104*7f2fe78bSCy Schubert if ' ' in line and seen_tab: 105*7f2fe78bSCy Schubert warn(ln, '8+ spaces in file which uses tabs') 106*7f2fe78bSCy Schubert 107*7f2fe78bSCy Schubert 108*7f2fe78bSCy Schubertdef check_trailing_whitespace(line, ln): 109*7f2fe78bSCy Schubert if line and line[-1] in ' \t': 110*7f2fe78bSCy Schubert warn(ln, 'Trailing whitespace') 111*7f2fe78bSCy Schubert 112*7f2fe78bSCy Schubert 113*7f2fe78bSCy Schubertdef check_comment(lines, ln): 114*7f2fe78bSCy Schubert align = lines[0].index('/*') + 1 115*7f2fe78bSCy Schubert if not lines[0].lstrip().startswith('/*'): 116*7f2fe78bSCy Schubert warn(ln, 'Multi-line comment begins after code') 117*7f2fe78bSCy Schubert for line in lines[1:]: 118*7f2fe78bSCy Schubert ln += 1 119*7f2fe78bSCy Schubert if len(line) <= align or line[align] != '*': 120*7f2fe78bSCy Schubert warn(ln, 'Comment line does not have * aligned with top') 121*7f2fe78bSCy Schubert elif line[:align].lstrip() != '': 122*7f2fe78bSCy Schubert warn(ln, 'Garbage before * in comment line') 123*7f2fe78bSCy Schubert if not lines[-1].rstrip().endswith('*/'): 124*7f2fe78bSCy Schubert warn(ln, 'Code after end of multi-line comment') 125*7f2fe78bSCy Schubert if len(lines) > 2 and (lines[0].strip() not in ('/*', '/**') or 126*7f2fe78bSCy Schubert lines[-1].strip() != '*/'): 127*7f2fe78bSCy Schubert warn(ln, 'Comment is 3+ lines but is not formatted as block comment') 128*7f2fe78bSCy Schubert 129*7f2fe78bSCy Schubert 130*7f2fe78bSCy Schubertdef check_preprocessor(line, ln): 131*7f2fe78bSCy Schubert if line.startswith('#'): 132*7f2fe78bSCy Schubert warn(ln, 'Preprocessor statement in function body') 133*7f2fe78bSCy Schubert 134*7f2fe78bSCy Schubert 135*7f2fe78bSCy Schubertdef check_braces(line, ln): 136*7f2fe78bSCy Schubert # Strip out one-line initializer expressions. 137*7f2fe78bSCy Schubert line = re.sub(r'=\s*{.*}', '', line) 138*7f2fe78bSCy Schubert if line.lstrip().startswith('{') and not line.startswith('{'): 139*7f2fe78bSCy Schubert warn(ln, 'Un-cuddled open brace') 140*7f2fe78bSCy Schubert if re.search(r'{\s*\S', line): 141*7f2fe78bSCy Schubert warn(ln, 'Code on line after open brace') 142*7f2fe78bSCy Schubert if re.search(r'\S.*}', line): 143*7f2fe78bSCy Schubert warn(ln, 'Code on line before close brace') 144*7f2fe78bSCy Schubert 145*7f2fe78bSCy Schubert 146*7f2fe78bSCy Schubert# This test gives false positives on some function pointer type 147*7f2fe78bSCy Schubert# declarations or casts. Avoid this by using typedefs. 148*7f2fe78bSCy Schubertdef check_space_before_paren(line, ln): 149*7f2fe78bSCy Schubert for m in re.finditer(r'([\w]+)(\s*)\(', line): 150*7f2fe78bSCy Schubert ident, ws = m.groups() 151*7f2fe78bSCy Schubert if ident in ('void', 'char', 'int', 'long', 'unsigned'): 152*7f2fe78bSCy Schubert pass 153*7f2fe78bSCy Schubert elif ident in ('if', 'for', 'while', 'switch'): 154*7f2fe78bSCy Schubert if not ws: 155*7f2fe78bSCy Schubert warn(ln, 'No space after flow control keyword') 156*7f2fe78bSCy Schubert elif ident != 'return': 157*7f2fe78bSCy Schubert if ws: 158*7f2fe78bSCy Schubert warn(ln, 'Space before parenthesis in function call') 159*7f2fe78bSCy Schubert 160*7f2fe78bSCy Schubert if re.search(r' \)', line): 161*7f2fe78bSCy Schubert warn(ln, 'Space before close parenthesis') 162*7f2fe78bSCy Schubert 163*7f2fe78bSCy Schubert 164*7f2fe78bSCy Schubertdef check_parenthesized_return(line, ln): 165*7f2fe78bSCy Schubert if re.search(r'return\s*\(.*\);', line): 166*7f2fe78bSCy Schubert warn(ln, 'Parenthesized return expression') 167*7f2fe78bSCy Schubert 168*7f2fe78bSCy Schubert 169*7f2fe78bSCy Schubertdef check_cast(line, ln): 170*7f2fe78bSCy Schubert # We can't reliably distinguish cast operators from parenthesized 171*7f2fe78bSCy Schubert # expressions or function call parameters without a real C parser, 172*7f2fe78bSCy Schubert # so we use some heuristics. A cast operator is followed by an 173*7f2fe78bSCy Schubert # expression, which usually begins with an identifier or an open 174*7f2fe78bSCy Schubert # paren. A function call or parenthesized expression is never 175*7f2fe78bSCy Schubert # followed by an identifier and only rarely by an open paren. We 176*7f2fe78bSCy Schubert # won't detect a cast operator when it's followed by an expression 177*7f2fe78bSCy Schubert # beginning with '*', since it's hard to distinguish that from a 178*7f2fe78bSCy Schubert # multiplication operator. We will get false positives from 179*7f2fe78bSCy Schubert # "(*fp) (args)" and "if (condition) statement", but both of those 180*7f2fe78bSCy Schubert # are erroneous anyway. 181*7f2fe78bSCy Schubert for m in re.finditer(r'\(([^(]+)\)(\s*)[a-zA-Z_(]', line): 182*7f2fe78bSCy Schubert if m.group(2): 183*7f2fe78bSCy Schubert warn(ln, 'Space after cast operator (or inline if/while body)') 184*7f2fe78bSCy Schubert # Check for casts like (char*) which should have a space. 185*7f2fe78bSCy Schubert if re.search(r'[^\s\*]\*+$', m.group(1)): 186*7f2fe78bSCy Schubert warn(ln, 'No space before * in cast operator') 187*7f2fe78bSCy Schubert 188*7f2fe78bSCy Schubert 189*7f2fe78bSCy Schubertdef check_binary_operator(line, ln): 190*7f2fe78bSCy Schubert binop = r'(\+|-|\*|/|%|\^|==|=|!=|<=|<|>=|>|&&|&|\|\||\|)' 191*7f2fe78bSCy Schubert if re.match(r'\s*' + binop + r'\s', line): 192*7f2fe78bSCy Schubert warn(ln - 1, 'Line broken before binary operator') 193*7f2fe78bSCy Schubert for m in re.finditer(r'(\s|\w)' + binop + r'(\s|\w)', line): 194*7f2fe78bSCy Schubert before, op, after = m.groups() 195*7f2fe78bSCy Schubert if not before.isspace() and not after.isspace(): 196*7f2fe78bSCy Schubert warn(ln, 'No space before or after binary operator') 197*7f2fe78bSCy Schubert elif not before.isspace(): 198*7f2fe78bSCy Schubert warn(ln, 'No space before binary operator') 199*7f2fe78bSCy Schubert elif op not in ('-', '*', '&') and not after.isspace(): 200*7f2fe78bSCy Schubert warn(ln, 'No space after binary operator') 201*7f2fe78bSCy Schubert 202*7f2fe78bSCy Schubert 203*7f2fe78bSCy Schubertdef check_assignment_in_conditional(line, ln): 204*7f2fe78bSCy Schubert # Check specifically for if statements; we allow assignments in 205*7f2fe78bSCy Schubert # loop expressions. 206*7f2fe78bSCy Schubert if re.search(r'if\s*\(+\w+\s*=[^=]', line): 207*7f2fe78bSCy Schubert warn(ln, 'Assignment in if conditional') 208*7f2fe78bSCy Schubert 209*7f2fe78bSCy Schubert 210*7f2fe78bSCy Schubertdef indent(line): 211*7f2fe78bSCy Schubert return len(re.match('\s*', line).group(0).expandtabs()) 212*7f2fe78bSCy Schubert 213*7f2fe78bSCy Schubert 214*7f2fe78bSCy Schubertdef check_unbraced_flow_body(line, ln, lines): 215*7f2fe78bSCy Schubert if re.match(r'\s*do$', line): 216*7f2fe78bSCy Schubert warn(ln, 'do statement without braces') 217*7f2fe78bSCy Schubert return 218*7f2fe78bSCy Schubert 219*7f2fe78bSCy Schubert m = re.match(r'\s*(})?\s*else(\s*if\s*\(.*\))?\s*({)?\s*$', line) 220*7f2fe78bSCy Schubert if m and (m.group(1) is None) != (m.group(3) is None): 221*7f2fe78bSCy Schubert warn(ln, 'One arm of if/else statement braced but not the other') 222*7f2fe78bSCy Schubert 223*7f2fe78bSCy Schubert if (re.match('\s*(if|else if|for|while)\s*\(.*\)$', line) or 224*7f2fe78bSCy Schubert re.match('\s*else$', line)): 225*7f2fe78bSCy Schubert base = indent(line) 226*7f2fe78bSCy Schubert # Look at the next two lines (ln is 1-based so lines[ln] is next). 227*7f2fe78bSCy Schubert if indent(lines[ln]) > base and indent(lines[ln + 1]) > base: 228*7f2fe78bSCy Schubert warn(ln, 'Body is 2+ lines but has no braces') 229*7f2fe78bSCy Schubert 230*7f2fe78bSCy Schubert 231*7f2fe78bSCy Schubertdef check_bad_string_fn(line, ln): 232*7f2fe78bSCy Schubert # This is intentionally pretty fuzzy so that we catch the whole scanf 233*7f2fe78bSCy Schubert if re.search(r'\W(strcpy|strcat|sprintf|\w*scanf)\W', line): 234*7f2fe78bSCy Schubert warn(ln, 'Prohibited string function') 235*7f2fe78bSCy Schubert 236*7f2fe78bSCy Schubert 237*7f2fe78bSCy Schubertdef check_indentation(line, indented_lines, ln): 238*7f2fe78bSCy Schubert if not indented_lines: 239*7f2fe78bSCy Schubert return 240*7f2fe78bSCy Schubert 241*7f2fe78bSCy Schubert if ln - 1 >= len(indented_lines): 242*7f2fe78bSCy Schubert # This should only happen when the emacs reindent removed 243*7f2fe78bSCy Schubert # blank lines from the input file, but check. 244*7f2fe78bSCy Schubert if line.strip() == '': 245*7f2fe78bSCy Schubert warn(ln, 'Trailing blank line') 246*7f2fe78bSCy Schubert return 247*7f2fe78bSCy Schubert 248*7f2fe78bSCy Schubert if line != indented_lines[ln - 1].rstrip('\r\n'): 249*7f2fe78bSCy Schubert warn(ln, 'Indentation may be incorrect') 250*7f2fe78bSCy Schubert 251*7f2fe78bSCy Schubert 252*7f2fe78bSCy Schubertdef check_file(lines): 253*7f2fe78bSCy Schubert # Check if this file allows tabs. 254*7f2fe78bSCy Schubert if len(lines) == 0: 255*7f2fe78bSCy Schubert return 256*7f2fe78bSCy Schubert allow_tabs = 'indent-tabs-mode: nil' not in lines[0] 257*7f2fe78bSCy Schubert seen_tab = False 258*7f2fe78bSCy Schubert indented_lines = emacs_reindent(lines) 259*7f2fe78bSCy Schubert 260*7f2fe78bSCy Schubert in_function = False 261*7f2fe78bSCy Schubert comment = [] 262*7f2fe78bSCy Schubert ln = 0 263*7f2fe78bSCy Schubert for line in lines: 264*7f2fe78bSCy Schubert ln += 1 265*7f2fe78bSCy Schubert line = line.rstrip('\r\n') 266*7f2fe78bSCy Schubert seen_tab = seen_tab or ('\t' in line) 267*7f2fe78bSCy Schubert 268*7f2fe78bSCy Schubert # Check line structure issues before altering the line. 269*7f2fe78bSCy Schubert check_indentation(line, indented_lines, ln) 270*7f2fe78bSCy Schubert check_length(line, ln) 271*7f2fe78bSCy Schubert check_tabs(line, ln, allow_tabs, seen_tab) 272*7f2fe78bSCy Schubert check_trailing_whitespace(line, ln) 273*7f2fe78bSCy Schubert 274*7f2fe78bSCy Schubert # Strip out single-line comments the contents of string literals. 275*7f2fe78bSCy Schubert if not comment: 276*7f2fe78bSCy Schubert line = re.sub(r'/\*.*?\*/', '', line) 277*7f2fe78bSCy Schubert line = re.sub(r'"(\\.|[^"])*"', '""', line) 278*7f2fe78bSCy Schubert 279*7f2fe78bSCy Schubert # Parse out and check multi-line comments. (Ignore code on 280*7f2fe78bSCy Schubert # the first or last line; check_comment will warn about it.) 281*7f2fe78bSCy Schubert if comment or '/*' in line: 282*7f2fe78bSCy Schubert comment.append(line) 283*7f2fe78bSCy Schubert if '*/' in line: 284*7f2fe78bSCy Schubert check_comment(comment, ln - len(comment) + 1) 285*7f2fe78bSCy Schubert comment = [] 286*7f2fe78bSCy Schubert continue 287*7f2fe78bSCy Schubert 288*7f2fe78bSCy Schubert # Warn if we see a // comment and ignore anything following. 289*7f2fe78bSCy Schubert if '//' in line: 290*7f2fe78bSCy Schubert warn(ln, '// comment') 291*7f2fe78bSCy Schubert line = re.sub(r'//.*/', '', line) 292*7f2fe78bSCy Schubert 293*7f2fe78bSCy Schubert if line.startswith('{'): 294*7f2fe78bSCy Schubert in_function = True 295*7f2fe78bSCy Schubert elif line.startswith('}'): 296*7f2fe78bSCy Schubert in_function = False 297*7f2fe78bSCy Schubert 298*7f2fe78bSCy Schubert if in_function: 299*7f2fe78bSCy Schubert check_preprocessor(line, ln) 300*7f2fe78bSCy Schubert check_braces(line, ln) 301*7f2fe78bSCy Schubert check_space_before_paren(line, ln) 302*7f2fe78bSCy Schubert check_parenthesized_return(line, ln) 303*7f2fe78bSCy Schubert check_cast(line, ln) 304*7f2fe78bSCy Schubert check_binary_operator(line, ln) 305*7f2fe78bSCy Schubert check_assignment_in_conditional(line, ln) 306*7f2fe78bSCy Schubert check_unbraced_flow_body(line, ln, lines) 307*7f2fe78bSCy Schubert check_bad_string_fn(line, ln) 308*7f2fe78bSCy Schubert 309*7f2fe78bSCy Schubert if lines[-1] == '': 310*7f2fe78bSCy Schubert warn(ln, 'Blank line at end of file') 311*7f2fe78bSCy Schubert 312*7f2fe78bSCy Schubert 313*7f2fe78bSCy Schubertif len(sys.argv) == 1: 314*7f2fe78bSCy Schubert lines = sys.stdin.readlines() 315*7f2fe78bSCy Schubertelif len(sys.argv) == 2: 316*7f2fe78bSCy Schubert f = open(sys.argv[1]) 317*7f2fe78bSCy Schubert lines = f.readlines() 318*7f2fe78bSCy Schubert f.close() 319*7f2fe78bSCy Schubertelse: 320*7f2fe78bSCy Schubert sys.stderr.write('Usage: cstyle-file [filename]\n') 321*7f2fe78bSCy Schubert sys.exit(1) 322*7f2fe78bSCy Schubert 323*7f2fe78bSCy Schubertcheck_file(lines) 324