--- tools/clang/tools/scan-build/bin/scan-build.orig 2016-10-12 02:17:08 UTC +++ tools/clang/tools/scan-build/bin/scan-build @@ -0,0 +1,1832 @@ +#!/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; +use Hash::Util qw(lock_keys); + +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 $CmdArgs; + +my $Date = localtime(); + +# Command-line/config arguments. +my %Options = ( + Verbose => 0, # Verbose output from this script. + AnalyzeHeaders => 0, + OutputDir => undef, # Parent directory to store HTML files. + HtmlTitle => basename($CurrentDir)." - scan-build results", + IgnoreErrors => 0, # Ignore build errors. + ViewResults => 0, # View results when the build terminates. + ExitStatusFoundBugs => 0, # Exit status reflects whether bugs were found + KeepEmpty => 0, # Don't remove output directory even with 0 results. + EnableCheckers => {}, + DisableCheckers => {}, + UseCC => undef, # C compiler to use for compilation. + UseCXX => undef, # C++ compiler to use for compilation. + AnalyzerTarget => undef, + StoreModel => undef, + ConstraintsModel => undef, + InternalStats => undef, + OutputFormat => "html", + ConfigOptions => [], # Options to pass through to the analyzer's -analyzer-config flag. + ReportFailures => undef, + AnalyzerStats => 0, + MaxLoop => 0, + PluginsToLoad => [], + AnalyzerDiscoveryMethod => undef, + OverrideCompiler => 0 # The flag corresponding to the --override-compiler command line option. +); +lock_keys(%Options); + +##----------------------------------------------------------------------------## +# 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 ($Options{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 () { + 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 () { + last if (//); + + if (/$/) { + $BugType = $1; + } + elsif (/$/) { + $BugFile = abs_path($1); + if (!defined $BugFile) { + # The file no longer exists: use the original path. + $BugFile = $1; + } + UpdatePrefix($BugFile); + } + elsif (/$/) { + $BugPathLength = $1; + } + elsif (/$/) { + $BugLine = $1; + } + elsif (/$/) { + $BugCategory = $1; + } + elsif (/$/) { + $BugDescription = $1; + } + elsif (/$/) { + $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/../share/scan-build/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/../share/scan-build/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 = ; + 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 < + +${Options{HtmlTitle}} + + + + + + +

${Options{HtmlTitle}}

+ + + + + + + +ENDTEXT + +print OUT "\n" + if (defined($BuildName) && defined($BuildDate)); + +print OUT < +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 "

Bug Summary

"; + + if (defined $BuildName) { + print OUT "\n

Results in this analysis run are based on analyzer build $BuildName.

