diff options
Diffstat (limited to 'tools/scan-build/scan-build')
-rwxr-xr-x | tools/scan-build/scan-build | 1742 |
1 files changed, 1742 insertions, 0 deletions
diff --git a/tools/scan-build/scan-build b/tools/scan-build/scan-build new file mode 100755 index 0000000..d52d8f5 --- /dev/null +++ b/tools/scan-build/scan-build @@ -0,0 +1,1742 @@ +#!/usr/bin/env perl +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## +# +# A script designed to wrap a build so that all calls to gcc are intercepted +# and piped to the static analyzer. +# +##===----------------------------------------------------------------------===## + +use strict; +use warnings; +use FindBin qw($RealBin); +use Digest::MD5; +use File::Basename; +use File::Find; +use File::Copy qw(copy); +use File::Path qw( rmtree mkpath ); +use Term::ANSIColor; +use Term::ANSIColor qw(:constants); +use Cwd qw/ getcwd abs_path /; +use Sys::Hostname; + +my $Verbose = 0; # Verbose output from this script. +my $Prog = "scan-build"; +my $BuildName; +my $BuildDate; + +my $TERM = $ENV{'TERM'}; +my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT + and defined $ENV{'SCAN_BUILD_COLOR'}); + +# Portability: getpwuid is not implemented for Win32 (see Perl language +# reference, perlport), use getlogin instead. +my $UserName = HtmlEscape(getlogin() || getpwuid($<) || 'unknown'); +my $HostName = HtmlEscape(hostname() || 'unknown'); +my $CurrentDir = HtmlEscape(getcwd()); +my $CurrentDirSuffix = basename($CurrentDir); + +my @PluginsToLoad; +my $CmdArgs; + +my $HtmlTitle; + +my $Date = localtime(); + +##----------------------------------------------------------------------------## +# Diagnostics +##----------------------------------------------------------------------------## + +sub Diag { + if ($UseColor) { + print BOLD, MAGENTA "$Prog: @_"; + print RESET; + } + else { + print "$Prog: @_"; + } +} + +sub ErrorDiag { + if ($UseColor) { + print STDERR BOLD, RED "$Prog: "; + print STDERR RESET, RED @_; + print STDERR RESET; + } else { + print STDERR "$Prog: @_"; + } +} + +sub DiagCrashes { + my $Dir = shift; + Diag ("The analyzer encountered problems on some source files.\n"); + Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n"); + Diag ("Please consider submitting a bug report using these files:\n"); + Diag (" http://clang-analyzer.llvm.org/filing_bugs.html\n") +} + +sub DieDiag { + if ($UseColor) { + print STDERR BOLD, RED "$Prog: "; + print STDERR RESET, RED @_; + print STDERR RESET; + } + else { + print STDERR "$Prog: ", @_; + } + exit 1; +} + +##----------------------------------------------------------------------------## +# Print default checker names +##----------------------------------------------------------------------------## + +if (grep /^--help-checkers$/, @ARGV) { + my @options = qx($0 -h); + foreach (@options) { + next unless /^ \+/; + s/^\s*//; + my ($sign, $name, @text) = split ' ', $_; + print $name, $/ if $sign eq '+'; + } + exit 0; +} + +##----------------------------------------------------------------------------## +# Declaration of Clang options. Populated later. +##----------------------------------------------------------------------------## + +my $Clang; +my $ClangSB; +my $ClangCXX; +my $ClangVersion; + +##----------------------------------------------------------------------------## +# GetHTMLRunDir - Construct an HTML directory name for the current sub-run. +##----------------------------------------------------------------------------## + +sub GetHTMLRunDir { + die "Not enough arguments." if (@_ == 0); + my $Dir = shift @_; + my $TmpMode = 0; + if (!defined $Dir) { + $Dir = $ENV{'TMPDIR'} || $ENV{'TEMP'} || $ENV{'TMP'} || "/tmp"; + $TmpMode = 1; + } + + # Chop off any trailing '/' characters. + while ($Dir =~ /\/$/) { chop $Dir; } + + # Get current date and time. + my @CurrentTime = localtime(); + my $year = $CurrentTime[5] + 1900; + my $day = $CurrentTime[3]; + my $month = $CurrentTime[4] + 1; + my $hour = $CurrentTime[2]; + my $min = $CurrentTime[1]; + my $sec = $CurrentTime[0]; + + my $TimeString = sprintf("%02d%02d%02d", $hour, $min, $sec); + my $DateString = sprintf("%d-%02d-%02d-%s-$$", + $year, $month, $day, $TimeString); + + # Determine the run number. + my $RunNumber; + + if (-d $Dir) { + if (! -r $Dir) { + DieDiag("directory '$Dir' exists but is not readable.\n"); + } + # Iterate over all files in the specified directory. + my $max = 0; + opendir(DIR, $Dir); + my @FILES = grep { -d "$Dir/$_" } readdir(DIR); + closedir(DIR); + + foreach my $f (@FILES) { + # Strip the prefix '$Prog-' if we are dumping files to /tmp. + if ($TmpMode) { + next if (!($f =~ /^$Prog-(.+)/)); + $f = $1; + } + + my @x = split/-/, $f; + next if (scalar(@x) != 4); + next if ($x[0] != $year); + next if ($x[1] != $month); + next if ($x[2] != $day); + next if ($x[3] != $TimeString); + next if ($x[4] != $$); + + if ($x[5] > $max) { + $max = $x[5]; + } + } + + $RunNumber = $max + 1; + } + else { + + if (-x $Dir) { + DieDiag("'$Dir' exists but is not a directory.\n"); + } + + if ($TmpMode) { + DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n"); + } + + # $Dir does not exist. It will be automatically created by the + # clang driver. Set the run number to 1. + + $RunNumber = 1; + } + + die "RunNumber must be defined!" if (!defined $RunNumber); + + # Append the run number. + my $NewDir; + if ($TmpMode) { + $NewDir = "$Dir/$Prog-$DateString-$RunNumber"; + } + else { + $NewDir = "$Dir/$DateString-$RunNumber"; + } + + # Make sure that the directory does not exist in order to avoid hijack. + if (-e $NewDir) { + DieDiag("The directory '$NewDir' already exists.\n"); + } + + mkpath($NewDir); + return $NewDir; +} + +sub SetHtmlEnv { + + die "Wrong number of arguments." if (scalar(@_) != 2); + + my $Args = shift; + my $Dir = shift; + + die "No build command." if (scalar(@$Args) == 0); + + my $Cmd = $$Args[0]; + + if ($Cmd =~ /configure/ || $Cmd =~ /autogen/) { + return; + } + + if ($Verbose) { + Diag("Emitting reports for this run to '$Dir'.\n"); + } + + $ENV{'CCC_ANALYZER_HTML'} = $Dir; +} + +##----------------------------------------------------------------------------## +# ComputeDigest - Compute a digest of the specified file. +##----------------------------------------------------------------------------## + +sub ComputeDigest { + my $FName = shift; + DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName); + + # Use Digest::MD5. We don't have to be cryptographically secure. We're + # just looking for duplicate files that come from a non-malicious source. + # We use Digest::MD5 because it is a standard Perl module that should + # come bundled on most systems. + open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n"); + binmode FILE; + my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest; + close(FILE); + + # Return the digest. + return $Result; +} + +##----------------------------------------------------------------------------## +# UpdatePrefix - Compute the common prefix of files. +##----------------------------------------------------------------------------## + +my $Prefix; + +sub UpdatePrefix { + my $x = shift; + my $y = basename($x); + $x =~ s/\Q$y\E$//; + + if (!defined $Prefix) { + $Prefix = $x; + return; + } + + chop $Prefix while (!($x =~ /^\Q$Prefix/)); +} + +sub GetPrefix { + return $Prefix; +} + +##----------------------------------------------------------------------------## +# UpdateInFilePath - Update the path in the report file. +##----------------------------------------------------------------------------## + +sub UpdateInFilePath { + my $fname = shift; + my $regex = shift; + my $newtext = shift; + + open (RIN, $fname) or die "cannot open $fname"; + open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp"; + + while (<RIN>) { + s/$regex/$newtext/; + print ROUT $_; + } + + close (ROUT); + close (RIN); + rename("$fname.tmp", $fname) +} + +##----------------------------------------------------------------------------## +# AddStatLine - Decode and insert a statistics line into the database. +##----------------------------------------------------------------------------## + +sub AddStatLine { + my $Line = shift; + my $Stats = shift; + my $File = shift; + + print $Line . "\n"; + + my $Regex = qr/(.*?)\ ->\ Total\ CFGBlocks:\ (\d+)\ \|\ Unreachable + \ CFGBlocks:\ (\d+)\ \|\ Exhausted\ Block:\ (yes|no)\ \|\ Empty\ WorkList: + \ (yes|no)/x; + + if ($Line !~ $Regex) { + return; + } + + # Create a hash of the interesting fields + my $Row = { + Filename => $File, + Function => $1, + Total => $2, + Unreachable => $3, + Aborted => $4, + Empty => $5 + }; + + # Add them to the stats array + push @$Stats, $Row; +} + +##----------------------------------------------------------------------------## +# ScanFile - Scan a report file for various identifying attributes. +##----------------------------------------------------------------------------## + +# Sometimes a source file is scanned more than once, and thus produces +# multiple error reports. We use a cache to solve this problem. + +my %AlreadyScanned; + +sub ScanFile { + + my $Index = shift; + my $Dir = shift; + my $FName = shift; + my $Stats = shift; + + # Compute a digest for the report file. Determine if we have already + # scanned a file that looks just like it. + + my $digest = ComputeDigest("$Dir/$FName"); + + if (defined $AlreadyScanned{$digest}) { + # Redundant file. Remove it. + unlink("$Dir/$FName"); + return; + } + + $AlreadyScanned{$digest} = 1; + + # At this point the report file is not world readable. Make it happen. + chmod(0644, "$Dir/$FName"); + + # Scan the report file for tags. + open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n"); + + my $BugType = ""; + my $BugFile = ""; + my $BugFunction = ""; + my $BugCategory = ""; + my $BugDescription = ""; + my $BugPathLength = 1; + my $BugLine = 0; + + while (<IN>) { + last if (/<!-- BUGMETAEND -->/); + + if (/<!-- BUGTYPE (.*) -->$/) { + $BugType = $1; + } + elsif (/<!-- BUGFILE (.*) -->$/) { + $BugFile = abs_path($1); + if (!defined $BugFile) { + # The file no longer exists: use the original path. + $BugFile = $1; + } + UpdatePrefix($BugFile); + } + elsif (/<!-- BUGPATHLENGTH (.*) -->$/) { + $BugPathLength = $1; + } + elsif (/<!-- BUGLINE (.*) -->$/) { + $BugLine = $1; + } + elsif (/<!-- BUGCATEGORY (.*) -->$/) { + $BugCategory = $1; + } + elsif (/<!-- BUGDESC (.*) -->$/) { + $BugDescription = $1; + } + elsif (/<!-- FUNCTIONNAME (.*) -->$/) { + $BugFunction = $1; + } + + } + + + close(IN); + + if (!defined $BugCategory) { + $BugCategory = "Other"; + } + + # Don't add internal statistics to the bug reports + if ($BugCategory =~ /statistics/i) { + AddStatLine($BugDescription, $Stats, $BugFile); + return; + } + + push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugFunction, $BugLine, + $BugPathLength ]; +} + +##----------------------------------------------------------------------------## +# CopyFiles - Copy resource files to target directory. +##----------------------------------------------------------------------------## + +sub CopyFiles { + + my $Dir = shift; + + my $JS = Cwd::realpath("$RealBin/sorttable.js"); + + DieDiag("Cannot find 'sorttable.js'.\n") + if (! -r $JS); + + copy($JS, "$Dir"); + + DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n") + if (! -r "$Dir/sorttable.js"); + + my $CSS = Cwd::realpath("$RealBin/scanview.css"); + + DieDiag("Cannot find 'scanview.css'.\n") + if (! -r $CSS); + + copy($CSS, "$Dir"); + + DieDiag("Could not copy 'scanview.css' to '$Dir'.\n") + if (! -r $CSS); +} + +##----------------------------------------------------------------------------## +# CalcStats - Calculates visitation statistics and returns the string. +##----------------------------------------------------------------------------## + +sub CalcStats { + my $Stats = shift; + + my $TotalBlocks = 0; + my $UnreachedBlocks = 0; + my $TotalFunctions = scalar(@$Stats); + my $BlockAborted = 0; + my $WorkListAborted = 0; + my $Aborted = 0; + + # Calculate the unique files + my $FilesHash = {}; + + foreach my $Row (@$Stats) { + $FilesHash->{$Row->{Filename}} = 1; + $TotalBlocks += $Row->{Total}; + $UnreachedBlocks += $Row->{Unreachable}; + $BlockAborted++ if $Row->{Aborted} eq 'yes'; + $WorkListAborted++ if $Row->{Empty} eq 'no'; + $Aborted++ if $Row->{Aborted} eq 'yes' || $Row->{Empty} eq 'no'; + } + + my $TotalFiles = scalar(keys(%$FilesHash)); + + # Calculations + my $PercentAborted = sprintf("%.2f", $Aborted / $TotalFunctions * 100); + my $PercentBlockAborted = sprintf("%.2f", $BlockAborted / $TotalFunctions + * 100); + my $PercentWorkListAborted = sprintf("%.2f", $WorkListAborted / + $TotalFunctions * 100); + my $PercentBlocksUnreached = sprintf("%.2f", $UnreachedBlocks / $TotalBlocks + * 100); + + my $StatsString = "Analyzed $TotalBlocks blocks in $TotalFunctions functions" + . " in $TotalFiles files\n" + . "$Aborted functions aborted early ($PercentAborted%)\n" + . "$BlockAborted had aborted blocks ($PercentBlockAborted%)\n" + . "$WorkListAborted had unfinished worklists ($PercentWorkListAborted%)\n" + . "$UnreachedBlocks blocks were never reached ($PercentBlocksUnreached%)\n"; + + return $StatsString; +} + +##----------------------------------------------------------------------------## +# Postprocess - Postprocess the results of an analysis scan. +##----------------------------------------------------------------------------## + +my @filesFound; +my $baseDir; +sub FileWanted { + my $baseDirRegEx = quotemeta $baseDir; + my $file = $File::Find::name; + + # The name of the file is generated by clang binary (HTMLDiagnostics.cpp) + if ($file =~ /report-.*\.html$/) { + my $relative_file = $file; + $relative_file =~ s/$baseDirRegEx//g; + push @filesFound, $relative_file; + } +} + +sub Postprocess { + + my $Dir = shift; + my $BaseDir = shift; + my $AnalyzerStats = shift; + my $KeepEmpty = shift; + + die "No directory specified." if (!defined $Dir); + + if (! -d $Dir) { + Diag("No bugs found.\n"); + return 0; + } + + $baseDir = $Dir . "/"; + find({ wanted => \&FileWanted, follow => 0}, $Dir); + + if (scalar(@filesFound) == 0 and ! -e "$Dir/failures") { + if (! $KeepEmpty) { + Diag("Removing directory '$Dir' because it contains no reports.\n"); + rmtree($Dir) or die "Cannot rmtree '$Dir' : $!"; + } + Diag("No bugs found.\n"); + return 0; + } + + # Scan each report file and build an index. + my @Index; + my @Stats; + foreach my $file (@filesFound) { ScanFile(\@Index, $Dir, $file, \@Stats); } + + # Scan the failures directory and use the information in the .info files + # to update the common prefix directory. + my @failures; + my @attributes_ignored; + if (-d "$Dir/failures") { + opendir(DIR, "$Dir/failures"); + @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR); + closedir(DIR); + opendir(DIR, "$Dir/failures"); + @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR); + closedir(DIR); + foreach my $file (@failures) { + open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n"); + my $Path = <IN>; + if (defined $Path) { UpdatePrefix($Path); } + close IN; + } + } + + # Generate an index.html file. + my $FName = "$Dir/index.html"; + open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n"); + + # Print out the header. + +print OUT <<ENDTEXT; +<html> +<head> +<title>${HtmlTitle}</title> +<link type="text/css" rel="stylesheet" href="scanview.css"/> +<script src="sorttable.js"></script> +<script language='javascript' type="text/javascript"> +function SetDisplay(RowClass, DisplayVal) +{ + var Rows = document.getElementsByTagName("tr"); + for ( var i = 0 ; i < Rows.length; ++i ) { + if (Rows[i].className == RowClass) { + Rows[i].style.display = DisplayVal; + } + } +} + +function CopyCheckedStateToCheckButtons(SummaryCheckButton) { + var Inputs = document.getElementsByTagName("input"); + for ( var i = 0 ; i < Inputs.length; ++i ) { + if (Inputs[i].type == "checkbox") { + if(Inputs[i] != SummaryCheckButton) { + Inputs[i].checked = SummaryCheckButton.checked; + Inputs[i].onclick(); + } + } + } +} + +function returnObjById( id ) { + if (document.getElementById) + var returnVar = document.getElementById(id); + else if (document.all) + var returnVar = document.all[id]; + else if (document.layers) + var returnVar = document.layers[id]; + return returnVar; +} + +var NumUnchecked = 0; + +function ToggleDisplay(CheckButton, ClassName) { + if (CheckButton.checked) { + SetDisplay(ClassName, ""); + if (--NumUnchecked == 0) { + returnObjById("AllBugsCheck").checked = true; + } + } + else { + SetDisplay(ClassName, "none"); + NumUnchecked++; + returnObjById("AllBugsCheck").checked = false; + } +} +</script> +<!-- SUMMARYENDHEAD --> +</head> +<body> +<h1>${HtmlTitle}</h1> + +<table> +<tr><th>User:</th><td>${UserName}\@${HostName}</td></tr> +<tr><th>Working Directory:</th><td>${CurrentDir}</td></tr> +<tr><th>Command Line:</th><td>${CmdArgs}</td></tr> +<tr><th>Clang Version:</th><td>${ClangVersion}</td></tr> +<tr><th>Date:</th><td>${Date}</td></tr> +ENDTEXT + +print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n" + if (defined($BuildName) && defined($BuildDate)); + +print OUT <<ENDTEXT; +</table> +ENDTEXT + + if (scalar(@filesFound)) { + # Print out the summary table. + my %Totals; + + for my $row ( @Index ) { + my $bug_type = ($row->[2]); + my $bug_category = ($row->[1]); + my $key = "$bug_category:$bug_type"; + + if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; } + else { $Totals{$key}->[0]++; } + } + + print OUT "<h2>Bug Summary</h2>"; + + if (defined $BuildName) { + print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n" + } + + my $TotalBugs = scalar(@Index); +print OUT <<ENDTEXT; +<table> +<thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead> +<tr style="font-weight:bold"><td class="SUMM_DESC">All Bugs</td><td class="Q">$TotalBugs</td><td><center><input type="checkbox" id="AllBugsCheck" onClick="CopyCheckedStateToCheckButtons(this);" checked/></center></td></tr> +ENDTEXT + + my $last_category; + + for my $key ( + sort { + my $x = $Totals{$a}; + my $y = $Totals{$b}; + my $res = $x->[1] cmp $y->[1]; + $res = $x->[2] cmp $y->[2] if ($res == 0); + $res + } keys %Totals ) + { + my $val = $Totals{$key}; + my $category = $val->[1]; + if (!defined $last_category or $last_category ne $category) { + $last_category = $category; + print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n"; + } + my $x = lc $key; + $x =~ s/[ ,'":\/()]+/_/g; + print OUT "<tr><td class=\"SUMM_DESC\">"; + print OUT $val->[2]; + print OUT "</td><td class=\"Q\">"; + print OUT $val->[0]; + print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n"; + } + + # Print out the table of errors. + +print OUT <<ENDTEXT; +</table> +<h2>Reports</h2> + +<table class="sortable" style="table-layout:automatic"> +<thead><tr> + <td>Bug Group</td> + <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind"> ▾</span></td> + <td>File</td> + <td>Function/Method</td> + <td class="Q">Line</td> + <td class="Q">Path Length</td> + <td class="sorttable_nosort"></td> + <!-- REPORTBUGCOL --> +</tr></thead> +<tbody> +ENDTEXT + + my $prefix = GetPrefix(); + my $regex; + my $InFileRegex; + my $InFilePrefix = "File:</td><td>"; + + if (defined $prefix) { + $regex = qr/^\Q$prefix\E/is; + $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is; + } + + for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) { + my $x = "$row->[1]:$row->[2]"; + $x = lc $x; + $x =~ s/[ ,'":\/()]+/_/g; + + my $ReportFile = $row->[0]; + + print OUT "<tr class=\"bt_$x\">"; + print OUT "<td class=\"DESC\">"; + print OUT $row->[1]; + print OUT "</td>"; + print OUT "<td class=\"DESC\">"; + print OUT $row->[2]; + print OUT "</td>"; + + # Update the file prefix. + my $fname = $row->[3]; + + if (defined $regex) { + $fname =~ s/$regex//; + UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix) + } + + print OUT "<td>"; + my @fname = split /\//,$fname; + if ($#fname > 0) { + while ($#fname >= 0) { + my $x = shift @fname; + print OUT $x; + if ($#fname >= 0) { + print OUT "<span class=\"W\"> </span>/"; + } + } + } + else { + print OUT $fname; + } + print OUT "</td>"; + + print OUT "<td class=\"DESC\">"; + print OUT $row->[4]; + print OUT "</td>"; + + # Print out the quantities. + for my $j ( 5 .. 6 ) { + print OUT "<td class=\"Q\">$row->[$j]</td>"; + } + + # Print the rest of the columns. + for (my $j = 7; $j <= $#{$row}; ++$j) { + print OUT "<td>$row->[$j]</td>" + } + + # Emit the "View" link. + print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>"; + + # Emit REPORTBUG markers. + print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n"; + + # End the row. + print OUT "</tr>\n"; + } + + print OUT "</tbody>\n</table>\n\n"; + } + + if (scalar (@failures) || scalar(@attributes_ignored)) { + print OUT "<h2>Analyzer Failures</h2>\n"; + + if (scalar @attributes_ignored) { + print OUT "The analyzer's parser ignored the following attributes:<p>\n"; + print OUT "<table>\n"; + print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; + foreach my $file (sort @attributes_ignored) { + die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/)); + my $attribute = $1; + # Open the attribute file to get the first file that failed. + next if (!open (ATTR, "$Dir/failures/$file")); + my $ppfile = <ATTR>; + chomp $ppfile; + close ATTR; + next if (! -e "$Dir/failures/$ppfile"); + # Open the info file and get the name of the source file. + open (INFO, "$Dir/failures/$ppfile.info.txt") or + die "Cannot open $Dir/failures/$ppfile.info.txt\n"; + my $srcfile = <INFO>; + chomp $srcfile; + close (INFO); + # Print the information in the table. + my $prefix = GetPrefix(); + if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } + print OUT "<tr><td>$attribute</td><td>$srcfile</td><td><a href=\"failures/$ppfile\">$ppfile</a></td><td><a href=\"failures/$ppfile.stderr.txt\">$ppfile.stderr.txt</a></td></tr>\n"; + my $ppfile_clang = $ppfile; + $ppfile_clang =~ s/[.](.+)$/.clang.$1/; + print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; + } + print OUT "</table>\n"; + } + + if (scalar @failures) { + print OUT "<p>The analyzer had problems processing the following files:</p>\n"; + print OUT "<table>\n"; + print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; + foreach my $file (sort @failures) { + $file =~ /(.+).info.txt$/; + # Get the preprocessed file. + my $ppfile = $1; + # Open the info file and get the name of the source file. + open (INFO, "$Dir/failures/$file") or + die "Cannot open $Dir/failures/$file\n"; + my $srcfile = <INFO>; + chomp $srcfile; + my $problem = <INFO>; + chomp $problem; + close (INFO); + # Print the information in the table. + my $prefix = GetPrefix(); + if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } + print OUT "<tr><td>$problem</td><td>$srcfile</td><td><a href=\"failures/$ppfile\">$ppfile</a></td><td><a href=\"failures/$ppfile.stderr.txt\">$ppfile.stderr.txt</a></td></tr>\n"; + my $ppfile_clang = $ppfile; + $ppfile_clang =~ s/[.](.+)$/.clang.$1/; + print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; + } + print OUT "</table>\n"; + } + print OUT "<p>Please consider submitting preprocessed files as <a href=\"http://clang-analyzer.llvm.org/filing_bugs.html\">bug reports</a>. <!-- REPORTCRASHES --> </p>\n"; + } + + print OUT "</body></html>\n"; + close(OUT); + CopyFiles($Dir); + + # Make sure $Dir and $BaseDir are world readable/executable. + chmod(0755, $Dir); + if (defined $BaseDir) { chmod(0755, $BaseDir); } + + # Print statistics + print CalcStats(\@Stats) if $AnalyzerStats; + + my $Num = scalar(@Index); + if ($Num == 1) { + Diag("$Num bug found.\n"); + } else { + Diag("$Num bugs found.\n"); + } + if ($Num > 0 && -r "$Dir/index.html") { + Diag("Run 'scan-view $Dir' to examine bug reports.\n"); + } + + DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored); + + return $Num; +} + +##----------------------------------------------------------------------------## +# RunBuildCommand - Run the build command. +##----------------------------------------------------------------------------## + +sub AddIfNotPresent { + my $Args = shift; + my $Arg = shift; + my $found = 0; + + foreach my $k (@$Args) { + if ($k eq $Arg) { + $found = 1; + last; + } + } + + if ($found == 0) { + push @$Args, $Arg; + } +} + +sub SetEnv { + my $Options = shift @_; + foreach my $opt ('CC', 'CXX', 'CLANG', 'CLANG_CXX', + 'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS', + 'CCC_ANALYZER_CONFIG') { + die "$opt is undefined\n" if (!defined $opt); + $ENV{$opt} = $Options->{$opt}; + } + foreach my $opt ('CCC_ANALYZER_STORE_MODEL', + 'CCC_ANALYZER_PLUGINS', + 'CCC_ANALYZER_INTERNAL_STATS', + 'CCC_ANALYZER_OUTPUT_FORMAT') { + my $x = $Options->{$opt}; + if (defined $x) { $ENV{$opt} = $x } + } + my $Verbose = $Options->{'VERBOSE'}; + if ($Verbose >= 2) { + $ENV{'CCC_ANALYZER_VERBOSE'} = 1; + } + if ($Verbose >= 3) { + $ENV{'CCC_ANALYZER_LOG'} = 1; + } +} + +# The flag corresponding to the --override-compiler command line option. +my $OverrideCompiler = 0; + +sub RunXcodebuild { + my $Args = shift; + my $IgnoreErrors = shift; + my $CCAnalyzer = shift; + my $CXXAnalyzer = shift; + my $Options = shift; + + if ($IgnoreErrors) { + AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES"); + } + + # Detect the version of Xcode. If Xcode 4.6 or higher, use new + # in situ support for analyzer interposition without needed to override + # the compiler. + open(DETECT_XCODE, "-|", $Args->[0], "-version") or + die "error: cannot detect version of xcodebuild\n"; + + my $oldBehavior = 1; + + while(<DETECT_XCODE>) { + if (/^Xcode (.+)$/) { + my $ver = $1; + if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) { + if ($1 >= 4.6) { + $oldBehavior = 0; + last; + } + } + } + } + close(DETECT_XCODE); + + # If --override-compiler is explicitely requested, resort to the old + # behavior regardless of Xcode version. + if ($OverrideCompiler) { + $oldBehavior = 1; + } + + if ($oldBehavior == 0) { + my $OutputDir = $Options->{"OUTPUT_DIR"}; + my $CLANG = $Options->{"CLANG"}; + my $OtherFlags = $Options->{"CCC_ANALYZER_ANALYSIS"}; + push @$Args, + "RUN_CLANG_STATIC_ANALYZER=YES", + "CLANG_ANALYZER_OUTPUT=plist-html", + "CLANG_ANALYZER_EXEC=$CLANG", + "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir", + "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags"; + + return (system(@$Args) >> 8); + } + + # Default to old behavior where we insert a bogus compiler. + SetEnv($Options); + + # Check if using iPhone SDK 3.0 (simulator). If so the compiler being + # used should be gcc-4.2. + if (!defined $ENV{"CCC_CC"}) { + for (my $i = 0 ; $i < scalar(@$Args); ++$i) { + if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) { + if (@$Args[$i+1] =~ /^iphonesimulator3/) { + $ENV{"CCC_CC"} = "gcc-4.2"; + $ENV{"CCC_CXX"} = "g++-4.2"; + } + } + } + } + + # Disable PCH files until clang supports them. + AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO"); + + # When 'CC' is set, xcodebuild uses it to do all linking, even if we are + # linking C++ object files. Set 'LDPLUSPLUS' so that xcodebuild uses 'g++' + # (via c++-analyzer) when linking such files. + $ENV{"LDPLUSPLUS"} = $CXXAnalyzer; + + return (system(@$Args) >> 8); +} + +sub RunBuildCommand { + my $Args = shift; + my $IgnoreErrors = shift; + my $Cmd = $Args->[0]; + my $CCAnalyzer = shift; + my $CXXAnalyzer = shift; + my $Options = shift; + + if ($Cmd =~ /\bxcodebuild$/) { + return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $Options); + } + + # Setup the environment. + SetEnv($Options); + + if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or + $Cmd =~ /(.*\/?cc[^\/]*$)/ or + $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or + $Cmd =~ /(.*\/?clang$)/ or + $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) { + + if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) { + $ENV{"CCC_CC"} = $1; + } + + shift @$Args; + unshift @$Args, $CCAnalyzer; + } + elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or + $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or + $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or + $Cmd =~ /(.*\/?clang\+\+$)/ or + $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) { + if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) { + $ENV{"CCC_CXX"} = $1; + } + shift @$Args; + unshift @$Args, $CXXAnalyzer; + } + elsif ($Cmd eq "make" or $Cmd eq "gmake" or $Cmd eq "mingw32-make") { + AddIfNotPresent($Args, "CC=$CCAnalyzer"); + AddIfNotPresent($Args, "CXX=$CXXAnalyzer"); + if ($IgnoreErrors) { + AddIfNotPresent($Args,"-k"); + AddIfNotPresent($Args,"-i"); + } + } + + return (system(@$Args) >> 8); +} + +##----------------------------------------------------------------------------## +# DisplayHelp - Utility function to display all help options. +##----------------------------------------------------------------------------## + +sub DisplayHelp { + +print <<ENDTEXT; +USAGE: $Prog [options] <build command> [build options] + +ENDTEXT + + if (defined $BuildName) { + print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n"; + } + +print <<ENDTEXT; +OPTIONS: + + -analyze-headers + + Also analyze functions in #included files. By default, such functions + are skipped unless they are called by functions within the main source file. + + -o <output location> + + Specifies the output directory for analyzer reports. Subdirectories will be + created as needed to represent separate "runs" of the analyzer. If this + option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X) + to store the reports. + + -h + --help + + Display this message. + + -k + --keep-going + + Add a "keep on going" option to the specified build command. This option + currently supports make and xcodebuild. This is a convenience option; one + can specify this behavior directly using build options. + + --html-title [title] + --html-title=[title] + + Specify the title used on generated HTML pages. If not specified, a default + title will be used. + + -plist + + By default the output of scan-build is a set of HTML files. This option + outputs the results as a set of .plist files. + + -plist-html + + By default the output of scan-build is a set of HTML files. This option + outputs the results as a set of HTML and .plist files. + + --status-bugs + + By default, the exit status of scan-build is the same as the executed build + command. Specifying this option causes the exit status of scan-build to be 1 + if it found potential bugs and 0 otherwise. + + --use-cc [compiler path] + --use-cc=[compiler path] + + scan-build analyzes a project by interposing a "fake compiler", which + executes a real compiler for compilation and the static analyzer for analysis. + Because of the current implementation of interposition, scan-build does not + know what compiler your project normally uses. Instead, it simply overrides + the CC environment variable, and guesses your default compiler. + + In the future, this interposition mechanism to be improved, but if you need + scan-build to use a specific compiler for *compilation* then you can use + this option to specify a path to that compiler. + + --use-c++ [compiler path] + --use-c++=[compiler path] + + This is the same as "-use-cc" but for C++ code. + + -v + + Enable verbose output from scan-build. A second and third '-v' increases + verbosity. + + -V + --view + + View analysis results in a web browser when the build completes. + +ADVANCED OPTIONS: + + -no-failure-reports + + Do not create a 'failures' subdirectory that includes analyzer crash reports + and preprocessed source files. + + -stats + + Generates visitation statistics for the project being analyzed. + + -maxloop <loop count> + + Specifiy the number of times a block can be visited before giving up. + Default is 4. Increase for more comprehensive coverage at a cost of speed. + + -internal-stats + + Generate internal analyzer statistics. + + --use-analyzer [Xcode|path to clang] + --use-analyzer=[Xcode|path to clang] + + scan-build uses the 'clang' executable relative to itself for static + analysis. One can override this behavior with this option by using the + 'clang' packaged with Xcode (on OS X) or from the PATH. + + --keep-empty + + Don't remove the build results directory even if no issues were reported. + + --override-compiler + Always resort to the ccc-analyzer even when better interposition methods + are available. + + -analyzer-config <options> + + Provide options to pass through to the analyzer's -analyzer-config flag. + Several options are separated with comma: 'key1=val1,key2=val2' + + Available options: + * stable-report-filename=true or false (default) + Switch the page naming to: + report-<filename>-<function/method name>-<id>.html + instead of report-XXXXXX.html + +CONTROLLING CHECKERS: + + A default group of checkers are always run unless explicitly disabled. + Checkers may be enabled/disabled using the following options: + + -enable-checker [checker name] + -disable-checker [checker name] + +LOADING CHECKERS: + + Loading external checkers using the clang plugin interface: + + -load-plugin [plugin library] +ENDTEXT + + # Query clang for list of checkers that are enabled. + + # create a list to load the plugins via the 'Xclang' command line + # argument + my @PluginLoadCommandline_xclang; + foreach my $param ( @PluginsToLoad ) { + push ( @PluginLoadCommandline_xclang, "-Xclang" ); + push ( @PluginLoadCommandline_xclang, $param ); + } + my %EnabledCheckers; + foreach my $lang ("c", "objective-c", "objective-c++", "c++") { + pipe(FROM_CHILD, TO_PARENT); + my $pid = fork(); + if ($pid == 0) { + close FROM_CHILD; + open(STDOUT,">&", \*TO_PARENT); + open(STDERR,">&", \*TO_PARENT); + exec $Clang, ( @PluginLoadCommandline_xclang, '--analyze', '-x', $lang, '-', '-###'); + } + close(TO_PARENT); + while(<FROM_CHILD>) { + foreach my $val (split /\s+/) { + $val =~ s/\"//g; + if ($val =~ /-analyzer-checker\=([^\s]+)/) { + $EnabledCheckers{$1} = 1; + } + } + } + waitpid($pid,0); + close(FROM_CHILD); + } + + # Query clang for complete list of checkers. + if (defined $Clang && -x $Clang) { + pipe(FROM_CHILD, TO_PARENT); + my $pid = fork(); + if ($pid == 0) { + close FROM_CHILD; + open(STDOUT,">&", \*TO_PARENT); + open(STDERR,">&", \*TO_PARENT); + exec $Clang, ('-cc1', @PluginsToLoad , '-analyzer-checker-help'); + } + close(TO_PARENT); + my $foundCheckers = 0; + while(<FROM_CHILD>) { + if (/CHECKERS:/) { + $foundCheckers = 1; + last; + } + } + if (!$foundCheckers) { + print " *** Could not query Clang for the list of available checkers."; + } + else { + print("\nAVAILABLE CHECKERS:\n\n"); + my $skip = 0; + while(<FROM_CHILD>) { + if (/experimental/) { + $skip = 1; + next; + } + if ($skip) { + next if (!/^\s\s[^\s]/); + $skip = 0; + } + s/^\s\s//; + if (/^([^\s]+)/) { + # Is the checker enabled? + my $checker = $1; + my $enabled = 0; + my $aggregate = ""; + foreach my $domain (split /\./, $checker) { + $aggregate .= $domain; + if ($EnabledCheckers{$aggregate}) { + $enabled =1; + last; + } + # append a dot, if an additional domain is added in the next iteration + $aggregate .= "."; + } + + if ($enabled) { + print " + "; + } + else { + print " "; + } + } + else { + print " "; + } + print $_; + } + print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n" + } + waitpid($pid,0); + close(FROM_CHILD); + } + +print <<ENDTEXT + +BUILD OPTIONS + + You can specify any build option acceptable to the build command. + +EXAMPLE + + scan-build -o /tmp/myhtmldir make -j4 + +The above example causes analysis reports to be deposited into a subdirectory +of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different +subdirectory is created each time scan-build analyzes a project. The analyzer +should support most parallel builds, but not distributed builds. + +ENDTEXT +} + +##----------------------------------------------------------------------------## +# HtmlEscape - HTML entity encode characters that are special in HTML +##----------------------------------------------------------------------------## + +sub HtmlEscape { + # copy argument to new variable so we don't clobber the original + my $arg = shift || ''; + my $tmp = $arg; + $tmp =~ s/&/&/g; + $tmp =~ s/</</g; + $tmp =~ s/>/>/g; + return $tmp; +} + +##----------------------------------------------------------------------------## +# ShellEscape - backslash escape characters that are special to the shell +##----------------------------------------------------------------------------## + +sub ShellEscape { + # copy argument to new variable so we don't clobber the original + my $arg = shift || ''; + if ($arg =~ /["\s]/) { return "'" . $arg . "'"; } + return $arg; +} + +##----------------------------------------------------------------------------## +# Process command-line arguments. +##----------------------------------------------------------------------------## + +my $AnalyzeHeaders = 0; +my $HtmlDir; # Parent directory to store HTML files. +my $IgnoreErrors = 0; # Ignore build errors. +my $ViewResults = 0; # View results when the build terminates. +my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found +my $KeepEmpty = 0; # Don't remove output directory even with 0 results. +my @AnalysesToRun; +my $StoreModel; +my $ConstraintsModel; +my $InternalStats; +my @ConfigOptions; +my $OutputFormat = "html"; +my $AnalyzerStats = 0; +my $MaxLoop = 0; +my $RequestDisplayHelp = 0; +my $ForceDisplayHelp = 0; +my $AnalyzerDiscoveryMethod; + +if (!@ARGV) { + $ForceDisplayHelp = 1 +} + +while (@ARGV) { + + # Scan for options we recognize. + + my $arg = $ARGV[0]; + + if ($arg eq "-h" or $arg eq "--help") { + $RequestDisplayHelp = 1; + shift @ARGV; + next; + } + + if ($arg eq '-analyze-headers') { + shift @ARGV; + $AnalyzeHeaders = 1; + next; + } + + if ($arg eq "-o") { + shift @ARGV; + + if (!@ARGV) { + DieDiag("'-o' option requires a target directory name.\n"); + } + + # Construct an absolute path. Uses the current working directory + # as a base if the original path was not absolute. + $HtmlDir = abs_path(shift @ARGV); + + next; + } + + if ($arg =~ /^--html-title(=(.+))?$/) { + shift @ARGV; + + if (!defined $2 || $2 eq '') { + if (!@ARGV) { + DieDiag("'--html-title' option requires a string.\n"); + } + + $HtmlTitle = shift @ARGV; + } else { + $HtmlTitle = $2; + } + + next; + } + + if ($arg eq "-k" or $arg eq "--keep-going") { + shift @ARGV; + $IgnoreErrors = 1; + next; + } + + if ($arg =~ /^--use-cc(=(.+))?$/) { + shift @ARGV; + my $cc; + + if (!defined $2 || $2 eq "") { + if (!@ARGV) { + DieDiag("'--use-cc' option requires a compiler executable name.\n"); + } + $cc = shift @ARGV; + } + else { + $cc = $2; + } + + $ENV{"CCC_CC"} = $cc; + next; + } + + if ($arg =~ /^--use-c\+\+(=(.+))?$/) { + shift @ARGV; + my $cxx; + + if (!defined $2 || $2 eq "") { + if (!@ARGV) { + DieDiag("'--use-c++' option requires a compiler executable name.\n"); + } + $cxx = shift @ARGV; + } + else { + $cxx = $2; + } + + $ENV{"CCC_CXX"} = $cxx; + next; + } + + if ($arg eq "-v") { + shift @ARGV; + $Verbose++; + next; + } + + if ($arg eq "-V" or $arg eq "--view") { + shift @ARGV; + $ViewResults = 1; + next; + } + + if ($arg eq "--status-bugs") { + shift @ARGV; + $ExitStatusFoundBugs = 1; + next; + } + + if ($arg eq "-store") { + shift @ARGV; + $StoreModel = shift @ARGV; + next; + } + + if ($arg eq "-constraints") { + shift @ARGV; + $ConstraintsModel = shift @ARGV; + next; + } + + if ($arg eq "-internal-stats") { + shift @ARGV; + $InternalStats = 1; + next; + } + + if ($arg eq "-plist") { + shift @ARGV; + $OutputFormat = "plist"; + next; + } + if ($arg eq "-plist-html") { + shift @ARGV; + $OutputFormat = "plist-html"; + next; + } + + if ($arg eq "-analyzer-config") { + shift @ARGV; + push @ConfigOptions, "-analyzer-config", shift @ARGV; + next; + } + + if ($arg eq "-no-failure-reports") { + shift @ARGV; + $ENV{"CCC_REPORT_FAILURES"} = 0; + next; + } + if ($arg eq "-stats") { + shift @ARGV; + $AnalyzerStats = 1; + next; + } + if ($arg eq "-maxloop") { + shift @ARGV; + $MaxLoop = shift @ARGV; + next; + } + if ($arg eq "-enable-checker") { + shift @ARGV; + push @AnalysesToRun, "-analyzer-checker", shift @ARGV; + next; + } + if ($arg eq "-disable-checker") { + shift @ARGV; + push @AnalysesToRun, "-analyzer-disable-checker", shift @ARGV; + next; + } + if ($arg eq "-load-plugin") { + shift @ARGV; + push @PluginsToLoad, "-load", shift @ARGV; + next; + } + if ($arg eq "--use-analyzer") { + shift @ARGV; + $AnalyzerDiscoveryMethod = shift @ARGV; + next; + } + if ($arg =~ /^--use-analyzer=(.+)$/) { + shift @ARGV; + $AnalyzerDiscoveryMethod = $1; + next; + } + if ($arg eq "--keep-empty") { + shift @ARGV; + $KeepEmpty = 1; + next; + } + + if ($arg eq "--override-compiler") { + shift @ARGV; + $OverrideCompiler = 1; + next; + } + + DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); + + last; +} + +if (!@ARGV and !$RequestDisplayHelp) { + ErrorDiag("No build command specified.\n\n"); + $ForceDisplayHelp = 1; +} + +# Find 'clang' +if (!defined $AnalyzerDiscoveryMethod) { + $Clang = Cwd::realpath("$RealBin/bin/clang"); + if (!defined $Clang || ! -x $Clang) { + $Clang = Cwd::realpath("$RealBin/clang"); + } + if (!defined $Clang || ! -x $Clang) { + if (!$RequestDisplayHelp && !$ForceDisplayHelp) { + DieDiag("error: Cannot find an executable 'clang' relative to scan-build." . + " Consider using --use-analyzer to pick a version of 'clang' to use for static analysis.\n"); + } + } +} +else { + if ($AnalyzerDiscoveryMethod =~ /^[Xx]code$/) { + my $xcrun = `which xcrun`; + chomp $xcrun; + if ($xcrun eq "") { + DieDiag("Cannot find 'xcrun' to find 'clang' for analysis.\n"); + } + $Clang = `$xcrun -toolchain XcodeDefault -find clang`; + chomp $Clang; + if ($Clang eq "") { + DieDiag("No 'clang' executable found by 'xcrun'\n"); + } + } + else { + $Clang = $AnalyzerDiscoveryMethod; + if (!defined $Clang or not -x $Clang) { + DieDiag("Cannot find an executable clang at '$AnalyzerDiscoveryMethod'\n"); + } + } +} + +if ($ForceDisplayHelp || $RequestDisplayHelp) { + DisplayHelp(); + exit $ForceDisplayHelp; +} + +$ClangCXX = $Clang; +# Determine operating system under which this copy of Perl was built. +my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/); +if($IsWinBuild) { + $ClangCXX =~ s/.exe$/++.exe/; +} +else { + $ClangCXX =~ s/\-\d+\.\d+$//; + $ClangCXX .= "++"; +} + +# Make sure to use "" to handle paths with spaces. +$ClangVersion = HtmlEscape(`"$Clang" --version`); + +# Determine where results go. +$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV))); +$HtmlTitle = "${CurrentDirSuffix} - scan-build results" + unless (defined($HtmlTitle)); + +# Determine the output directory for the HTML reports. +my $BaseDir = $HtmlDir; +$HtmlDir = GetHTMLRunDir($HtmlDir); + +# Determine the location of ccc-analyzer. +my $AbsRealBin = Cwd::realpath($RealBin); +my $Cmd = "$AbsRealBin/libexec/ccc-analyzer"; +my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer"; + +# Portability: use less strict but portable check -e (file exists) instead of +# non-portable -x (file is executable). On some windows ports -x just checks +# file extension to determine if a file is executable (see Perl language +# reference, perlport) +if (!defined $Cmd || ! -e $Cmd) { + $Cmd = "$AbsRealBin/ccc-analyzer"; + DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd); +} +if (!defined $CmdCXX || ! -e $CmdCXX) { + $CmdCXX = "$AbsRealBin/c++-analyzer"; + DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX); +} + +Diag("Using '$Clang' for static analysis\n"); + +SetHtmlEnv(\@ARGV, $HtmlDir); +if ($AnalyzeHeaders) { push @AnalysesToRun,"-analyzer-opt-analyze-headers"; } +if ($AnalyzerStats) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } +if ($MaxLoop > 0) { push @AnalysesToRun, "-analyzer-max-loop $MaxLoop"; } + +# Delay setting up other environment variables in case we can do true +# interposition. +my $CCC_ANALYZER_ANALYSIS = join ' ',@AnalysesToRun; +my $CCC_ANALYZER_PLUGINS = join ' ',@PluginsToLoad; +my $CCC_ANALYZER_CONFIG = join ' ',@ConfigOptions; +my %Options = ( + 'CC' => $Cmd, + 'CXX' => $CmdCXX, + 'CLANG' => $Clang, + 'CLANG_CXX' => $ClangCXX, + 'VERBOSE' => $Verbose, + 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS, + 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS, + 'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG, + 'OUTPUT_DIR' => $HtmlDir +); + +if (defined $StoreModel) { + $Options{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel; +} +if (defined $ConstraintsModel) { + $Options{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel; +} +if (defined $InternalStats) { + $Options{'CCC_ANALYZER_INTERNAL_STATS'} = 1; +} +if (defined $OutputFormat) { + $Options{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat; +} + +# Run the build. +my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd, $CmdCXX, + \%Options); + +if (defined $OutputFormat) { + if ($OutputFormat =~ /plist/) { + Diag "Analysis run complete.\n"; + Diag "Analysis results (plist files) deposited in '$HtmlDir'\n"; + } + if ($OutputFormat =~ /html/) { + # Postprocess the HTML directory. + my $NumBugs = Postprocess($HtmlDir, $BaseDir, $AnalyzerStats, $KeepEmpty); + + if ($ViewResults and -r "$HtmlDir/index.html") { + Diag "Analysis run complete.\n"; + Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n"; + my $ScanView = Cwd::realpath("$RealBin/scan-view"); + if (! -x $ScanView) { $ScanView = "scan-view"; } + exec $ScanView, "$HtmlDir"; + } + + if ($ExitStatusFoundBugs) { + exit 1 if ($NumBugs > 0); + exit 0; + } + } +} + +exit $ExitStatus; |