Blame src/util/cstyle-file.py

Packit fd8b60
# Copyright (C) 2012 by the Massachusetts Institute of Technology.
Packit fd8b60
# All rights reserved.
Packit fd8b60
#
Packit fd8b60
# Export of this software from the United States of America may
Packit fd8b60
#   require a specific license from the United States Government.
Packit fd8b60
#   It is the responsibility of any person or organization contemplating
Packit fd8b60
#   export to obtain such a license before exporting.
Packit fd8b60
#
Packit fd8b60
# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
Packit fd8b60
# distribute this software and its documentation for any purpose and
Packit fd8b60
# without fee is hereby granted, provided that the above copyright
Packit fd8b60
# notice appear in all copies and that both that copyright notice and
Packit fd8b60
# this permission notice appear in supporting documentation, and that
Packit fd8b60
# the name of M.I.T. not be used in advertising or publicity pertaining
Packit fd8b60
# to distribution of the software without specific, written prior
Packit fd8b60
# permission.  Furthermore if you modify this software you must label
Packit fd8b60
# your software as modified software and not distribute it in such a
Packit fd8b60
# fashion that it might be confused with the original M.I.T. software.
Packit fd8b60
# M.I.T. makes no representations about the suitability of
Packit fd8b60
# this software for any purpose.  It is provided "as is" without express
Packit fd8b60
# or implied warranty.
Packit fd8b60
Packit fd8b60
# This program checks for some kinds of MIT krb5 coding style
Packit fd8b60
# violations in a single file.  Checked violations include:
Packit fd8b60
#
Packit fd8b60
#   Line is too long
Packit fd8b60
#   Tabs violations
Packit fd8b60
#   Trailing whitespace and final blank lines
Packit fd8b60
#   Comment formatting errors
Packit fd8b60
#   Preprocessor statements in function bodies
Packit fd8b60
#   Misplaced braces
Packit fd8b60
#   Space before paren in function call, or no space after if/for/while
Packit fd8b60
#   Parenthesized return expression
Packit fd8b60
#   Space after cast operator, or no space before * in cast operator
Packit fd8b60
#   Line broken before binary operator
Packit fd8b60
#   Lack of spaces around binary operator (sometimes)
Packit fd8b60
#   Assignment at the beginning of an if conditional
Packit fd8b60
#   Use of prohibited string functions
Packit fd8b60
#   Lack of braces around 2+ line flow control body
Packit fd8b60
#   Incorrect indentation as determined by emacs c-mode (if possible)
Packit fd8b60
#
Packit fd8b60
# This program does not check for the following:
Packit fd8b60
#
Packit fd8b60
#   Anything outside of a function body except line length/whitespace
Packit fd8b60
#   Anything non-syntactic (proper cleanup flow control, naming, etc.)
Packit fd8b60
#   UTF-8 violations
Packit fd8b60
#   Implicit tests against NULL or '\0'
Packit fd8b60
#   Inner-scope variable declarations
Packit fd8b60
#   Over- or under-parenthesization
Packit fd8b60
#   Long or deeply nested function bodies
Packit fd8b60
#   Syntax of function calls through pointers
Packit fd8b60
Packit fd8b60
import os
Packit fd8b60
import re
Packit fd8b60
import sys
Packit fd8b60
from subprocess import call
Packit fd8b60
from tempfile import NamedTemporaryFile
Packit fd8b60
Packit fd8b60
def warn(ln, msg):
Packit fd8b60
    print('%5d  %s' % (ln, msg))
Packit fd8b60
Packit fd8b60
Packit fd8b60
# If lines[0] indicates the krb5 C style, try to use emacs to reindent
Packit fd8b60
# a copy of lines.  Return None if the file does not use the krb5 C
Packit fd8b60
# style or if the emacs batch reindent is unsuccessful.
Packit fd8b60
def emacs_reindent(lines):
Packit fd8b60
    if 'c-basic-offset: 4; indent-tabs-mode: nil' not in lines[0]:
Packit fd8b60
        return None
Packit fd8b60
Packit fd8b60
    util_dir = os.path.dirname(sys.argv[0])
Packit fd8b60
    cstyle_el = os.path.join(util_dir, 'krb5-c-style.el')
Packit fd8b60
    reindent_el = os.path.join(util_dir, 'krb5-batch-reindent.el')
Packit fd8b60
    with NamedTemporaryFile(suffix='.c', mode='w+') as f:
Packit fd8b60
        f.write(''.join(lines))
