Path: blob/main/crypto/krb5/src/util/cstyle-file.py
34869 views
# Copyright (C) 2012 by the Massachusetts Institute of Technology.1# All rights reserved.2#3# Export of this software from the United States of America may4# require a specific license from the United States Government.5# It is the responsibility of any person or organization contemplating6# export to obtain such a license before exporting.7#8# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and9# distribute this software and its documentation for any purpose and10# without fee is hereby granted, provided that the above copyright11# notice appear in all copies and that both that copyright notice and12# this permission notice appear in supporting documentation, and that13# the name of M.I.T. not be used in advertising or publicity pertaining14# to distribution of the software without specific, written prior15# permission. Furthermore if you modify this software you must label16# your software as modified software and not distribute it in such a17# fashion that it might be confused with the original M.I.T. software.18# M.I.T. makes no representations about the suitability of19# this software for any purpose. It is provided "as is" without express20# or implied warranty.2122# This program checks for some kinds of MIT krb5 coding style23# violations in a single file. Checked violations include:24#25# Line is too long26# Tabs violations27# Trailing whitespace and final blank lines28# Comment formatting errors29# Preprocessor statements in function bodies30# Misplaced braces31# Space before paren in function call, or no space after if/for/while32# Parenthesized return expression33# Space after cast operator, or no space before * in cast operator34# Line broken before binary operator35# Lack of spaces around binary operator (sometimes)36# Assignment at the beginning of an if conditional37# Use of prohibited string functions38# Lack of braces around 2+ line flow control body39# Incorrect indentation as determined by emacs c-mode (if possible)40#41# This program does not check for the following:42#43# Anything outside of a function body except line length/whitespace44# Anything non-syntactic (proper cleanup flow control, naming, etc.)45# UTF-8 violations46# Implicit tests against NULL or '\0'47# Inner-scope variable declarations48# Over- or under-parenthesization49# Long or deeply nested function bodies50# Syntax of function calls through pointers5152import os53import re54import sys55from subprocess import call56from tempfile import NamedTemporaryFile5758def warn(ln, msg):59print('%5d %s' % (ln, msg))606162# If lines[0] indicates the krb5 C style, try to use emacs to reindent63# a copy of lines. Return None if the file does not use the krb5 C64# style or if the emacs batch reindent is unsuccessful.65def emacs_reindent(lines):66if 'c-basic-offset: 4; indent-tabs-mode: nil' not in lines[0]:67return None6869util_dir = os.path.dirname(sys.argv[0])70cstyle_el = os.path.join(util_dir, 'krb5-c-style.el')71reindent_el = os.path.join(util_dir, 'krb5-batch-reindent.el')72with NamedTemporaryFile(suffix='.c', mode='w+') as f:73f.write(''.join(lines))74f.flush()75args = ['emacs', '-q', '-batch', '-l', cstyle_el, '-l', reindent_el,76f.name]77with open(os.devnull, 'w') as devnull:78try:79st = call(args, stdin=devnull, stdout=devnull, stderr=devnull)80if st != 0:81return None82except OSError:83# Fail gracefully if emacs isn't installed.84return None85f.seek(0)86ilines = f.readlines()87f.close()88return ilines899091def check_length(line, ln):92if len(line) > 79 and not line.startswith(' * Copyright'):93warn(ln, 'Length exceeds 79 characters')949596def check_tabs(line, ln, allow_tabs, seen_tab):97if not allow_tabs:98if '\t' in line:99warn(ln, 'Tab character in file which does not allow tabs')100else:101if ' \t' in line:102warn(ln, 'Tab character immediately following space')103if ' ' in line and seen_tab:104warn(ln, '8+ spaces in file which uses tabs')105106107def check_trailing_whitespace(line, ln):108if line and line[-1] in ' \t':109warn(ln, 'Trailing whitespace')110111112def check_comment(lines, ln):113align = lines[0].index('/*') + 1114if not lines[0].lstrip().startswith('/*'):115warn(ln, 'Multi-line comment begins after code')116for line in lines[1:]:117ln += 1118if len(line) <= align or line[align] != '*':119warn(ln, 'Comment line does not have * aligned with top')120elif line[:align].lstrip() != '':121warn(ln, 'Garbage before * in comment line')122if not lines[-1].rstrip().endswith('*/'):123warn(ln, 'Code after end of multi-line comment')124if len(lines) > 2 and (lines[0].strip() not in ('/*', '/**') or125lines[-1].strip() != '*/'):126warn(ln, 'Comment is 3+ lines but is not formatted as block comment')127128129def check_preprocessor(line, ln):130if line.startswith('#'):131warn(ln, 'Preprocessor statement in function body')132133134def check_braces(line, ln):135# Strip out one-line initializer expressions.136line = re.sub(r'=\s*{.*}', '', line)137if line.lstrip().startswith('{') and not line.startswith('{'):138warn(ln, 'Un-cuddled open brace')139if re.search(r'{\s*\S', line):140warn(ln, 'Code on line after open brace')141if re.search(r'\S.*}', line):142warn(ln, 'Code on line before close brace')143144145# This test gives false positives on some function pointer type146# declarations or casts. Avoid this by using typedefs.147def check_space_before_paren(line, ln):148for m in re.finditer(r'([\w]+)(\s*)\(', line):149ident, ws = m.groups()150if ident in ('void', 'char', 'int', 'long', 'unsigned'):151pass152elif ident in ('if', 'for', 'while', 'switch'):153if not ws:154warn(ln, 'No space after flow control keyword')155elif ident != 'return':156if ws:157warn(ln, 'Space before parenthesis in function call')158159if re.search(r' \)', line):160warn(ln, 'Space before close parenthesis')161162163def check_parenthesized_return(line, ln):164if re.search(r'return\s*\(.*\);', line):165warn(ln, 'Parenthesized return expression')166167168def check_cast(line, ln):169# We can't reliably distinguish cast operators from parenthesized170# expressions or function call parameters without a real C parser,171# so we use some heuristics. A cast operator is followed by an172# expression, which usually begins with an identifier or an open173# paren. A function call or parenthesized expression is never174# followed by an identifier and only rarely by an open paren. We175# won't detect a cast operator when it's followed by an expression176# beginning with '*', since it's hard to distinguish that from a177# multiplication operator. We will get false positives from178# "(*fp) (args)" and "if (condition) statement", but both of those179# are erroneous anyway.180for m in re.finditer(r'\(([^(]+)\)(\s*)[a-zA-Z_(]', line):181if m.group(2):182warn(ln, 'Space after cast operator (or inline if/while body)')183# Check for casts like (char*) which should have a space.184if re.search(r'[^\s\*]\*+$', m.group(1)):185warn(ln, 'No space before * in cast operator')186187188def check_binary_operator(line, ln):189binop = r'(\+|-|\*|/|%|\^|==|=|!=|<=|<|>=|>|&&|&|\|\||\|)'190if re.match(r'\s*' + binop + r'\s', line):191warn(ln - 1, 'Line broken before binary operator')192for m in re.finditer(r'(\s|\w)' + binop + r'(\s|\w)', line):193before, op, after = m.groups()194if not before.isspace() and not after.isspace():195warn(ln, 'No space before or after binary operator')196elif not before.isspace():197warn(ln, 'No space before binary operator')198elif op not in ('-', '*', '&') and not after.isspace():199warn(ln, 'No space after binary operator')200201202def check_assignment_in_conditional(line, ln):203# Check specifically for if statements; we allow assignments in204# loop expressions.205if re.search(r'if\s*\(+\w+\s*=[^=]', line):206warn(ln, 'Assignment in if conditional')207208209def indent(line):210return len(re.match(r'\s*', line).group(0).expandtabs())211212213def check_unbraced_flow_body(line, ln, lines):214if re.match(r'\s*do$', line):215warn(ln, 'do statement without braces')216return217218m = re.match(r'\s*(})?\s*else(\s*if\s*\(.*\))?\s*({)?\s*$', line)219if m and (m.group(1) is None) != (m.group(3) is None):220warn(ln, 'One arm of if/else statement braced but not the other')221222if (re.match(r'\s*(if|else if|for|while)\s*\(.*\)$', line) or223re.match(r'\s*else$', line)):224base = indent(line)225# Look at the next two lines (ln is 1-based so lines[ln] is next).226if indent(lines[ln]) > base and indent(lines[ln + 1]) > base:227warn(ln, 'Body is 2+ lines but has no braces')228229230def check_bad_string_fn(line, ln):231# This is intentionally pretty fuzzy so that we catch the whole scanf232if re.search(r'\W(strcpy|strcat|sprintf|\w*scanf)\W', line):233warn(ln, 'Prohibited string function')234235236def check_indentation(line, indented_lines, ln):237if not indented_lines:238return239240if ln - 1 >= len(indented_lines):241# This should only happen when the emacs reindent removed242# blank lines from the input file, but check.243if line.strip() == '':244warn(ln, 'Trailing blank line')245return246247if line != indented_lines[ln - 1].rstrip('\r\n'):248warn(ln, 'Indentation may be incorrect')249250251def check_file(lines):252# Check if this file allows tabs.253if len(lines) == 0:254return255allow_tabs = 'indent-tabs-mode: nil' not in lines[0]256seen_tab = False257indented_lines = emacs_reindent(lines)258259in_function = False260comment = []261ln = 0262for line in lines:263ln += 1264line = line.rstrip('\r\n')265seen_tab = seen_tab or ('\t' in line)266267# Check line structure issues before altering the line.268check_indentation(line, indented_lines, ln)269check_length(line, ln)270check_tabs(line, ln, allow_tabs, seen_tab)271check_trailing_whitespace(line, ln)272273# Strip out single-line comments the contents of string literals.274if not comment:275line = re.sub(r'/\*.*?\*/', '', line)276line = re.sub(r'"(\\.|[^"])*"', '""', line)277278# Parse out and check multi-line comments. (Ignore code on279# the first or last line; check_comment will warn about it.)280if comment or '/*' in line:281comment.append(line)282if '*/' in line:283check_comment(comment, ln - len(comment) + 1)284comment = []285continue286287# Warn if we see a // comment and ignore anything following.288if '//' in line:289warn(ln, '// comment')290line = re.sub(r'//.*/', '', line)291292if line.startswith('{'):293in_function = True294elif line.startswith('}'):295in_function = False296297if in_function:298check_preprocessor(line, ln)299check_braces(line, ln)300check_space_before_paren(line, ln)301check_parenthesized_return(line, ln)302check_cast(line, ln)303check_binary_operator(line, ln)304check_assignment_in_conditional(line, ln)305check_unbraced_flow_body(line, ln, lines)306check_bad_string_fn(line, ln)307308if lines[-1] == '':309warn(ln, 'Blank line at end of file')310311312if len(sys.argv) == 1:313lines = sys.stdin.readlines()314elif len(sys.argv) == 2:315f = open(sys.argv[1])316lines = f.readlines()317f.close()318else:319sys.stderr.write('Usage: cstyle-file [filename]\n')320sys.exit(1)321322check_file(lines)323324325