#!/usr/bin/env perl use strict; use warnings; use File::Path qw(make_path); use File::Find; use File::Basename qw(dirname basename); use File::Which qw(which); use Cwd; $|++; # This causes printed output to be flushed immediately, regardless of newline # Find helper programs our $cppcheck = "not found"; our $code2html = "not found"; if(exists($ENV{'CPPCHECK'})){ $cppcheck = $ENV{'CPPCHECK'}; }elsif(defined(which("cppcheck"))){ $cppcheck = which("cppcheck"); } if(exists($ENV{'CODE2HTML'})){ $code2html = $ENV{'CODE2HTML'}; }elsif(defined(which("code2html"))){ $code2html = which("code2html"); } if($cppcheck eq "not found" or $code2html eq "not found"){ print "Helper applications not found:\n"; print " cppcheck : $cppcheck\n"; print " code2html : $code2html \n"; print "\n"; print "Make sure these are either in your PATH or set your\n"; print "CPPCHECK and CODE2HTML environment variables to point to them.\n"; print "\n"; exit(-1); } #our $bindir = "/Users/davidl/Desktop/c++check"; #our $cppcheck = "$bindir/cppcheck"; #our $code2html = "$bindir/code2html"; our $top_dir = cwd(); our $htmldir = "$top_dir/html"; our @infiles; #--------------------------------------------------------------- # Loop over user's inputs and make list of all files to process foreach my $arg (@ARGV) { # User could pass file or directory if( -d $arg){ # Scan directory looking for C,C++ files our $startdir_abs = $arg; if( $startdir_abs !~ /\/$/){ $startdir_abs .= "/"; } find(\&AddFile, $arg); }elsif( -f $arg){ my $relpath = dirname($arg)."/"; my $fname = $arg; if( $fname =~ /^\Q$relpath/){ $fname=$'; } if($relpath eq "./"){ $relpath = ""; } my $entry = {relpath=>"$relpath", fname=>"$fname", startdir_abs=>dirname($arg)."/"}; push(@infiles, $entry); }else{ print "Can't find \"$arg\"\n"; } } #--------------------------------------------------------------- # Check that we found at least 1 input file before continuing my$Ninputfiles = @infiles; if($Ninputfiles < 1){ print "\nNeed at least 1 input file!\n"; printf "\n Usage:\n c++check file_or_dir1 [file_or_dir2] ...\n\n"; exit(0); } #--------------------------------------------------------------- # Loop over input files and run each through the c++ code checker print "making \"$htmldir\"\n"; mkdir($htmldir); our @items = (); foreach my $infile (@infiles){ # Copy info to hash variable for easier access my %infile = %$infile; # Make sure output directory exists for html file my $pathname = $infile{"relpath"}.$infile{"fname"}; chdir($htmldir); make_path($infile{"relpath"}); # Process file and store output in hash chdir($top_dir); # the startdir_abs may be relative chdir($infile{"startdir_abs"}); my @tmp = &ProcessFile($pathname); push(@items, @tmp); print "Found ".@tmp." issues (".@items." total so far)\n"; } #--------------------------------------------------------------- # Remove duplicate entries from items print "Removing duplicate items\n"; for(my $i=0; $i<@items; $i++){ my $item1 = $items[$i]; my %it1 = %$item1; for(my $j=$i+1; $j<@items; $j++){ my $item2 = $items[$j]; my %it2 = %$item2; my $differ = 0; while( (my $k, my $v) = each %it1){ if($it2{$k} ne $v){ if($k =~ /^(file|line|type|mess)/){ # only check file,line,type, and mess $differ=1; } } } if($differ == 0){ splice @items,$j,1; # remove the duplicate item $j--; # backup one so we check the new j-th element on next iteration } } } print " ".@items." unique issues identified.\n"; #--------------------------------------------------------------- # Count number of errors, warnings, etc our %Ntype; foreach my $item (@items){ my %it = %$item; $Ntype{$it{"type"}}++; } #--------------------------------------------------------------- # Loop over input files again, running each through html-ifier our $startdir_abs; chdir($top_dir); # the startdir_abs may be relative chdir($startdir_abs); foreach my $infile (@infiles){ my %infile = %$infile; my $fname = $infile{"fname"}; my $pathname = $infile{"relpath"}.$fname; my $ofname = $pathname.".html"; # Open output file print "Writing $pathname ...\n"; open FILE, ">$htmldir/$ofname"; # Run code2html my $cmd = "$code2html -n -N -l c++ $pathname"; print FILE "\n"; my $out = `$cmd`; my @lines = split(/\n/, $out); foreach my $line (@lines){ # Add link to CPPCheck, it's full output, and self-recognition line at bottom if( $line =~ /<\/body/){ my $html = "
Static C++ analysis provided by "; $html .= "".`$cppcheck --version`.""; $html .= " (see full output from cppcheck for this file)"; $html .= "
Integration of static analysis results into HTML by David Lawrence
\n"; print FILE "$html
\n"; } # Print original line print FILE "$line\n"; # Add footer for Toggler if( $line =~ /<\/body/){ print FILE &MakeTogglerJavascriptFooter()."\n"; } # Add our own style sheet if ($line =~ /\/){ my $style = ""; $style .= " \n"; $style .= "\n"; print FILE $style; print FILE &MakeTogglerJavascriptHeader(0)."\n"; } # Add link to index.html and Toggler header if( $line =~ /^back to full report"; print FILE "$html
\n"; print FILE &MakeTogglerJavascriptMenu(0); } # Check for any messages related to this line foreach my $item (@items){ my %it = %$item; # Is this item related to this file? if($it{"filename"} eq $pathname){ my $lineno = $it{line}; my $regex = "name=\"line$lineno"; if ($line =~ /$regex/){ my $mess = $it{mess}; my $type = $it{type}; print FILE "

${mess}

\n"; } } } } # Close output file close(FILE); } #--------------------------------------------------------------- # Write out index file open FILE, ">$htmldir/index.html"; print FILE "\n"; print FILE "\n"; print FILE &MakeTogglerJavascriptHeader()."\n"; print FILE "\n"; print FILE "\n\n"; print FILE "

C++ Static Analysis Report for ".basename($startdir_abs)."

\n\n"; print FILE "\n"; print FILE "\n"; print FILE "\n"; print FILE "\n"; print FILE "
date: ".`date` ."
invocator: ".`whoami` ."
machine: ".`uname -a`."

\n\n"; print FILE "

"; print FILE "Notification counts:
\n"; print FILE &MakeTogglerJavascriptMenu(1); print FILE "

\n"; print FILE "
\n"; print FILE "\n"; print FILE "\n"; print FILE "\n"; print FILE "\n"; foreach my $item (@items){ my %it = %$item; my $line = $it{"line"}; my $file = $it{"filename"}; my $linkstr = "$file:$line"; print FILE "\n"; #print FILE "\n"; print FILE " \n"; print FILE " \n"; print FILE " \n"; print FILE "\n"; } print FILE "
FileType
$linkstr".$it{"type"}."".$it{"mess"}."
\n"; print FILE "\n"; print FILE &MakeTogglerJavascriptFooter()."\n"; print FILE "\n\n"; # Close index.html file close(FILE); #<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> #---------------------------- SUBROUTINES ---------------------------------- #<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> #--------------------- # AddFile #--------------------- sub AddFile { # This gets called from the "find(...)" routine for every # file or directory encountered as it recursively traverses # the tree. # # The job of this routine is to identify source files and copy # them into the @infiles array for later processing our $startdir_abs; my $fname = $_; if( /\.(cc|c++|cpp|c|C|h|hpp|h++)$/ ){ # Get directory relative to starting directory specified by user # or absolute if we can't match it. my $relpath = dirname($File::Find::name)."/"; if( $relpath =~ /^$startdir_abs/ ){ $relpath = $'; } # If relative directory path is "." or "./" then drop it if(($relpath eq ".") or ($relpath eq "./")){ $relpath = ""; } my $entry = {relpath=>"$relpath", fname=>"$fname", startdir_abs=>"$startdir_abs"}; push(@infiles, $entry); } } #--------------------- # ProcessFile #--------------------- sub ProcessFile { our $cppcheck; # Get input filename and derive output filename my $pathname = $_[0]; print "Processing $pathname ... "; # Run cppcheck on input file my $cmd = "$cppcheck --enable=all $pathname"; my @lines; #push(@lines, ""); my $out = `$cmd 2>&1 1>/dev/null`; push(@lines,split(/\n/, $out)); # Create output file for un-modified output of cppcheck my $ofname = $pathname.".cppcheck_output.txt"; open FILE, ">$htmldir/$ofname"; print FILE "$cmd\n\n"; # Loop over output of cppcheck and parse info into array my @items = (); foreach (@lines){ my $fullmess = $_; print FILE "$fullmess\n"; if( /]:/ ){ # It seems the output format can have 2 forms: # # [file:line]: (type) message # # and # # [file:line] -> [includefile:line]: (type) message # # The file in the first block does not necessarily need to be # the input file. It does seem that the message required the # first file to include the second file so the message should # be stored with the first. Also, it is unclear if multiple "->" # symbols can appear. # Get type and bare message $fullmess =~ /\)/; my $mess = $'; $` =~ /\(/; my $type = $'; # Check if there is a "->" we need to handle my $secondary_file = ""; my $secondary_line = ""; if($` =~ /-> \[/){ $' =~ /:/; $secondary_file = $`; $' =~ /]/; $secondary_line = $`; } # Search for the end of he first block $fullmess =~ /]/; # Separate filename, line number, and message $` =~ /:/; my $line = $'; $` =~ /\[/; my $file = $'; # Record info my $entry = {filename => "$file", line => "$line", type => "$type", mess => "$mess" , secondary_file=>"$secondary_file", secondary_line=>"$secondary_line" , cppcheck_ofname=>"$ofname"}; push(@items, $entry); } } # Close output file close(FILE); return @items; } #--------------------- # MakeTogglerJavascriptHeader #--------------------- sub MakeTogglerJavascriptHeader { # This will generate the HTML ecapsulated javascript that can be used to # toggle elements of a given class from back and forth from being displayed my $html = " "; return $html; } #--------------------- # MakeTogglerJavascriptFooter #--------------------- sub MakeTogglerJavascriptFooter { our %Ntype; my $html = ""; return $html; } #--------------------- # MakeTogglerJavascriptMenu #--------------------- sub MakeTogglerJavascriptMenu { my $include_num = $_[0]; our %Ntype; my $html = "\n"; while ((my $k, my $v) = each(%Ntype)){ $html .= "\n"; $html .= "\n"; $html .= "\n"; if($include_num){$html .= "\n";} $html .= "\n"; $html .= "\n"; $html .= "\n"; } $html .= "
$k: $v
\n"; return $html; }