#! /usr/bin/perl
# -*- Mode: perl; -*-
#use Crypt::TripleDES;
#
# Check that coding standards have been followed.  This checks for common
# and easy to overlook errors, such as the use of non-portable system routines
#
# ToDo:
# Add support for
# /* style: allow:routine-name:count sig:value */
# where routine-name is the name of a routine that is on the warning list
# and value is a digital signature computed from the filename and routine-name
# by the lead programmer.  (currently implemented without the signature 
# check).
#
# Add line-length check.  Optionally warn of >80 character lines (or
# perhaps 80 + epsilon)
#
# Add option to output all of the "allow" styles encountered, with the file
# name and user (from the sig).
#
# Add an option to check that the "style" include files are used.
# E.g., if mpiimpl.h defines malloc/free, then make sure that mpiimpl.h
# (or a file that includes it) is used.  This could alternately use the
# 
# Add a check for literal lengths to routines that take string lengths.
# These should always use a constant (if statically declared, as in
# char foo[MAXNAME] or a variable if dynamically declared, as in 
# char *foo = (char *)malloc(size)).  The routines that should be checked
# include gethostname( x, len ) and strncpy( a, x, len )
#
# Add a summary that gives specific suggestions and reasons (e.g., 
# buffer overwrites in strxxx and sprintf a major and unnecessary source
# of errors in codes).  We could implement by having each error save the
# fact in $errorFound{name}, and a routine &${name}Explanation( fd ) that
# writes the explanation out to the given file.

# Find parse.sub; expect it in the tree or the INC array
$topdir = ".";
if (! -s "$topdir/maint/parse.sub") {
    my $curdir = `pwd`;
    $curdir =~ s/\r?\n//;
    # Try to chop off all but the mpich2 part
    if ($curdir =~ /^(.*\/mpich2)\//) {
	$topdir = $1;
	print "Found top of build tree: <$topdir>\n" if $debug;
    }
}
require "$topdir/maint/parse.sub";

$verbose = 0;
#
# Debug controls
$debug = 0;
$gDebugAllow = 0;

$showfiles = 0;
$depth = 1;
$curdir = ".";
%skipfiles = ();
$line_num = 0;
$allowRomioNames = 1;
$withinRomio = 0;
$skipTest = 1; 
$removeFileMissingCopyright = 0;
# Check for special args
@files = ();
#
# Checks to perform (all by default)
$checkComments  = 1;
$checkFunctions = 1;
$checkDefines   = 1;
$checkStyle     = 1;

$missingCopyright = 0;
@NoCopyright = ();

# avoidLevel controls the level of routine that should be avoided (setting this
# at 3 or lower suppresses some warnings).
$avoidLevel = 3;

# List the known C preprocessor directives
# Note particularly the "warning" is not in this list; warning is not
# standard.  Ditto for abort.  This list contains *ALL* of the valid
# and portable directive names.  DO NOT ADD TO IT.
%allowedDirectives = ( 'if' => 1,
		       'ifdef' => 1,
		       'ifndef' => 1,
		       'else' => 1,
		       'elif' => 1,
		       'endif' => 1,
		      'pragma' => 1,
		      'define' => 1,
		      'undef' => 1, 
		      'error' => 1,
		      'include' => 1,
		      );
# invalidDirectivesSeen records where such directives were seen
%invalidDirectivesSeen = ();
$invalidDirectivesReason = "The C language defines the set of preprocessor directives that may be used.  Some compilers support other directives, but programs that take advantage of those directives are not portable.";

# For various constructions, we keep track of where seen and provide reasons
%avoidConstructionsSeen = ();
%avoidConstructionsReasons = ( 
			      "//" => "// is not a valid C comment, even though some C compilers accept it.  If this is in a header file used ONLY in C++ programs, include the comment /* style:c++ header */ near the top of the file.  Even better, set the Emacs style with /* -*- Mode: C++; c-basic-offset:4 */",
			       "define" => "Names defined for the C preprocessor (i.e., with #define) should be clearly distinct from any name that may be defined by another, outside module, such as a system or third-party header file.  Unfortunately, system headers do not follow this rule (one system defined CACHE_SIZE), so we must.",
			       "define__" => "The C standard restricts the use of names with two leading underscores to \"the system\"; these names should not be used in applications.  The intent is to allow the compiler to have a private name space of preprocessor names",
			       "styleheader" => "The style header ensures that a common indentation size is used in code; it also helps to distinguish C from C++ header files",
			      );

