|
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)
|