Packit fd8b60
        f.flush()
Packit fd8b60
        args = ['emacs', '-q', '-batch', '-l', cstyle_el, '-l', reindent_el,
Packit fd8b60
                f.name]
Packit fd8b60
        with open(os.devnull, 'w') as devnull:
Packit fd8b60
            try:
Packit fd8b60
                st = call(args, stdin=devnull, stdout=devnull, stderr=devnull)
Packit fd8b60
                if st != 0:
Packit fd8b60
                    return None
Packit fd8b60
            except OSError:
Packit fd8b60
                # Fail gracefully if emacs isn't installed.
Packit fd8b60
                return None
Packit fd8b60
        f.seek(0)
Packit fd8b60
        ilines = f.readlines()
Packit fd8b60
        f.close()
Packit fd8b60
        return ilines
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_length(line, ln):
Packit fd8b60
    if len(line) > 79 and not line.startswith(' * Copyright'):
Packit fd8b60
        warn(ln, 'Length exceeds 79 characters')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_tabs(line, ln, allow_tabs, seen_tab):
Packit fd8b60
    if not allow_tabs:
Packit fd8b60
        if '\t' in line:
Packit fd8b60
            warn(ln, 'Tab character in file which does not allow tabs')
Packit fd8b60
    else:
Packit fd8b60
        if ' \t' in line:
Packit fd8b60
            warn(ln, 'Tab character immediately following space')
Packit fd8b60
        if '        ' in line and seen_tab:
Packit fd8b60
            warn(ln, '8+ spaces in file which uses tabs')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_trailing_whitespace(line, ln):
Packit fd8b60
    if line and line[-1] in ' \t':
Packit fd8b60
        warn(ln, 'Trailing whitespace')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_comment(lines, ln):
Packit fd8b60
    align = lines[0].index('/*') + 1
Packit fd8b60
    if not lines[0].lstrip().startswith('/*'):
Packit fd8b60
        warn(ln, 'Multi-line comment begins after code')
Packit fd8b60
    for line in lines[1:]:
Packit fd8b60
        ln += 1
Packit fd8b60
        if len(line) <= align or line[align] != '*':
Packit fd8b60
            warn(ln, 'Comment line does not have * aligned with top')
Packit fd8b60
        elif line[:align].lstrip() != '':
Packit fd8b60
            warn(ln, 'Garbage before * in comment line')
Packit fd8b60
    if not lines[-1].rstrip().endswith('*/'):
Packit fd8b60
        warn(ln, 'Code after end of multi-line comment')
Packit fd8b60
    if len(lines) > 2 and (lines[0].strip() not in ('/*', '/**') or
Packit fd8b60
                           lines[-1].strip() != '*/'):
Packit fd8b60
        warn(ln, 'Comment is 3+ lines but is not formatted as block comment')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_preprocessor(line, ln):
Packit fd8b60
    if line.startswith('#'):
Packit fd8b60
        warn(ln, 'Preprocessor statement in function body')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_braces(line, ln):
Packit fd8b60
    # Strip out one-line initializer expressions.
Packit fd8b60
    line = re.sub(r'=\s*{.*}', '', line)
Packit fd8b60
    if line.lstrip().startswith('{') and not line.startswith('{'):
Packit fd8b60
        warn(ln, 'Un-cuddled open brace')
Packit fd8b60
    if re.search(r'{\s*\S', line):
Packit fd8b60
        warn(ln, 'Code on line after open brace')
Packit fd8b60
    if re.search(r'\S.*}', line):
Packit fd8b60
        warn(ln, 'Code on line before close brace')
Packit fd8b60
Packit fd8b60
Packit fd8b60
# This test gives false positives on some function pointer type
Packit fd8b60
# declarations or casts.  Avoid this by using typedefs.
Packit fd8b60
def check_space_before_paren(line, ln):
Packit fd8b60
    for m in re.finditer(r'([\w]+)(\s*)\(', line):
Packit fd8b60
        ident, ws = m.groups()
Packit fd8b60
        if ident in ('void', 'char', 'int', 'long', 'unsigned'):
Packit fd8b60
            pass
Packit fd8b60
        elif ident in ('if', 'for', 'while', 'switch'):
Packit fd8b60
            if not ws:
Packit fd8b60
                warn(ln, 'No space after flow control keyword')
Packit fd8b60
        elif ident != 'return':
Packit fd8b60
            if ws:
Packit fd8b60
                warn(ln, 'Space before parenthesis in function call')