\n" + } + + my $TotalBugs = scalar(@Index); +print OUT < +
+ +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 "\n"; + } + my $x = lc $key; + $x =~ s/[ ,'":\/()]+/_/g; + print OUT "\n"; + } + + # Print out the table of errors. + +print OUT < +

Reports

+ +
User:${UserName}\@${HostName}
Working Directory:${CurrentDir}
Command Line:${CmdArgs}
Clang Version:${ClangVersion}
Date:${Date}
Version:${BuildName} (${BuildDate})
Bug TypeQuantityDisplay?
All Bugs$TotalBugs
$category
"; + print OUT $val->[2]; + print OUT ""; + print OUT $val->[0]; + print OUT "
+ + + + + + + + + + + +ENDTEXT + + my $prefix = GetPrefix(); + my $regex; + my $InFileRegex; + my $InFilePrefix = "File:"; + print OUT ""; + print OUT ""; + + # Update the file prefix. + my $fname = $row->[3]; + + if (defined $regex) { + $fname =~ s/$regex//; + UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix) + } + + print OUT ""; + + print OUT ""; + + # Print out the quantities. + for my $j ( 5 .. 6 ) { + print OUT ""; + } + + # Print the rest of the columns. + for (my $j = 7; $j <= $#{$row}; ++$j) { + print OUT "" + } + + # Emit the "View" link. + print OUT ""; + + # Emit REPORTBUG markers. + print OUT "\n\n"; + + # End the row. + print OUT "\n"; + } + + print OUT "\n
Bug GroupBug Type ▾FileFunction/MethodLinePath Length
"; + + 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 "
"; + print OUT $row->[1]; + print OUT ""; + print OUT $row->[2]; + print OUT ""; + my @fname = split /\//,$fname; + if ($#fname > 0) { + while ($#fname >= 0) { + my $x = shift @fname; + print OUT $x; + if ($#fname >= 0) { + print OUT "/"; + } + } + } + else { + print OUT $fname; + } + print OUT ""; + print OUT $row->[4]; + print OUT "$row->[$j]$row->[$j]View Report
\n\n"; + } + + if (scalar (@failures) || scalar(@attributes_ignored)) { + print OUT "

Analyzer Failures

\n"; + + if (scalar @attributes_ignored) { + print OUT "The analyzer's parser ignored the following attributes:

\n"; + print OUT "\n"; + print OUT "\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 = ; + 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 = ; + chomp $srcfile; + close (INFO); + # Print the information in the table. + my $prefix = GetPrefix(); + if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } + print OUT "\n"; + my $ppfile_clang = $ppfile; + $ppfile_clang =~ s/[.](.+)$/.clang.$1/; + print OUT " \n"; + } + print OUT "
AttributeSource FilePreprocessed FileSTDERR Output
$attribute$srcfile$ppfile$ppfile.stderr.txt
\n"; + } + + if (scalar @failures) { + print OUT "

The analyzer had problems processing the following files:

\n"; + print OUT "\n"; + print OUT "\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 = ; + chomp $srcfile; + my $problem = ; + chomp $problem; + close (INFO); + # Print the information in the table. + my $prefix = GetPrefix(); + if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } + print OUT "\n"; + my $ppfile_clang = $ppfile; + $ppfile_clang =~ s/[.](.+)$/.clang.$1/; + print OUT " \n"; + } + print OUT "
ProblemSource FilePreprocessed FileSTDERR Output
$problem$srcfile$ppfile$ppfile.stderr.txt
\n"; + } + print OUT "

Please consider submitting preprocessed files as bug reports.

\n"; + } + + print OUT "\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 $EnvVars = shift @_; + foreach my $var ('CC', 'CXX', 'CLANG', 'CLANG_CXX', + 'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS', + 'CCC_ANALYZER_CONFIG') { + die "$var is undefined\n" if (!defined $var); + $ENV{$var} = $EnvVars->{$var}; + } + foreach my $var ('CCC_ANALYZER_STORE_MODEL', + 'CCC_ANALYZER_CONSTRAINTS_MODEL', + 'CCC_ANALYZER_INTERNAL_STATS', + 'CCC_ANALYZER_OUTPUT_FORMAT', + 'CCC_CC', + 'CCC_CXX', + 'CCC_REPORT_FAILURES', + 'CLANG_ANALYZER_TARGET') { + my $x = $EnvVars->{$var}; + if (defined $x) { $ENV{$var} = $x } + } + my $Verbose = $EnvVars->{'VERBOSE'}; + if ($Verbose >= 2) { + $ENV{'CCC_ANALYZER_VERBOSE'} = 1; + } + if ($Verbose >= 3) { + $ENV{'CCC_ANALYZER_LOG'} = 1; + } +} + +sub RunXcodebuild { + my $Args = shift; + my $IgnoreErrors = shift; + my $CCAnalyzer = shift; + my $CXXAnalyzer = shift; + my $EnvVars = 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() { + 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 ($Options{OverrideCompiler}) { + $oldBehavior = 1; + } + + if ($oldBehavior == 0) { + my $OutputDir = $EnvVars->{"OUTPUT_DIR"}; + my $CLANG = $EnvVars->{"CLANG"}; + my $OtherFlags = $EnvVars->{"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($EnvVars); + + # 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 $EnvVars = shift; + + if ($Cmd =~ /\bxcodebuild$/) { + return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $EnvVars); + } + + # Setup the environment. + SetEnv($EnvVars); + + 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 { + + my $ArgClangNotFoundErrMsg = shift; +print < [build options] + +ENDTEXT + + if (defined $BuildName) { + print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n"; + } + +print < + + 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. + + If the given compiler is a cross compiler, you may also need to provide + --analyzer-target option to properly analyze the source code because static + analyzer runs as if the code is compiled for the host machine by default. + + --use-c++ [compiler path] + --use-c++=[compiler path] + + This is the same as "--use-cc" but for C++ code. + + --analyzer-target [target triple name for analysis] + --analyzer-target=[target triple name for analysis] + + This provides target triple information to clang static analyzer. + It only changes the target for analysis but doesn't change the target of a + real compiler given by --use-cc and --use-c++ options. + + -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 + + 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 + + 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---.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 + + if (defined $Clang && -x $Clang) { + # 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 ( @{$Options{PluginsToLoad}} ) { + push ( @PluginLoadCommandline_xclang, "-Xclang" ); + push ( @PluginLoadCommandline_xclang, "-load" ); + push ( @PluginLoadCommandline_xclang, "-Xclang" ); + push ( @PluginLoadCommandline_xclang, $param ); + } + + my %EnabledCheckers; + foreach my $lang ("c", "objective-c", "objective-c++", "c++") { + my $ExecLine = join(' ', qq/"$Clang"/, @PluginLoadCommandline_xclang, "--analyze", "-x", $lang, "-", "-###", "2>&1", "|"); + open(PS, $ExecLine); + while () { + foreach my $val (split /\s+/) { + $val =~ s/\"//g; + if ($val =~ /-analyzer-checker\=([^\s]+)/) { + $EnabledCheckers{$1} = 1; + } + } + } + } + + # Query clang for complete list of checkers. + my @PluginLoadCommandline; + foreach my $param ( @{$Options{PluginsToLoad}} ) { + push ( @PluginLoadCommandline, "-load" ); + push ( @PluginLoadCommandline, $param ); + } + + my $ExecLine = join(' ', qq/"$Clang"/, "-cc1", @PluginLoadCommandline, "-analyzer-checker-help", "2>&1", "|"); + open(PS, $ExecLine); + my $foundCheckers = 0; + while () { + 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() { + 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"; + } + close PS; + } + else { + print " *** Could not query Clang for the list of available checkers.\n"; + if (defined $ArgClangNotFoundErrMsg) { + print " *** Reason: $ArgClangNotFoundErrMsg\n"; + } + } + +print </>/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; +} + +##----------------------------------------------------------------------------## +# FindClang - searches for 'clang' executable. +##----------------------------------------------------------------------------## + +sub FindClang { + if (!defined $Options{AnalyzerDiscoveryMethod}) { + $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang"); + if (!defined $Clang || ! -x $Clang) { + $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang"); + } + if (!defined $Clang || ! -x $Clang) { + return "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 ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) { + my $xcrun = `which xcrun`; + chomp $xcrun; + if ($xcrun eq "") { + return "Cannot find 'xcrun' to find 'clang' for analysis.\n"; + } + $Clang = `$xcrun -toolchain XcodeDefault -find clang`; + chomp $Clang; + if ($Clang eq "") { + return "No 'clang' executable found by 'xcrun'\n"; + } + } + else { + $Clang = $Options{AnalyzerDiscoveryMethod}; + if (!defined $Clang or not -x $Clang) { + return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n"; + } + } + } + return undef; +} + +##----------------------------------------------------------------------------## +# Process command-line arguments. +##----------------------------------------------------------------------------## + +my $RequestDisplayHelp = 0; +my $ForceDisplayHelp = 0; + +sub ProcessArgs { + my $Args = shift; + my $NumArgs = 0; + + while (@$Args) { + + $NumArgs++; + + # Scan for options we recognize. + + my $arg = $Args->[0]; + + if ($arg eq "-h" or $arg eq "--help") { + $RequestDisplayHelp = 1; + shift @$Args; + next; + } + + if ($arg eq '-analyze-headers') { + shift @$Args; + $Options{AnalyzeHeaders} = 1; + next; + } + + if ($arg eq "-o") { + shift @$Args; + + if (!@$Args) { + 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. + my $OutDir = shift @$Args; + mkpath($OutDir) unless (-e $OutDir); # abs_path wants existing dir + $Options{OutputDir} = abs_path($OutDir); + + next; + } + + if ($arg =~ /^--html-title(=(.+))?$/) { + shift @$Args; + + if (!defined $2 || $2 eq '') { + if (!@$Args) { + DieDiag("'--html-title' option requires a string.\n"); + } + + $Options{HtmlTitle} = shift @$Args; + } else { + $Options{HtmlTitle} = $2; + } + + next; + } + + if ($arg eq "-k" or $arg eq "--keep-going") { + shift @$Args; + $Options{IgnoreErrors} = 1; + next; + } + + if ($arg =~ /^--use-cc(=(.+))?$/) { + shift @$Args; + my $cc; + + if (!defined $2 || $2 eq "") { + if (!@$Args) { + DieDiag("'--use-cc' option requires a compiler executable name.\n"); + } + $cc = shift @$Args; + } + else { + $cc = $2; + } + + $Options{UseCC} = $cc; + next; + } + + if ($arg =~ /^--use-c\+\+(=(.+))?$/) { + shift @$Args; + my $cxx; + + if (!defined $2 || $2 eq "") { + if (!@$Args) { + DieDiag("'--use-c++' option requires a compiler executable name.\n"); + } + $cxx = shift @$Args; + } + else { + $cxx = $2; + } + + $Options{UseCXX} = $cxx; + next; + } + + if ($arg =~ /^--analyzer-target(=(.+))?$/) { + shift @ARGV; + my $AnalyzerTarget; + + if (!defined $2 || $2 eq "") { + if (!@ARGV) { + DieDiag("'--analyzer-target' option requires a target triple name.\n"); + } + $AnalyzerTarget = shift @ARGV; + } + else { + $AnalyzerTarget = $2; + } + + $Options{AnalyzerTarget} = $AnalyzerTarget; + next; + } + + if ($arg eq "-v") { + shift @$Args; + $Options{Verbose}++; + next; + } + + if ($arg eq "-V" or $arg eq "--view") { + shift @$Args; + $Options{ViewResults} = 1; + next; + } + + if ($arg eq "--status-bugs") { + shift @$Args; + $Options{ExitStatusFoundBugs} = 1; + next; + } + + if ($arg eq "-store") { + shift @$Args; + $Options{StoreModel} = shift @$Args; + next; + } + + if ($arg eq "-constraints") { + shift @$Args; + $Options{ConstraintsModel} = shift @$Args; + next; + } + + if ($arg eq "-internal-stats") { + shift @$Args; + $Options{InternalStats} = 1; + next; + } + + if ($arg eq "-plist") { + shift @$Args; + $Options{OutputFormat} = "plist"; + next; + } + + if ($arg eq "-plist-html") { + shift @$Args; + $Options{OutputFormat} = "plist-html"; + next; + } + + if ($arg eq "-analyzer-config") { + shift @$Args; + push @{$Options{ConfigOptions}}, shift @$Args; + next; + } + + if ($arg eq "-no-failure-reports") { + shift @$Args; + $Options{ReportFailures} = 0; + next; + } + + if ($arg eq "-stats") { + shift @$Args; + $Options{AnalyzerStats} = 1; + next; + } + + if ($arg eq "-maxloop") { + shift @$Args; + $Options{MaxLoop} = shift @$Args; + next; + } + + if ($arg eq "-enable-checker") { + shift @$Args; + my $Checker = shift @$Args; + # Store $NumArgs to preserve the order the checkers were enabled. + $Options{EnableCheckers}{$Checker} = $NumArgs; + delete $Options{DisableCheckers}{$Checker}; + next; + } + + if ($arg eq "-disable-checker") { + shift @$Args; + my $Checker = shift @$Args; + # Store $NumArgs to preserve the order the checkers were disabled. + $Options{DisableCheckers}{$Checker} = $NumArgs; + delete $Options{EnableCheckers}{$Checker}; + next; + } + + if ($arg eq "-load-plugin") { + shift @$Args; + push @{$Options{PluginsToLoad}}, shift @$Args; + next; + } + + if ($arg eq "--use-analyzer") { + shift @$Args; + $Options{AnalyzerDiscoveryMethod} = shift @$Args; + next; + } + + if ($arg =~ /^--use-analyzer=(.+)$/) { + shift @$Args; + $Options{AnalyzerDiscoveryMethod} = $1; + next; + } + + if ($arg eq "--keep-empty") { + shift @$Args; + $Options{KeepEmpty} = 1; + next; + } + + if ($arg eq "--override-compiler") { + shift @$Args; + $Options{OverrideCompiler} = 1; + next; + } + + DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); + + $NumArgs--; + last; + } + return $NumArgs; +} + +if (!@ARGV) { + $ForceDisplayHelp = 1 +} + +ProcessArgs(\@ARGV); +# All arguments are now shifted from @ARGV. The rest is a build command, if any. + +if (!@ARGV and !$RequestDisplayHelp) { + ErrorDiag("No build command specified.\n\n"); + $ForceDisplayHelp = 1; +} + +my $ClangNotFoundErrMsg = FindClang(); + +if ($ForceDisplayHelp || $RequestDisplayHelp) { + DisplayHelp($ClangNotFoundErrMsg); + exit $ForceDisplayHelp; +} + +DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg); + +$ClangCXX = $Clang; +if ($Clang !~ /\+\+(\.exe)?$/) { + # If $Clang holds the name of the clang++ executable then we leave + # $ClangCXX and $Clang equal, otherwise construct the name of the clang++ + # executable from the clang executable name. + + # 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))); + +# Determine the output directory for the HTML reports. +my $BaseDir = $Options{OutputDir}; +$Options{OutputDir} = GetHTMLRunDir($Options{OutputDir}); + +# 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, $Options{OutputDir}); + +my @AnalysesToRun; +foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} } + keys %{$Options{EnableCheckers}}) { + # Push checkers in order they were enabled. + push @AnalysesToRun, "-analyzer-checker", $_; +} +foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} } + keys %{$Options{DisableCheckers}}) { + # Push checkers in order they were disabled. + push @AnalysesToRun, "-analyzer-disable-checker", $_; +} +if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; } +if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } +if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{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 ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}}; +my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}}; +my %EnvVars = ( + 'CC' => $Cmd, + 'CXX' => $CmdCXX, + 'CLANG' => $Clang, + 'CLANG_CXX' => $ClangCXX, + 'VERBOSE' => $Options{Verbose}, + 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS, + 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS, + 'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG, + 'OUTPUT_DIR' => $Options{OutputDir}, + 'CCC_CC' => $Options{UseCC}, + 'CCC_CXX' => $Options{UseCXX}, + 'CCC_REPORT_FAILURES' => $Options{ReportFailures}, + 'CCC_ANALYZER_STORE_MODEL' => $Options{StoreModel}, + 'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel}, + 'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats}, + 'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat}, + 'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget} +); + +# Run the build. +my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Cmd, $CmdCXX, + \%EnvVars); + +if (defined $Options{OutputFormat}) { + if ($Options{OutputFormat} =~ /plist/) { + Diag "Analysis run complete.\n"; + Diag "Analysis results (plist files) deposited in '$Options{OutputDir}'\n"; + } + if ($Options{OutputFormat} =~ /html/) { + # Postprocess the HTML directory. + my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir, + $Options{AnalyzerStats}, $Options{KeepEmpty}); + + if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") { + Diag "Analysis run complete.\n"; + Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n"; + my $ScanView = Cwd::realpath("$RealBin/scan-view"); + if (! -x $ScanView) { $ScanView = "scan-view"; } + if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); } + exec $ScanView, "$Options{OutputDir}"; + } + + if ($Options{ExitStatusFoundBugs}) { + exit 1 if ($NumBugs > 0); + exit 0; + } + } +} + +exit $ExitStatus;