summaryrefslogtreecommitdiffstats
path: root/utils/scan-build
diff options
context:
space:
mode:
Diffstat (limited to 'utils/scan-build')
-rwxr-xr-xutils/scan-build1278
1 files changed, 1278 insertions, 0 deletions
diff --git a/utils/scan-build b/utils/scan-build
new file mode 100755
index 0000000..5835628
--- /dev/null
+++ b/utils/scan-build
@@ -0,0 +1,1278 @@
+#!/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 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 $CXX; # Leave undefined initially.
+
+my $TERM = $ENV{'TERM'};
+my $UseColor = (defined $TERM and $TERM eq 'xterm-color' and -t STDOUT
+ and defined $ENV{'SCAN_BUILD_COLOR'});
+
+my $UserName = HtmlEscape(getpwuid($<) || 'unknown');
+my $HostName = HtmlEscape(hostname() || 'unknown');
+my $CurrentDir = HtmlEscape(getcwd());
+my $CurrentDirSuffix = basename($CurrentDir);
+
+my $CmdArgs;
+
+my $HtmlTitle;
+
+my $Date = localtime();
+
+##----------------------------------------------------------------------------##
+# Diagnostics
+##----------------------------------------------------------------------------##
+
+sub Diag {
+ if ($UseColor) {
+ print BOLD, MAGENTA "$Prog: @_";
+ print RESET;
+ }
+ else {
+ print "$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.llvm.org/StaticAnalysisUsage.html#filingbugs\n")
+}
+
+sub DieDiag {
+ if ($UseColor) {
+ print BOLD, RED "$Prog: ";
+ print RESET, RED @_;
+ print RESET;
+ }
+ else {
+ print "$Prog: ", @_;
+ }
+ exit(0);
+}
+
+##----------------------------------------------------------------------------##
+# Some initial preprocessing of Clang options.
+##----------------------------------------------------------------------------##
+
+# First, look for 'clang-cc' in libexec.
+my $ClangCCSB = Cwd::realpath("$RealBin/libexec/clang-cc");
+# Second, look for 'clang-cc' in the same directory as scan-build.
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ $ClangCCSB = Cwd::realpath("$RealBin/clang-cc");
+}
+# Third, look for 'clang-cc' in ../libexec
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ $ClangCCSB = Cwd::realpath("$RealBin/../libexec/clang-cc");
+}
+# Finally, default to looking for 'clang-cc' in the path.
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ $ClangCCSB = "clang-cc";
+}
+my $ClangCC = $ClangCCSB;
+
+# Now find 'clang'
+my $ClangSB = Cwd::realpath("$RealBin/bin/clang");
+if (!defined $ClangSB || ! -x $ClangSB) {
+ $ClangSB = Cwd::realpath("$RealBin/clang");
+}
+# Third, look for 'clang' in ../bin
+if (!defined $ClangSB || ! -x $ClangSB) {
+ $ClangSB = Cwd::realpath("$RealBin/../bin/clang");
+}
+# Finally, default to looking for 'clang-cc' in the path.
+if (!defined $ClangSB || ! -x $ClangSB) {
+ $ClangSB = "clang";
+}
+my $Clang = $ClangSB;
+
+
+my %AvailableAnalyses;
+
+# Query clang for analysis options.
+open(PIPE, "-|", $ClangCC, "--help") or
+ DieDiag("Cannot execute '$ClangCC'\n");
+
+my $FoundAnalysis = 0;
+
+while(<PIPE>) {
+ if ($FoundAnalysis == 0) {
+ if (/Checks and Analyses/) {
+ $FoundAnalysis = 1;
+ }
+ next;
+ }
+
+ if (/^\s\s\s\s([^\s]+)\s(.+)$/) {
+ next if ($1 =~ /-dump/ or $1 =~ /-view/
+ or $1 =~ /-checker-simple/ or $1 =~ /-warn-uninit/);
+
+ $AvailableAnalyses{$1} = $2;
+ next;
+ }
+ last;
+}
+
+close (PIPE);
+
+my %AnalysesDefaultEnabled = (
+ '-warn-dead-stores' => 1,
+ '-checker-cfref' => 1,
+ '-warn-objc-methodsigs' => 1,
+ # Do not enable the missing -dealloc check by default.
+ # '-warn-objc-missing-dealloc' => 1,
+ '-warn-objc-unused-ivars' => 1,
+);
+
+##----------------------------------------------------------------------------##
+# 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) {
+ if (`uname` =~ /Darwin/) {
+ $Dir = $ENV{'TMPDIR'};
+ if (!defined $Dir) { $Dir = "/tmp"; }
+ }
+ else {
+ $Dir = "/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 $DateString = sprintf("%d-%02d-%02d", $year, $month, $day);
+
+ # 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);
+
+ if ($x[3] > $max) {
+ $max = $x[3];
+ }
+ }
+
+ $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";
+ }
+ system 'mkdir','-p',$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/) {
+ 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);
+ system("mv", "$fname.tmp", $fname);
+}
+
+##----------------------------------------------------------------------------##
+# 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;
+
+ # 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.
+ system ("rm", "-f", "$Dir/$FName");
+ return;
+ }
+
+ $AlreadyScanned{$digest} = 1;
+
+ # At this point the report file is not world readable. Make it happen.
+ system ("chmod", "644", "$Dir/$FName");
+
+ # Scan the report file for tags.
+ open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
+
+ my $BugType = "";
+ my $BugFile = "";
+ my $BugCategory;
+ my $BugPathLength = 1;
+ my $BugLine = 0;
+ my $found = 0;
+
+ while (<IN>) {
+
+ last if ($found == 5);
+
+ if (/<!-- BUGTYPE (.*) -->$/) {
+ $BugType = $1;
+ ++$found;
+ }
+ elsif (/<!-- BUGFILE (.*) -->$/) {
+ $BugFile = abs_path($1);
+ UpdatePrefix($BugFile);
+ ++$found;
+ }
+ elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
+ $BugPathLength = $1;
+ ++$found;
+ }
+ elsif (/<!-- BUGLINE (.*) -->$/) {
+ $BugLine = $1;
+ ++$found;
+ }
+ elsif (/<!-- BUGCATEGORY (.*) -->$/) {
+ $BugCategory = $1;
+ ++$found;
+ }
+ }
+
+ close(IN);
+
+ if (!defined $BugCategory) {
+ $BugCategory = "Other";
+ }
+
+ push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $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);
+
+ system ("cp", $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);
+
+ system ("cp", $CSS, "$Dir");
+
+ DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
+ if (! -r $CSS);
+}
+
+##----------------------------------------------------------------------------##
+# Postprocess - Postprocess the results of an analysis scan.
+##----------------------------------------------------------------------------##
+
+sub Postprocess {
+
+ my $Dir = shift;
+ my $BaseDir = shift;
+
+ die "No directory specified." if (!defined $Dir);
+
+ if (! -d $Dir) {
+ Diag("No bugs found.\n");
+ return 0;
+ }
+
+ opendir(DIR, $Dir);
+ my @files = grep { /^report-.*\.html$/ } readdir(DIR);
+ closedir(DIR);
+
+ if (scalar(@files) == 0 and ! -e "$Dir/failures") {
+ Diag("Removing directory '$Dir' because it contains no reports.\n");
+ system ("rm", "-fR", $Dir);
+ return 0;
+ }
+
+ # Scan each report file and build an index.
+ my @Index;
+ foreach my $file (@files) { ScanFile(\@Index, $Dir, $file); }
+
+ # 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>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(@files)) {
+ # 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">&nbsp;&#x25BE;</span></td>
+ <td>File</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 the quantities.
+ for my $j ( 4 .. 5 ) {
+ print OUT "<td class=\"Q\">$row->[$j]</td>";
+ }
+
+ # Print the rest of the columns.
+ for (my $j = 6; $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.llvm.org/StaticAnalysisUsage.html#filingbugs\">bug reports</a>. <!-- REPORTCRASHES --> </p>\n";
+ }
+
+ print OUT "</body></html>\n";
+ close(OUT);
+ CopyFiles($Dir);
+
+ # Make sure $Dir and $BaseDir are world readable/executable.
+ system("chmod", "755", $Dir);
+ if (defined $BaseDir) { system("chmod", "755", $BaseDir); }
+
+ my $Num = scalar(@Index);
+ 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 RunBuildCommand {
+
+ my $Args = shift;
+ my $IgnoreErrors = shift;
+ my $Cmd = $Args->[0];
+ my $CCAnalyzer = shift;
+
+ # Get only the part of the command after the last '/'.
+ if ($Cmd =~ /\/([^\/]+)$/) {
+ $Cmd = $1;
+ }
+
+ if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or
+ $Cmd =~ /(.*\/?cc[^\/]*$)/ or
+ $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
+ $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
+
+ if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
+ $ENV{"CCC_CC"} = $1;
+ }
+
+ shift @$Args;
+ unshift @$Args, $CCAnalyzer;
+ }
+ elsif ($IgnoreErrors) {
+ if ($Cmd eq "make" or $Cmd eq "gmake") {
+ AddIfNotPresent($Args,"-k");
+ AddIfNotPresent($Args,"-i");
+ }
+ elsif ($Cmd eq "xcodebuild") {
+ AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
+ }
+ }
+
+ if ($Cmd eq "xcodebuild") {
+ # 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";
+ }
+ }
+ }
+ }
+
+ # Disable distributed builds for xcodebuild.
+ AddIfNotPresent($Args,"-nodistribute");
+
+ # 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++'
+ # when linking such files.
+ die if (!defined $CXX);
+ my $LDPLUSPLUS = `which $CXX`;
+ $LDPLUSPLUS =~ s/\015?\012//; # strip newlines
+ $ENV{'LDPLUSPLUS'} = $LDPLUSPLUS;
+ }
+
+ 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.
+
+ -o - Target directory for HTML report files. 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 - Display this message.
+ --help
+
+ -k - Add a "keep on going" option to the specified build command.
+ --keep-going This option currently supports make and xcodebuild.
+ This is a convenience option; one can specify this
+ behavior directly using build options.
+
+ --html-title [title] - Specify the title used on generated HTML pages.
+ --html-title=[title] 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.
+
+ --status-bugs - By default, the exit status of $Prog is the same as the
+ executed build command. Specifying this option causes the
+ exit status of $Prog to be 1 if it found potential bugs
+ and 0 otherwise.
+
+ --use-cc [compiler path] - By default, $Prog uses 'gcc' to compile and link
+ --use-cc=[compiler path] your C and Objective-C code. Use this option
+ to specify an alternate compiler.
+
+ --use-c++ [compiler path] - By default, $Prog uses 'g++' to compile and link
+ --use-c++=[compiler path] your C++ and Objective-C++ code. Use this option
+ to specify an alternate compiler.
+
+ -v - Verbose output from $Prog and the analyzer.
+ A second and third '-v' increases verbosity.
+
+ -V - View analysis results in a web browser when the build
+ --view completes.
+
+ADVANCED OPTIONS:
+
+ -constraints [model] - Specify the contraint engine used by the analyzer.
+ By default the 'range' model is used. Specifying
+ 'basic' uses a simpler, less powerful constraint model
+ used by checker-0.160 and earlier.
+
+ -store [model] - Specify the store model used by the analyzer. By default,
+ the 'basic' store model is used. 'region' specifies a field-
+ sensitive store model. Be warned that the 'region' model
+ is still in very early testing phase and may often crash.
+
+AVAILABLE ANALYSES (multiple analyses may be specified):
+
+ENDTEXT
+
+ foreach my $Analysis (sort keys %AvailableAnalyses) {
+ if (defined $AnalysesDefaultEnabled{$Analysis}) {
+ print " (+)";
+ }
+ else {
+ print " ";
+ }
+
+ print " $Analysis $AvailableAnalyses{$Analysis}\n";
+ }
+
+print <<ENDTEXT
+
+ NOTE: "(+)" indicates that an analysis is enabled by default unless one
+ or more analysis options are specified
+
+BUILD OPTIONS
+
+ You can specify any build option acceptable to the build command.
+
+EXAMPLE
+
+ $Prog -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 $Prog 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/&/&amp;/g;
+ $tmp =~ s/</&lt;/g;
+ $tmp =~ s/>/&gt;/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 @AnalysesToRun;
+my $StoreModel;
+my $ConstraintsModel;
+my $OutputFormat;
+
+if (!@ARGV) {
+ DisplayHelp();
+ exit 1;
+}
+
+while (@ARGV) {
+
+ # Scan for options we recognize.
+
+ my $arg = $ARGV[0];
+
+ if ($arg eq "-h" or $arg eq "--help") {
+ DisplayHelp();
+ exit 0;
+ }
+
+ if ($arg eq '-analyze-headers') {
+ shift @ARGV;
+ $AnalyzeHeaders = 1;
+ next;
+ }
+
+ if (defined $AvailableAnalyses{$arg}) {
+ shift @ARGV;
+ push @AnalysesToRun, $arg;
+ 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;
+
+ if (!defined $2 || $2 eq "") {
+ if (!@ARGV) {
+ DieDiag("'--use-c++' option requires a compiler executable name.\n");
+ }
+ $CXX = shift @ARGV;
+ }
+ else {
+ $CXX = $2;
+ }
+ 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 "-plist") {
+ shift @ARGV;
+ $OutputFormat = "plist";
+ next;
+ }
+
+ DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
+
+ last;
+}
+
+if (!@ARGV) {
+ Diag("No build command specified.\n\n");
+ DisplayHelp();
+ exit 1;
+}
+
+$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);
+
+# Set the appropriate environment variables.
+SetHtmlEnv(\@ARGV, $HtmlDir);
+
+my $Cmd = Cwd::realpath("$RealBin/libexec/ccc-analyzer");
+if (!defined $Cmd || ! -x $Cmd) {
+ $Cmd = Cwd::realpath("$RealBin/ccc-analyzer");
+ DieDiag("Executable 'ccc-analyzer' does not exist at '$Cmd'\n") if(! -x $Cmd);
+}
+
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ Diag("'clang-cc' executable not found in '$RealBin/libexec'.\n");
+ Diag("Using 'clang-cc' from path.\n");
+}
+if (!defined $ClangSB || ! -x $ClangSB) {
+ Diag("'clang' executable not found in '$RealBin/bin'.\n");
+ Diag("Using 'clang' from path.\n");
+}
+
+if (defined $CXX) {
+ $ENV{'CXX'} = $CXX;
+}
+else {
+ $CXX = 'g++'; # This variable is used by other parts of scan-build
+ # that need to know a default C++ compiler to fall back to.
+}
+
+$ENV{'CC'} = $Cmd;
+$ENV{'CLANG_CC'} = $ClangCC;
+$ENV{'CLANG'} = $Clang;
+
+if ($Verbose >= 2) {
+ $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
+}
+
+if ($Verbose >= 3) {
+ $ENV{'CCC_ANALYZER_LOG'} = 1;
+}
+
+if (scalar(@AnalysesToRun) == 0) {
+ foreach my $key (keys %AnalysesDefaultEnabled) {
+ push @AnalysesToRun,$key;
+ }
+}
+
+if ($AnalyzeHeaders) {
+ push @AnalysesToRun,"-analyzer-opt-analyze-headers";
+}
+
+$ENV{'CCC_ANALYZER_ANALYSIS'} = join ' ',@AnalysesToRun;
+
+if (defined $StoreModel) {
+ $ENV{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel;
+}
+
+if (defined $ConstraintsModel) {
+ $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel;
+}
+
+if (defined $OutputFormat) {
+ $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat;
+}
+
+
+# Run the build.
+my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd);
+
+if (defined $OutputFormat and $OutputFormat eq "plist") {
+ Diag "Analysis run complete.\n";
+ Diag "Analysis results (plist files) deposited in '$HtmlDir'\n";
+}
+else {
+ # Postprocess the HTML directory.
+ my $NumBugs = Postprocess($HtmlDir, $BaseDir);
+
+ 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;
+
OpenPOWER on IntegriCloud