Packit fd8b60
Packit fd8b60
    if re.search(r' \)', line):
Packit fd8b60
        warn(ln, 'Space before close parenthesis')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_parenthesized_return(line, ln):
Packit fd8b60
    if re.search(r'return\s*\(.*\);', line):
Packit fd8b60
        warn(ln, 'Parenthesized return expression')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_cast(line, ln):
Packit fd8b60
    # We can't reliably distinguish cast operators from parenthesized
Packit fd8b60
    # expressions or function call parameters without a real C parser,
Packit fd8b60
    # so we use some heuristics.  A cast operator is followed by an
Packit fd8b60
    # expression, which usually begins with an identifier or an open
Packit fd8b60
    # paren.  A function call or parenthesized expression is never
Packit fd8b60
    # followed by an identifier and only rarely by an open paren.  We
Packit fd8b60
    # won't detect a cast operator when it's followed by an expression
Packit fd8b60
    # beginning with '*', since it's hard to distinguish that from a
Packit fd8b60
    # multiplication operator.  We will get false positives from
Packit fd8b60
    # "(*fp) (args)" and "if (condition) statement", but both of those
Packit fd8b60
    # are erroneous anyway.
Packit fd8b60
    for m in re.finditer(r'\(([^(]+)\)(\s*)[a-zA-Z_(]', line):
Packit fd8b60
        if m.group(2):
Packit fd8b60
            warn(ln, 'Space after cast operator (or inline if/while body)')
Packit fd8b60
        # Check for casts like (char*) which should have a space.
Packit fd8b60
        if re.search(r'[^\s\*]\*+$', m.group(1)):
Packit fd8b60
            warn(ln, 'No space before * in cast operator')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_binary_operator(line, ln):
Packit fd8b60
    binop = r'(\+|-|\*|/|%|\^|==|=|!=|<=|<|>=|>|&&|&|\|\||\|)'
Packit fd8b60
    if re.match(r'\s*' + binop + r'\s', line):
Packit fd8b60
        warn(ln - 1, 'Line broken before binary operator')
Packit fd8b60
    for m in re.finditer(r'(\s|\w)' + binop + r'(\s|\w)', line):
Packit fd8b60
        before, op, after = m.groups()
Packit fd8b60
        if not before.isspace() and not after.isspace():
Packit fd8b60
            warn(ln, 'No space before or after binary operator')
Packit fd8b60
        elif not before.isspace():
Packit fd8b60
            warn(ln, 'No space before binary operator')
Packit fd8b60
        elif op not in ('-', '*', '&') and not after.isspace():
Packit fd8b60
            warn(ln, 'No space after binary operator')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_assignment_in_conditional(line, ln):
Packit fd8b60
    # Check specifically for if statements; we allow assignments in
Packit fd8b60
    # loop expressions.
Packit fd8b60
    if re.search(r'if\s*\(+\w+\s*=[^=]', line):
Packit fd8b60
        warn(ln, 'Assignment in if conditional')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def indent(line):
Packit fd8b60
    return len(re.match('\s*', line).group(0).expandtabs())
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_unbraced_flow_body(line, ln, lines):
Packit fd8b60
    if re.match(r'\s*do$', line):
Packit fd8b60
        warn(ln, 'do statement without braces')
Packit fd8b60
        return
Packit fd8b60
Packit fd8b60
    m = re.match(r'\s*(})?\s*else(\s*if\s*\(.*\))?\s*({)?\s*$', line)
Packit fd8b60
    if m and (m.group(1) is None) != (m.group(3) is None):
Packit fd8b60
        warn(ln, 'One arm of if/else statement braced but not the other')
Packit fd8b60
Packit fd8b60
    if (re.match('\s*(if|else if|for|while)\s*\(.*\)$', line) or
Packit fd8b60
        re.match('\s*else$', line)):
Packit fd8b60
        base = indent(line)
Packit fd8b60
        # Look at the next two lines (ln is 1-based so lines[ln] is next).
Packit fd8b60
        if indent(lines[ln]) > base and indent(lines[ln + 1]) > base:
Packit fd8b60
            warn(ln, 'Body is 2+ lines but has no braces')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_bad_string_fn(line, ln):
Packit fd8b60
    # This is intentionally pretty fuzzy so that we catch the whole scanf
Packit fd8b60
    if re.search(r'\W(strcpy|strcat|sprintf|\w*scanf)\W', line):
Packit fd8b60
        warn(ln, 'Prohibited string function')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_indentation(line, indented_lines, ln):