foreach $arg (@ARGV) {
    if ($arg =~ /^--?showfiles/) { $showfiles = 1; }
    elsif( $arg =~ /^--?debug/) { $debug = 1; $gDebugAllow = 1; }
    elsif ( $arg =~ /^--?verbose/) { $verbose = 1; }
    elsif ( $arg =~ /^--?skipfile=(.*)/) { 
	my $fname = $1;
	$skipfiles{$fname} = 1; 
	# Add ./$fname if we didn't get that form
	if (! ($fname =~ /^\.\//)) {
	    $skipfiles{"./$fname"} = 1;
	}
	}
    elsif ( $arg =~ /^--?allowromio=no/) { $allowRomioNames = 0; }
    elsif ( $arg =~ /^--?allowromio/) { $allowRomioNames = 1; }
    elsif ( $arg =~ /^--?checktest/) { $skipTest = 0; }
    elsif ( $arg =~ /^--?distcheck/) {
	# Perform minimal checks for the distribution
	$checkComments  = 0;
	$checkFunctions = 0;
	$checkDefines   = 0;
	$checkStyle     = 0;
	# Remove files that are missing a copyright so that the 
        # distribution can continue
        # DO NOT CHANGE THIS.  Files should have a copyright or be on the
        # list of foreign files.
	$removeFileMissingCopyright = 1;
    }
    elsif ( $arg =~ /^--?conf=(.*)/ ) {
	my $conffile = $1;
	if (-s $conffile) {
	    require $conffile;
	}
	else {
	    print STDERR "Unable to find configuration file $conffile";
	}
    }
    elsif ( $arg =~ /^--?help/) {
	&printHelp();
	exit(2);
    }
    else {
	print "Adding $arg to files\n" if $debug;
	$files[$#files+1] = $arg;
    }
}

# Call once for each file
sub ProcessFile {
    my $filename = $_[0];
    %AllowedRoutines = ();
    # Include system defined names
    %AllowedDefines = ( 'SA_RESETHAND' => 'sys', 
		        'WORDS_BIGENDIAN' => 'autoconf',
			'EAGAIN' => 'sys',
			'ENOENT' => 'sys',
			'EIDRM'  => 'sys',
			'EPERM'  => 'sys',
			'SEEK_SET' => 'sys',
			'SEEK_CUR' => 'sys',
			'SEEK_END' => 'sys',
			);
    %defsUsed = ();
    my $cxx_comments_found = 0;
    my $cxx_header = 0;

    # Check for files that we should skip
    $f2name = "./" . $filename;
    if (defined($skipfiles{$filename}) || defined($skipfiles{$f2name})) {
	return;
    }

    # Check for romio
    if ($filename =~ /romio/) { $withinRomio = 1; }
    else                      { $withinRomio = 0; }

    &ClearPreamble;
    %warningmsgs = ();
    &ClearMismatchedStates;
    &ClearNested;
    $notInNested = 1;
    
    open( FD, "<$filename" ) || die "Could not open $filename";
    $line_num = 0;
    while (<FD>) {
	$line_num++;
	$errsrc = " in $filename";
	# Check for special comments
	if (/\/\*\s*style:\s*allow:(\w+):(\d+)\s+sig:(\d+)/) {
	    # Found a style comment.  Add the routine name to this 
	    # list of allowed routines.  Eventually, this may require
	    # a signed key
	    # Compute as (using the Crypt module)
            # $public = new Crypt::RSA::Key::Public(Filename => $keyfile, );
            # $rsa->verify( Message => "$1:$2:$filename", 
	    # Signature => $3, Key => $public ) && $AllowedRoutines{$1}=$2;
            # But for now, we do:
	    $AllowedRoutines{$1} = $2;
	    print "Allowing if $2 uses of $1 in $filename\n" if $gDebugAllow;
	}
	elsif ( /\/\*\s*style:\s*define:([_\w]*):(\d*)\s+sig:(\d*)/) {
	    # This style comment permits special definitions
	    #print "Allowing define of :$1:\n"; 
	    $AllowedDefines{$1} = $2;
	}
	elsif (/\/\*\s*style:\s*c\+\+ header/) {
	    $cxx_header = 1;
	}
	elsif (/\/\*\s*begin:nested\s*\*\//) {
	    $notInNested = 0;
	}
	elsif (/\/\*\s*end:nested\s*\*\//) {
	    $notInNested = 1;
	}
	# The preamble is within a comment
	&CheckForPreamble( $_ );
	$_ = StripComments( FD, $_ );
	if ($comment_line ne "") { &CheckForPreamble( $comment_line ); }
	# Check for C++ style comments
	if ($checkComments && /\/\//) {
	    $cxx_comments_found ++;
	    if ($cxx_comments_found == 1 && ! $cxx_header) {
		&PrintCurrentDirectory;
		print "C++ comment found in $filename:\n";
		$avoidConstructionsSeen{"//"} .= "$filename;";
		my $line = $_;
		if (/(.*)\n/) {
		    $line = "$1\n";
		}
		else {
		    $line = $_;
		}
		print $line;
	    }
	    s/\/\/.*//;
	}
	# If there is a DEBUG that isn't closed, read ahead
	if ($checkFunctions) {
	    if (/DEBUG\s*\(/) {
		($leading,$result,$remainder) = &GetBalancedParen( FD, $_ );
		$_ = $leading . $remainder;
	    }
	    # Skip any single function calls within DEBUG
	    if (!/DEBUG\([^\(\)]*\([^\(\)]*\)/ && !$withinRomio) {
		# FIXME: eventually we need to include ROMIO in these checks
		&CheckForBadSubs( $_, $filename );
	    }

	    # We also need to check for the use of MPI routines from within
	    # the code (only use NMPI), at least for all of the MPI
	    # implementation code.  Withing src/mpi/*, we could look for
	    # MPI_([A-Z][a-z0-9_]*)[^;]*; and then complain if $1 is not one
	    # of Status, Comm, Group, Win, File, Aint, Fint, Offset, Request,
	    # File, Info, Datatype .
	    if ($notInNested) {
		&CheckForNested( FD, $_, $filename );
	    }			# 
	    if (! $filename =~ /binding\/f77/ &&
		! $filename =~ /include \/nmpi.h/) {
		# Allow PMPI calls within the f77 binding
		&CheckForPMPI( FD, $_, $filename );
	    }
	}

	&CheckForBadDirectives( FD, $_, $filename );
	if ($checkDefines) {
	    &CheckForGuardedDefines( FD, $_, $filename );
	    &CheckForBadDefines( FD, $_, $filename );
	    &CheckForMismatchedStates ( FD, $_, $filename );
	    &CheckForBadDefinitions( FD, $_, $filename );
	}
    }
    # Output summary messages
    foreach $key (keys(%warningmsgs)) {
	&PrintCurrentDirectory;
	my $ntimes = $warningmsgs{$key};
	my $plural = "";
	if ($ntimes > 1) { $plural = "s"; }
	print "$key ($warningmsgs{$key} time$plural)\n";
    }
    if ($cxx_comments_found > 1 && !$cxx_header) {
	&PrintCurrentDirectory;
	print "$cxx_comments_found C++ comments found in $filename\n";
    }
    &ReportOnPreamble( $filename );
}

#
# Subroutines that we want to avoid.  The associated value is the level
# at which we want to avoid them:
#   1: Always wrong 
#   2: Discouraged, better substitutes exist
#   3: Discouraged but sometimes ok during development
# $avoidLevel is set to the level permissible (3 by default)
%avoidsubs = ( "fprintf" => 2, "fputs" => 1, "puts" => 1, "printf" => 2, 
               "malloc" => 1, "calloc" => 1, "free" => 1, "strdup" => 1, 
	       "realloc" => 1,
	       "assert" => 3, "perror" => 3, "getenv" => 3 );

# avoidSubsReasons gives the reasons for avoiding each of these 
$AvoidPrint = "For internationalization, messages must be indirected through a routine such as gettext.  Marking the print routines makes it possible to catch oversights in writing messages for the user.  Alternate routines are provided to providing printed output for debugging or for the developer";

$AvoidMalloc = "Dynamic memory allocation often causes problems, though the creation of both storage leaks and memory overwrites.  By using the MPIU_xxx names for memory allocation, it is easy to choose between the standard malloc/free/etc. versions and special versions that provide detailed memory correctness diagnostics, without requiring any special third-party software (which makes these particularly appropriate when the problem occurs only when running on hundreds of nodes)";

$IntPointerMalloc = "Routines that return pointers must be declared as such to vavoid failures on platforms where pointers and integers have different sizes, such as Alphas and IA64s.";

#
# Added extra information to strdup because of failures in ROMIO code that
# uses strdup without declaring it (failed on IA64)
%avoidSubsReasons = ( "fprintf" => $AvoidPrint,
		      "fputs" => $AvoidPrint,
		      "puts" => $AvoidPrint,
		      "printf" => $AvoidPrint,
		      "malloc" => $AvoidMalloc,
		      "calloc" => $AvoidMalloc,
		      "free" => $AvoidMalloc,
		      "strdup" => $AvoidMalloc . "\n" . $IntPointerMalloc,
		      "realloc" => $AvoidMalloc,
		      "assert" => "Assert is a handy way to check code while developing, but in released code, these cases should be handled with more attention to the message the the user will receive.",
		      "perror" => "For internationalization reasons, messages should use the internationalization support instead of perror.", 
		      "getenv" => "In parallel program environments, there is no guarantee that environment variables will be available to all processes." ); 

# avoidSubsSeen keeps track of which of these routines were seen (including
# the count).
%avoidSubsSeen = ();
#
# Guarded subs maps the routines being guarded to the guard definition
# (we may eventually use colon separated values if multiple definitions are
# allowed)
%guardedsubs = ( "alloca" => "HAVE_ALLOCA", 
		 "closedir" => "HAVE_CLOSEDIR", 
		 "fnmatch" => "HAVE_FNMATCH", 
		 "getloadavg" => "HAVE_GETLOADAVG", 
		 "getmntent" => "HAVE_GETMNTENT", 
		 "getpgrp" => "HAVE_GETPGRP", 
		 "memcmp" => "HAVE_MEMCMP", 
		 "mmap" => "HAVE_MMAP",    # Not really good enough
		 "setpgrp" => "HAVE_SETPGRP", 
		 "setvbuf" => "HAVE_SETVBUF", 
		 "strcoll" => "HAVE_STRCOLL", 
		 "strftime" => "HAVE_STRFTIME",
		 "utime" => "HAVE_UTIME", 
		 "vfork" => "HAVE_VFORK", 
		 "vprintf" => "HAVE_VPRINTF", 
		 "wait3" => "HAVE_WAIT3" );
# Removed strcmp because it is ok if one string is guaranteed to 
# have a terminating null.  This is still touchy, but it isn't the risk
# posed by the copy or cat routines, since it doesn't write to any memory
# location.
%bettersubs = ( "assert" => "MPIU_Assert or MPIU_Assertp",
		"strcpy" => "MPIU_Strncpy",
	        "strncpy" => "MPIU_Strncpy",
#		"strcmp" => "strncmp",
		"strcat" => "MPIU_Strnapp",
		"sprintf" => "MPIU_Snprintf", 
		"snprintf" => "MPIU_Snprintf",
		"strerror" => "MPIU_Strerror",
		);

$BetterString = "The standard string routines are risky because they provide no protection for input strings that are too long for the output strings.  Some of the \"n\" versions (e.g., strncat) are also risky because the length parameter is not the length of the buffer, and hence can be computed incorrectly.";
%betterSubsReasons = (
		      "assert" => "Assert is a handy way to include sanity checks in a code during development, but it does not generate good error messages should the assert fail for a user.  Instead, it is better to use customized assert routines that will provide a more user-centric error message.",
		      "strcpy" => $BetterString,
		      "strncpy" => $BetterString,
		      "strcmp"  => $BetterString,
		      "strcat"  => $BetterString,
		      "sprintf" => "Sprintf, like the standard string routines, is risky because the input parameters can create an output string that is too long for the provided output buffer, with no way to detect this and indicate an error.",
		      "snprintf" => "This routine is not portable; not all environments provide it.  More specifically, snprintf was not part of the 1990 ANSI/IOS C standard though it is part of C99.  Further, while some enviroments provide an snprintf entry point, they implement it by calling sprintf directly with no checks on the provided buffer, making snprintf no safer than sprintf.  Also, C99 and Single Unix Specification give different return values for snprintf.  With all of this, it isn't even clear what routine you are using when you use snprintf.",
		      "strerror" => "This routine is not portable to older systems (it was added in C89); not all environments provide it.  Applications may use it but should consider guarding it with a HAVE_STRERROR test."
		      );
sub CheckForBadSubs {
    my $curline = $_[0];
    my $filename = $_[1];
    foreach $subname (keys(%avoidsubs)) {
	if (/(?<!\w)$subname\s*\(/ && !defined($AllowedRoutines{$subname})) {
	    # Check for level
	    $routineLevel = $avoidsubs{$subname};
	    if ($routineLevel >= $avoidLevel) { next; }
	    $msg = "Warning: found $subname in $filename";
	    $avoidSubsSeen{$subname} = $avoidSubsSeen{$subname} + 1;
	    if (defined($warningmsgs{$msg})) { 
		$warningmsgs{$msg} = $warningmsgs{$msg} + 1;
	    }
	    else {
		$warningmsgs{$msg} = 1;
	    }
	}
    }
    # The guarded subs are ones that GNU autoconf is worried about.
    # This code should look for some sort of configure-style CPP value
    # and allow the ones that are properly guarded.
    # The mysterious (?<!\w) is a "negative look-behind match".  That is,
    # do *not* match a single "word" (alphanumeric+_) character.  This
    # keeps us from matching MPIU_vprintf when vprintf is in the list
    # of test routines and MPIU_vprintf is in the current line.
    foreach $subname (keys(%guardedsubs)) {
	if (/(?<!\w)$subname\s*\(/ && !defined($AllowedRoutines{$subname})) {
	    if (!defined($defsUsed{$guardedsubs{$subname}})) {
		&PrintCurrentDirectory;
		print "Caution: found reference to routine $subname in $filename\n";
	    }
	}
    }
    # See above for an explanation of (?<!\w)
    foreach $subname (@guardsubs) {
        if (/(?<!\w)$subname\s*\(/ && !defined($AllowedRoutines{$subname})) {
            $msg = "Caution: found $subname in $filename";
            if (defined($warningmsgs{$msg})) {
                $warningmsgs{$msg} = $warningmsgs{$msg} + 1;
            }
            else {
                $warningmsgs{$msg} = 1;
            }
        }
    }
#    foreach $subname (keys %bettersubs) {
#	if (/(?<!\w)$subname\s*\(/) {
#	    print "Caution: found reference to routine $subname in $filename\n";
#	    print "Consider using $bettersubs{$subname} instead\n";
#	}
#    }

    foreach $subname (keys %bettersubs) {
        if (/(?<!\w)$subname\s*\(/ && !defined($AllowedRoutines{$subname})) {
            $msg = "Caution: found $subname in $filename, consider $bettersubs{$subname}";
            if (defined($warningmsgs{$msg})) {
                $warningmsgs{$msg} = $warningmsgs{$msg} + 1;
            }
            else {
                $warningmsgs{$msg} = 1;
            }
	    $betterSubsSeen{$subname} = $betterSubsSeen{$subname} + 1;
        }
    }
}


@GoodNames = ( 'HAVE_[A-Za-z0-9_]*',  'USE_[A-Za-z0-9_]*',
	       'NEEDS_[A-Za-z0-9_]*', 'WITH_[A-Z0-9_]*',
	       'MPICH_SINGLE_THREADED', 'CHAR_PTR_IS_ADDRESS', 'FOO',
	       'STDC_HEADERS', '__cplusplus', 'IOV_MAX', 'S_ISLNK', 
	       'MPIR_[A-Za-z0-9_]*',
	       'MPID_[A-Za-z0-9_]*',
	       'MPIDU_[A-Za-z0-9_]*',   # More internal MPID names
	       'MPIU_[A-Za-z0-9_]*',
	       'MPIDI_[A-Za-z0-9_]*',   # Internal MPID names
	       'MPICH_[A-Za-z0-9_]*', 
	       'ROMIO_[A-Za-z0-9_]*', 
	       'MPIO_[A-Za-z0-9_]*', 
	       'DBG_[A-Z0-9_]*',
	       '_H_INCLUDED',           # Ok for header files
	       'DEBUG_[A-Z0-9_]*',
	       'F77_[A-Z_]*',
	       'MPI_BUILD_PROFILING',
	       'MPI_Info_f2c', 'MPI_Info_c2f', 'MPI_File_f2c', 'MPI_File_c2f', 
	       'MPI_DUP_FN',            # protected in f77 interface
	       'AFX_.*_INCLUDED_',      # Autogenerated defines for some Windows code
	       'APSTUDIO_INVOKED',      # Autogenerated ?
	       'PVFS_.*',                 # Used in some ROMIO files
	       'SA_RESTART', 'SA_INTERRUPT', # Sigaction values
	       'MPI_COMM_DUP_FN',       # The following 9 definitions
	       'MPI_TYPE_DUP_FN',       # are used to handle the attribute
	       'MPI_WIN_DUP_FN',        # dup functions
	       'MPI_COMM_NULL_COPY_FN', 
	       'MPI_COMM_NULL_DELETE_FN',
	       'MPI_TYPE_NULL_COPY_FN', 
	       'MPI_TYPE_NULL_DELETE_FN',
	       'MPI_WIN_NULL_COPY_FN', 
	       'MPI_WIN_NULL_DELETE_FN',
	       'MPI_SEEK_SET',          # These two are used in the C++ 
	       'MPI_MODE_RDONLY',       # interface to check for problems
	       );

@BadNames = ( 'rs6000', 'solaris', 'hpux', 'linux', 'HPUX', 'IRIX', 'LINUX', );

# These aren't really good names, we just want to suppress messages about them
# for now so that we can concentrate on the new MPICH2 code.
@GoodRomioNames = ( 'PROFILE', 'PRINT_ERR_MSG', 'HPUX', 'SPPUX', 'SX4',
		   'AIO_SUN', 'AIO_HANDLE_IN_AIOCB', 'NO_FD_IN_AIOCB',
		   'NO_AIO', 'AIO_PRIORITY_DEFAULT', 'AIO_SIGNOTIFY_NONE',
		   'MPISGI', 'CRAY', 'PARAGON', 'FREEBSD', 'LINUX',
		   'tflops', 'NFS', 'XFS', 'CB_CONFIG_LIST_DEBUG',
		   'SFS', 'HFS', 'UFS', 'MPI_hpux', 'FORTRANCAPS',
		   'MPILAM', 'NEEDS_ADIOCB_T', 'AGG_DEBUG', 'SOLARIS',
		   'IRIX', 'AIX', 'DEC', 'NEEDS_MPI_TEST', 'PFS', 
		   'PIOFS', 'MPICH', 'MPI_OFFSET_IS_INT', 
		   'MPI_COMBINER_NAMED', '_UNICOS', 'MPIHP', 
		   );
sub CheckName {
    my $name = $_[0];
    
    # Remove blanks from around name
    $name =~ s/\s//g;

    foreach $good (@GoodNames) {
	if ($name =~ /$good/) { return 0; }
    }
    if ($allowRomioNames && $withinRomio) {
	foreach $good (@GoodRomioNames) {
	    if ($name =~ /$good/) { return 0; }
	}
    }
    foreach $bad (@BadNames) {
	if ($name =~ /$bad/) { return 1; }
    }
    if ($name eq "__DATE__") {
	# We allow this because some older C compilers don't set it.
	# We should really check that it is not set withing an 
	#  #ifndef __DATE__... 
	return 0;
    }

    # Check for defining a __ name (not allowed by ``users'', only
    # by the environment.  In the case of GCC, instead of setting
    # things like __USE_MISC, you should set the appropriate _xxx_SOURCE
    # variable.)
    if ($name =~ /^__.*/) { return 1; }

    return 1;
}
# In the process for checking for bad definitions, we also *remember* all
# of the definitions that we see.  This helps with the tests for 
# guarded routines
sub CheckForBadDefines {
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    if ($curline =~ /^\s*#if/) {
	while ($curline =~ /\/$/) {
	    $curline .= <$FD>;
	    $line_num ++;
	}
	# Search for either ifdef or defined(...)
	if ($curline =~ /^\s*#ifdef\s*(.*)\s*/) {
	    $defname = $1;
	    $defname =~ s/\r*\n*//g;
	    $defsUsed{$defname} = "$filename:$curline";
	    print "$defname\n" if $debug;
	    if (! defined($AllowedDefines{$defname}) && 
		&CheckName( $defname ) ) {
		&PrintCurrentDirectory;
		print "Warning: ifdef name $defname used in $filename is nonstandard\n";
		$avoidConstructionsSeen{"define"} .= "$filename;";
	    }
	}
	elsif ($curline =~ /^\s*#if\s/ || $curline =~ /^\s*#elif\s/) {
	       while ($curline =~ /defined\(([^\)]*)\)/) {
		   $defname = $1;
		   $defsUsed{$defname} = "$filename:$curline";
		   print "$defname\n" if $debug;
		   if (! defined($AllowedDefines{$defname}) && 
		       &CheckName( $defname ) ) {
		       &PrintCurrentDirectory;
		       print "Warning: ifdef name $defname used in $filename is nonstandard\n";
		       $avoidConstructionsSeen{"define"} .= "$filename;";
		       }
		   $curline =~ s/defined\($defname\)//g;
	       }
	}
    }
}
# This checks for definitions that the user should not make (e.g., 
# #define __foo)
sub CheckForBadDefinitions {
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    if ($curline =~ /^#\s*define\s+(__\w*)/) {
	$defname = $1;
	if ($defname eq "__DATE__") {
	    # We allow this because some older C compilers don't set it.
	    # We should really check that it is not set withing an 
	    #  #ifndef __DATE__... 
	    return 0;
	}
	print "Warning: definition of name $defname used in $filename is not allowed\n";
	$avoidConstructionsSeen{"define__"} .= "$filename;";
    }
    return 0;
}

sub CheckForBadDirectives{
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    if ($curline =~ /^#\s*(\w+)/) {
	$directiveName = $1;
	if (!defined($allowedDirectives{$directiveName})) {
	    print "Warning: directive $directiveName is unknown in $filename\n";
	    $invalidDirectivesSeen{$directiveName} .= "$filename;";
	}
    }
    return 0;
}

#
# This checks for
# # ifdef NEEDS_foo_DEFINED
# # define foo
# # endif
# Basically, it allows you to skip over a define that would otherwise
# cause an error report in CheckForBadDefines
sub CheckForGuardedDefines {
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    my $allowed_name = "";
    if ($curline =~ /^#\s*ifdef\s+NEEDS_(.*)_DEFINED/ ||
	$curline =~ /^#\s*if\s+defined\(NEEDS_(.*)_DEFINED\)/) {
        $allowed_name = $1;
	# Read *only* the next line
	$curline = <$FD>;
	# Allow some extra underscores before and after 
	if ($curline =~ /^#\s*define\s+_*${allowed_name}_*/) {
	    ;
	}
	else {
	    &PrintCurrentDirectory;
	    print "Warning: unexpected line after NEEDS_${allowed_name}_DEFINED in $filename\n";
        }
    }
}

foreach $file (@files) {
    print "$file\n" if $showfiles;
    if (-d $file) {
	# Skip test directories if requested
	if ($skipTest && $file =~ /test$/) {
	    next;
	}
	&ProcessDir( $file );
    }
    else {
        &ProcessFile( $file );
    }
}

# Summarize the problems seen.
$topHeader = 
"============================================================================
Summary of problems detected
Note that while in special cases, some of the following may be correct,
there is a cost to ensuring that EACH time the code is reviewed, either
by a person or by a style-checking program.  It is better to avoid
any construction whose use is not clearly allowed in all circumstances.
============================================================================\n";
# Summarize the reason for any avoid subs
$headerPrinted = 0;
foreach $key (keys(%avoidSubsSeen)) {
    my $reason = $avoidSubsReasons{$key};
    if (!$headerPrinted && $reason ne "") {
	$headerPrinted = 1;
	print "${topHeader}Explanation for messages about routines to avoid\n";
	$topHeader = "";
    }
    print "\n$key: $reason\n";
}

# Summarize the reason for any better subs
$headerPrinted = 0;
foreach $key (keys(%betterSubsSeen)) {
    my $reason = $betterSubsReasons{$key};
    if (!$headerPrinted && $reason ne "") {
	$headerPrinted = 1;
	print "$topHeader\nExplanation for messages about routines with prefered versions\n";
	$topHeader = "";
    }
    print "\n$key: $reason\n";
}

#
# Summarize the reason for any invalid directive
$headerPrinted = 0;
foreach $key (keys(%invalidDirectivesSeen)) {
    if (!$headerPrinted) {
	$headerPrinted = 1;
	print "$topHeader\n$invalidDirectivesReason\n";
	print "The following invalid directives were encountered:\n";
	$topHeader = "";
    }
    print "$key\n";
}

# Summarize any other problems
$headerPrinted = 0;
foreach $key (keys(%avoidConstructionsSeen)) {
    if (!$headerPrinted) {
	$headerPrinted = 1;
	print "$topHeader\nOther issues\n";
	$topHeader = "";
    }
    $reason = $avoidConstructionsReasons{$key};
    print "\n$key: $reason\n";
}
#
# ----------------------------------------------------------------------------
# Because Copyrights are important, this gets a separate step, at the
# end of the tests.
#
if ($missingCopyright) {
    print STDERR "Some files are missing the copyright statement\n";
    foreach $file (@NoCopyright) {
	print STDERR "$file\n";
    }
    if ($removeFileMissingCopyright == 1) {
	print STDERR "Files have been removed and an error status returned\n";
    }
    exit 1;
}

exit 0;

# ----------------------------------------------------------------------------
sub ProcessDir {
    my $DIR = "DIR$depth"; $depth++;
    my $dir = $_[0]; $dir =~ s/\/$//;
    my $savedir = $curdir;
    $curdir =~ s/\/$//;

    my $f2name = "./" . "$curdir/$dir" ;
    #print "checking $f2name and $curdir/$dir against skipfiles\n";
    if (defined($skipfiles{"$curdir/$dir"}) || defined($skipfiles{$f2name})) {
	print "Skipping directory $curdir\n" if ($verbose);
	$savedir = $curdir;
	return;
    }

    $curdir = "$curdir/$dir";

    print "Processing directory $curdir\n" if ($verbose);
    opendir( $DIR, "$curdir" ) || die "Cannot open directory $curdir\n";
    my @filelist;
    while ($file = readdir( $DIR) ) {
	print "File $file\n" if ($verbose);
	$filelist[$#filelist+1] = $file;
    }
    closedir( $DIR );
    foreach $file (@filelist) {
	print "processing $file\n" if ($verbose);
	if (-d "$curdir/$file") {
	    if (! ($file =~ /^\./) && ! ($file =~ /CVS/) ) { 
		&SetCurrentDirectory( "$curdir/$file" );
		&ProcessDir( $file );
	    }
	}
	elsif ($file =~ /\.[chi]$/) {
	    # Some included C source files in MPICH2 use suffix i,
	    # probably to exclude them from *.c-style compiles.
	    print "File $file\n" if ($verbose);
	    &ProcessFile( "$curdir/$file" );
	}
    }
    closedir( $DIR );
    $curdir = $savedir;
}
#
# Look for mismatched names in the state definitions 
# MPID_MPI_<optionalmode_>FUNC_EXIT, MPID_MPI_<optionalmode_>FUNC_ENTER
$statename = "";
$funcname  = "";
sub ClearMismatchedStates {
    $statename = "";
    $funcname  = "";
}
sub CheckForMismatchedStates {
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    if ($curline =~ /#define\s\s*FUNCNAME\s\s*(.*)\s*$/) {
        $funcname = $1;
        $funcname =~ s/\r//;
	$funcname =~ tr/a-z/A-Z/;
    }
    elsif ($curline =~ /\s*#define/) { return; }
    elsif ($curline =~ /MPID_MPI_[A-Z0-9_]*FUNC_ENTER\(\s*(.*)\s*\)/ ||
	   $curline =~ /MPID_MPI_[A-Z0-9_]*FUNC_ENTER_FRONT\(\s*(.*)\s*\)/ ||
	   $curline =~ /MPID_MPI_[A-Z0-9_]*FUNC_ENTER_BOTH\(\s*(.*)\s*\)/ ||
	   $curline =~ /MPID_MPI_[A-Z0-9_]*FUNC_ENTER_BACK\(\s*(.*)\s*\)/) {
	$statename = $1;
	$funcstatename = "MPID_STATE_".$funcname;
	if ($funcname ne "" && $statename ne $funcstatename) {
	    &PrintCurrentDirectory;
	    print "Warning: State name \"$statename\" does not match function name \"$funcname\"\n";
	}
    }
    elsif ($curline =~ /MPID_MPI_[A-Z0-9_]*FUNC_EXIT\(\s*([^\s]*)\s*\)/) {
	$endname = $1;
	if ($statename ne $endname && $endname ne "stateid") {
	    &PrintCurrentDirectory;
	    print "Warning: State $statename ended with $endname in $filename\[$line_num\]\n";
	}
    }
}
#
# Check for nested routine calls (NMPI) without Nest_incr/decr
# Special feature:
# /* begin:nested */ and /* end:nested */ can be used around
# routines that are only called with by another routine that
# has incremented the nested count
$nested_seen = 0;

sub ClearNested {
    $nested_seen = 0;
}
sub CheckForNested {
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    
    # Skip definitions
    if ($curline =~ /#define\s+NMPI/) {
	return;
    }
    if ($curline =~ /NMPI_H_INCLUDED/) { 
	return;
    }
    # Check for usage
    if ($curline =~ /NMPI_([A-Za-z0-9_]*)/) {
	my $name = $1;
	if (!$nested_seen && $name ne "Abort") {
	    # Don't warn about NMPI_Abort
	    &PrintCurrentDirectory;
	    print "Error: NMPI_$name called without MPIR_Nest_incr in $filename\[$line_num\]\n";
	}
    }
    elsif ($curline =~ /MPIR_Nest_incr/) {
	$nested_seen ++;
    }
    elsif ($curline =~ /MPIR_Nest_decr/) {
	# We do not decrement nested seen since we do not analyze program 
	# structure
	# $nested_seen --;
	;
    }
}
# Check for PMPI routine calls (normally, all calls should use NMPI)
sub CheckForPMPI {
    my $FD = $_[0];
    my $curline = $_[1];
    my $filename = $_[2];
    
    # Skip definitions
    if ($curline =~ /#define\s+MPI/) { # Skip #define MPI_xxx PMPI_xxx
	return;
    }
    if ($curline =~ /pragma/) { # Skip pragmas containing PMPI
	return;
    }
    if ($curline =~ /PMPI_LOCAL/) { # Skip the PMPI_LOCAL defn
	return;
    }
    if ($curline =~ /PMPI_([A-Za-z0-9_]*)/) {
	$name = $1;
	&PrintCurrentDirectory;
	print "Error: PMPI_$name called in $filename\[$line_num\]\n";
    }
}

#
# Check for proper preamble.  Record during file reading and check at end
$saw_c_style            = 0;
$saw_cxx_style          = 0;
$saw_fortran_style      = 0;
$style                  = "";
$saw_copyright          = 0;
$saw_autogenerated_file = 0;

sub ClearPreamble {
    $saw_c_style            = 0;
    $saw_cxx_style          = 0;
    $saw_fortran_style      = 0;
    $style                  = "";
    $saw_copyright          = 0;
    $saw_autogenerated_file = 0;
}    

sub CheckForPreamble {
    my $curline = $_[0];
    if ($curline =~ /-\*-\s*Mode:\s*C;\s*c-basic-offset:4/) {
	$saw_c_style = 1;
	$style = "C";
    }
    if ($curline =~ /-\*-\s*Mode:\s*C\+\+;\s*c-basic-offset:4/) {
	$saw_cxx_style = 1;
	$style = "C++";
    }
    if ($curline =~ /-\*-\s*Mode:\s*Fortran/) {
	$saw_fortran_style = 1;
	$style = "Fortran";
    }
    if ($curline =~ /See COPYRIGHT/) {
	$saw_copyright = 1;
    }
    if ($curline =~ /Copyright \(C\)/) {
	# This is for files with external copyrights, such as the
	# Etnus/Dolphin files for the Totalview interface
	$saw_copyright = 1;
    }
    if ($curline =~/DO NOT EDIT THIS FILE/) {
	$saw_autogenerated_file = 1;
    }
}
sub ReportOnPreamble {
    my $filename = $_[0];

    # Is this a derived file?
    if (-s "$filename.in") { return 0; }
    if ($checkStyle && 
	! $saw_c_style && !$saw_cxx_style && !$saw_fortran_style) {
	&PrintCurrentDirectory;
	print "C style header (c-basic-offset:4) missing in $filename\n";
	$avoidConstructionsSeen{"styleheader"} .= "$filename;";
    }
    if (! $saw_copyright  && ! $saw_autogenerated_file) {
	&PrintCurrentDirectory;
	print "Copyright statement missing from $filename\n";
	$missingCopyright ++;
	$NoCopyright[$#NoCopyright+1] = $filename;
	if ($removeFileMissingCopyright) {
	    print "Removing $filename\n";
	    unlink $filename;
	}
    }
}
# --------------------------------------------------------------------------
# These routines ensure that the current directory is printed before any
# warning messages.  This is used to reduce the amount of noise that
# comes out of the codingcheck
# --------------------------------------------------------------------------
$currentDirectory = "";
$directoryPrinted = 0;
sub SetCurrentDirectory {
    $currentDirectory =  $_[0];
    $directoryPrinted = 0;
}
sub PrintCurrentDirectory {
    if (!$directoryPrinted) {
	$directoryPrinted = 1;
	print "Directory $currentDirectory\n";
    }
}
# --------------------------------------------------------------------------
# Print help and usage information
sub printHelp {
    print STDOUT "codingcheck [ -showfiles ] [ -conf=filename ] 
\t[ -checktest ] [ -distcheck ] [ -skipfile=path ] [ -debug ] [ -verbose ]
\tpath [ more paths ]

 Checks source files for conformance to coding standards.  Items for which
 checks are made include :
 
 Routines whose use is risky, such as strcpy instead of strncpy.
 Extensions to C that are not portable, such as C++ comments, C-preprocessor
 directives, C-preprocessor names that may conflict with other packages,
 and MPICH2-specific tests, such as macros for function entry and exit and
 file headers.

 -skipfile=path - Ignore (skip) this file.  This should be used only for files
                  that are external
 -showfiles     - Print the name of each file as it is processed
 -conf=filename - Include a configuration file that can modify the 
                  behavior of checkbuild.  The contents of this file are 
                  described below.
 -debug, -verbose - Print additional information about the processing
 -distcheck     - Apply only minimal checks.  Most checks are disabled,
                  but checks for copyright statments
 -checktest     - Apply the checks to directories whose name is \"test\"

 MPICH2-specific arguments
 -allowromio    - Allow names used within ROMIO

 Configuration file contents:
    Setting elements of the hash skipfiles to one will cause these files
    to be skipped.  Names should start with \"./\".

    
 \n";
                  
}

#
# ToDo
# Look for overly general .cvsignore patterns.
# E.g., reject Makefile\* 

