xref: /freebsd/crypto/krb5/src/util/cstyle-file.py (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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