Packit fd8b60
    if not indented_lines:
Packit fd8b60
        return
Packit fd8b60
Packit fd8b60
    if ln - 1 >= len(indented_lines):
Packit fd8b60
        # This should only happen when the emacs reindent removed
Packit fd8b60
        # blank lines from the input file, but check.
Packit fd8b60
        if line.strip() == '':
Packit fd8b60
            warn(ln, 'Trailing blank line')
Packit fd8b60
        return
Packit fd8b60
Packit fd8b60
    if line != indented_lines[ln - 1].rstrip('\r\n'):
Packit fd8b60
        warn(ln, 'Indentation may be incorrect')
Packit fd8b60
Packit fd8b60
Packit fd8b60
def check_file(lines):
Packit fd8b60
    # Check if this file allows tabs.
Packit fd8b60
    if len(lines) == 0:
Packit fd8b60
        return
Packit fd8b60
    allow_tabs = 'indent-tabs-mode: nil' not in lines[0]
Packit fd8b60
    seen_tab = False
Packit fd8b60
    indented_lines = emacs_reindent(lines)
Packit fd8b60
Packit fd8b60
    in_function = False
Packit fd8b60
    comment = []
Packit fd8b60
    ln = 0
Packit fd8b60
    for line in lines:
Packit fd8b60
        ln += 1
Packit fd8b60
        line = line.rstrip('\r\n')
Packit fd8b60
        seen_tab = seen_tab or ('\t' in line)
Packit fd8b60
Packit fd8b60
        # Check line structure issues before altering the line.
Packit fd8b60
        check_indentation(line, indented_lines, ln)
Packit fd8b60
        check_length(line, ln)
Packit fd8b60
        check_tabs(line, ln, allow_tabs, seen_tab)
Packit fd8b60
        check_trailing_whitespace(line, ln)
Packit fd8b60
Packit fd8b60
        # Strip out single-line comments the contents of string literals.
Packit fd8b60
        if not comment:
Packit fd8b60
            line = re.sub(r'/\*.*?\*/', '', line)
Packit fd8b60
            line = re.sub(r'"(\\.|[^"])*"', '""', line)
Packit fd8b60
Packit fd8b60
        # Parse out and check multi-line comments.  (Ignore code on
Packit fd8b60
        # the first or last line; check_comment will warn about it.)
Packit fd8b60
        if comment or '/*' in line:
Packit fd8b60
            comment.append(line)
Packit fd8b60
            if '*/' in line:
Packit fd8b60
                check_comment(comment, ln - len(comment) + 1)
Packit fd8b60
                comment = []
Packit fd8b60
            continue
Packit fd8b60
Packit fd8b60
        # Warn if we see a // comment and ignore anything following.
Packit fd8b60
        if '//' in line:
Packit fd8b60
            warn(ln, '// comment')
Packit fd8b60
            line = re.sub(r'//.*/', '', line)
Packit fd8b60
Packit fd8b60
        if line.startswith('{'):
Packit fd8b60
            in_function = True
Packit fd8b60
        elif line.startswith('}'):
Packit fd8b60
            in_function = False
Packit fd8b60
Packit fd8b60
        if in_function:
Packit fd8b60
            check_preprocessor(line, ln)
Packit fd8b60
            check_braces(line, ln)
Packit fd8b60
            check_space_before_paren(line, ln)
Packit fd8b60
            check_parenthesized_return(line, ln)
Packit fd8b60
            check_cast(line, ln)
Packit fd8b60
            check_binary_operator(line, ln)
Packit fd8b60
            check_assignment_in_conditional(line, ln)
Packit fd8b60
            check_unbraced_flow_body(line, ln, lines)
Packit fd8b60
            check_bad_string_fn(line, ln)
Packit fd8b60
Packit fd8b60
    if lines[-1] == '':
Packit fd8b60
        warn(ln, 'Blank line at end of file')
Packit fd8b60
Packit fd8b60
Packit fd8b60
if len(sys.argv) == 1:
Packit fd8b60
    lines = sys.stdin.readlines()
Packit fd8b60
elif len(sys.argv) == 2:
Packit fd8b60
    f = open(sys.argv[1])
Packit fd8b60
    lines = f.readlines()
Packit fd8b60
    f.close()
Packit fd8b60
else:
Packit fd8b60
    sys.stderr.write('Usage: cstyle-file [filename]\n')
Packit fd8b60
    sys.exit(1)
Packit fd8b60
Packit fd8b60
check_file(lines)