3 # The LLVM Compiler Infrastructure
5 # This file is distributed under the University of Illinois Open Source
6 # License. See LICENSE.TXT for details.
8 ##===----------------------------------------------------------------------===##
10 # A script designed to wrap a build so that all calls to gcc are intercepted
11 # and piped to the static analyzer.
13 ##===----------------------------------------------------------------------===##
17 use FindBin qw($RealBin);
22 use Term::ANSIColor qw(:constants);
23 use Cwd qw/ getcwd abs_path /;
26 my $Verbose = 0; # Verbose output from this script.
27 my $Prog = "scan-build";
31 my $TERM = $ENV{'TERM'};
32 my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT
33 and defined $ENV{'SCAN_BUILD_COLOR'});
35 # Portability: getpwuid is not implemented for Win32 (see Perl language
36 # reference, perlport), use getlogin instead.
37 my $UserName = HtmlEscape(getlogin() || getpwuid($<) || 'unknown');
38 my $HostName = HtmlEscape(hostname() || 'unknown');
39 my $CurrentDir = HtmlEscape(getcwd());
40 my $CurrentDirSuffix = basename($CurrentDir);
47 my $Date = localtime();
49 ##----------------------------------------------------------------------------##
51 ##----------------------------------------------------------------------------##
55 print BOLD, MAGENTA "$Prog: @_";
65 print STDERR BOLD, RED "$Prog: ";
66 print STDERR RESET, RED @_;
69 print STDERR "$Prog: @_";
75 Diag ("The analyzer encountered problems on some source files.\n");
76 Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
77 Diag ("Please consider submitting a bug report using these files:\n");
78 Diag (" http://clang-analyzer.llvm.org/filing_bugs.html\n")
83 print STDERR BOLD, RED "$Prog: ";
84 print STDERR RESET, RED @_;
88 print STDERR "$Prog: ", @_;
93 ##----------------------------------------------------------------------------##
94 # Print default checker names
95 ##----------------------------------------------------------------------------##
97 if (grep /^--help-checkers$/, @ARGV) {
98 my @options = qx($0 -h);
102 my ($sign, $name, @text) = split ' ', $_;
103 print $name, $/ if $sign eq '+';
108 ##----------------------------------------------------------------------------##
109 # Declaration of Clang options. Populated later.
110 ##----------------------------------------------------------------------------##
117 ##----------------------------------------------------------------------------##
118 # GetHTMLRunDir - Construct an HTML directory name for the current sub-run.
119 ##----------------------------------------------------------------------------##
122 die "Not enough arguments." if (@_ == 0);
126 $Dir = $ENV{'TMPDIR'} || $ENV{'TEMP'} || $ENV{'TMP'} || "/tmp";
130 # Chop off any trailing '/' characters.
131 while ($Dir =~ /\/$/) { chop $Dir; }
133 # Get current date and time.
134 my @CurrentTime = localtime();
135 my $year = $CurrentTime[5] + 1900;
136 my $day = $CurrentTime[3];
137 my $month = $CurrentTime[4] + 1;
138 my $hour = $CurrentTime[2];
139 my $min = $CurrentTime[1];
140 my $sec = $CurrentTime[0];
142 my $TimeString = sprintf("%02d%02d%02d", $hour, $min, $sec);
143 my $DateString = sprintf("%d-%02d-%02d-%s-$$",
144 $year, $month, $day, $TimeString);
146 # Determine the run number.
151 DieDiag("directory '$Dir' exists but is not readable.\n");
153 # Iterate over all files in the specified directory.
156 my @FILES = grep { -d "$Dir/$_" } readdir(DIR);
159 foreach my $f (@FILES) {
160 # Strip the prefix '$Prog-' if we are dumping files to /tmp.
162 next if (!($f =~ /^$Prog-(.+)/));
166 my @x = split/-/, $f;
167 next if (scalar(@x) != 4);
168 next if ($x[0] != $year);
169 next if ($x[1] != $month);
170 next if ($x[2] != $day);
171 next if ($x[3] != $TimeString);
172 next if ($x[4] != $$);
179 $RunNumber = $max + 1;
184 DieDiag("'$Dir' exists but is not a directory.\n");
188 DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n");
191 # $Dir does not exist. It will be automatically created by the
192 # clang driver. Set the run number to 1.
197 die "RunNumber must be defined!" if (!defined $RunNumber);
199 # Append the run number.
202 $NewDir = "$Dir/$Prog-$DateString-$RunNumber";
205 $NewDir = "$Dir/$DateString-$RunNumber";
207 system 'mkdir','-p',$NewDir;
213 die "Wrong number of arguments." if (scalar(@_) != 2);
218 die "No build command." if (scalar(@$Args) == 0);
222 if ($Cmd =~ /configure/ || $Cmd =~ /autogen/) {
227 Diag("Emitting reports for this run to '$Dir'.\n");
230 $ENV{'CCC_ANALYZER_HTML'} = $Dir;
233 ##----------------------------------------------------------------------------##
234 # ComputeDigest - Compute a digest of the specified file.
235 ##----------------------------------------------------------------------------##
239 DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);
241 # Use Digest::MD5. We don't have to be cryptographically secure. We're
242 # just looking for duplicate files that come from a non-malicious source.
243 # We use Digest::MD5 because it is a standard Perl module that should
244 # come bundled on most systems.
245 open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
247 my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
254 ##----------------------------------------------------------------------------##
255 # UpdatePrefix - Compute the common prefix of files.
256 ##----------------------------------------------------------------------------##
262 my $y = basename($x);
265 if (!defined $Prefix) {
270 chop $Prefix while (!($x =~ /^\Q$Prefix/));
277 ##----------------------------------------------------------------------------##
278 # UpdateInFilePath - Update the path in the report file.
279 ##----------------------------------------------------------------------------##
281 sub UpdateInFilePath {
286 open (RIN, $fname) or die "cannot open $fname";
287 open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
296 system("mv", "$fname.tmp", $fname);
299 ##----------------------------------------------------------------------------##
300 # AddStatLine - Decode and insert a statistics line into the database.
301 ##----------------------------------------------------------------------------##
310 my $Regex = qr/(.*?)\ ->\ Total\ CFGBlocks:\ (\d+)\ \|\ Unreachable
311 \ CFGBlocks:\ (\d+)\ \|\ Exhausted\ Block:\ (yes|no)\ \|\ Empty\ WorkList:
314 if ($Line !~ $Regex) {
318 # Create a hash of the interesting fields
328 # Add them to the stats array
332 ##----------------------------------------------------------------------------##
333 # ScanFile - Scan a report file for various identifying attributes.
334 ##----------------------------------------------------------------------------##
336 # Sometimes a source file is scanned more than once, and thus produces
337 # multiple error reports. We use a cache to solve this problem.
348 # Compute a digest for the report file. Determine if we have already
349 # scanned a file that looks just like it.
351 my $digest = ComputeDigest("$Dir/$FName");
353 if (defined $AlreadyScanned{$digest}) {
354 # Redundant file. Remove it.
355 system ("rm", "-f", "$Dir/$FName");
359 $AlreadyScanned{$digest} = 1;
361 # At this point the report file is not world readable. Make it happen.
362 system ("chmod", "644", "$Dir/$FName");
364 # Scan the report file for tags.
365 open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
369 my $BugCategory = "";
370 my $BugDescription = "";
371 my $BugPathLength = 1;
375 last if (/<!-- BUGMETAEND -->/);
377 if (/<!-- BUGTYPE (.*) -->$/) {
380 elsif (/<!-- BUGFILE (.*) -->$/) {
381 $BugFile = abs_path($1);
382 UpdatePrefix($BugFile);
384 elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
387 elsif (/<!-- BUGLINE (.*) -->$/) {
390 elsif (/<!-- BUGCATEGORY (.*) -->$/) {
393 elsif (/<!-- BUGDESC (.*) -->$/) {
394 $BugDescription = $1;
400 if (!defined $BugCategory) {
401 $BugCategory = "Other";
404 # Don't add internal statistics to the bug reports
405 if ($BugCategory =~ /statistics/i) {
406 AddStatLine($BugDescription, $Stats, $BugFile);
410 push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugLine,
414 ##----------------------------------------------------------------------------##
415 # CopyFiles - Copy resource files to target directory.
416 ##----------------------------------------------------------------------------##
422 my $JS = Cwd::realpath("$RealBin/sorttable.js");
424 DieDiag("Cannot find 'sorttable.js'.\n")
427 system ("cp", $JS, "$Dir");
429 DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
430 if (! -r "$Dir/sorttable.js");
432 my $CSS = Cwd::realpath("$RealBin/scanview.css");
434 DieDiag("Cannot find 'scanview.css'.\n")
437 system ("cp", $CSS, "$Dir");
439 DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
443 ##----------------------------------------------------------------------------##
444 # CalcStats - Calculates visitation statistics and returns the string.
445 ##----------------------------------------------------------------------------##
451 my $UnreachedBlocks = 0;
452 my $TotalFunctions = scalar(@$Stats);
453 my $BlockAborted = 0;
454 my $WorkListAborted = 0;
457 # Calculate the unique files
460 foreach my $Row (@$Stats) {
461 $FilesHash->{$Row->{Filename}} = 1;
462 $TotalBlocks += $Row->{Total};
463 $UnreachedBlocks += $Row->{Unreachable};
464 $BlockAborted++ if $Row->{Aborted} eq 'yes';
465 $WorkListAborted++ if $Row->{Empty} eq 'no';
466 $Aborted++ if $Row->{Aborted} eq 'yes' || $Row->{Empty} eq 'no';
469 my $TotalFiles = scalar(keys(%$FilesHash));
472 my $PercentAborted = sprintf("%.2f", $Aborted / $TotalFunctions * 100);
473 my $PercentBlockAborted = sprintf("%.2f", $BlockAborted / $TotalFunctions
475 my $PercentWorkListAborted = sprintf("%.2f", $WorkListAborted /
476 $TotalFunctions * 100);
477 my $PercentBlocksUnreached = sprintf("%.2f", $UnreachedBlocks / $TotalBlocks
480 my $StatsString = "Analyzed $TotalBlocks blocks in $TotalFunctions functions"
481 . " in $TotalFiles files\n"
482 . "$Aborted functions aborted early ($PercentAborted%)\n"
483 . "$BlockAborted had aborted blocks ($PercentBlockAborted%)\n"
484 . "$WorkListAborted had unfinished worklists ($PercentWorkListAborted%)\n"
485 . "$UnreachedBlocks blocks were never reached ($PercentBlocksUnreached%)\n";
490 ##----------------------------------------------------------------------------##
491 # Postprocess - Postprocess the results of an analysis scan.
492 ##----------------------------------------------------------------------------##
497 my $baseDirRegEx = quotemeta $baseDir;
498 my $file = $File::Find::name;
499 if ($file =~ /report-.*\.html$/) {
500 my $relative_file = $file;
501 $relative_file =~ s/$baseDirRegEx//g;
502 push @filesFound, $relative_file;
510 my $AnalyzerStats = shift;
511 my $KeepEmpty = shift;
513 die "No directory specified." if (!defined $Dir);
516 Diag("No bugs found.\n");
520 $baseDir = $Dir . "/";
521 find({ wanted => \&FileWanted, follow => 0}, $Dir);
523 if (scalar(@filesFound) == 0 and ! -e "$Dir/failures") {
525 Diag("Removing directory '$Dir' because it contains no reports.\n");
526 system ("rm", "-fR", $Dir);
528 Diag("No bugs found.\n");
532 # Scan each report file and build an index.
535 foreach my $file (@filesFound) { ScanFile(\@Index, $Dir, $file, \@Stats); }
537 # Scan the failures directory and use the information in the .info files
538 # to update the common prefix directory.
540 my @attributes_ignored;
541 if (-d "$Dir/failures") {
542 opendir(DIR, "$Dir/failures");
543 @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
545 opendir(DIR, "$Dir/failures");
546 @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
548 foreach my $file (@failures) {
549 open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
551 if (defined $Path) { UpdatePrefix($Path); }
556 # Generate an index.html file.
557 my $FName = "$Dir/index.html";
558 open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
560 # Print out the header.
565 <title>${HtmlTitle}</title>
566 <link type="text/css" rel="stylesheet" href="scanview.css"/>
567 <script src="sorttable.js"></script>
568 <script language='javascript' type="text/javascript">
569 function SetDisplay(RowClass, DisplayVal)
571 var Rows = document.getElementsByTagName("tr");
572 for ( var i = 0 ; i < Rows.length; ++i ) {
573 if (Rows[i].className == RowClass) {
574 Rows[i].style.display = DisplayVal;
579 function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
580 var Inputs = document.getElementsByTagName("input");
581 for ( var i = 0 ; i < Inputs.length; ++i ) {
582 if (Inputs[i].type == "checkbox") {
583 if(Inputs[i] != SummaryCheckButton) {
584 Inputs[i].checked = SummaryCheckButton.checked;
591 function returnObjById( id ) {
592 if (document.getElementById)
593 var returnVar = document.getElementById(id);
594 else if (document.all)
595 var returnVar = document.all[id];
596 else if (document.layers)
597 var returnVar = document.layers[id];
601 var NumUnchecked = 0;
603 function ToggleDisplay(CheckButton, ClassName) {
604 if (CheckButton.checked) {
605 SetDisplay(ClassName, "");
606 if (--NumUnchecked == 0) {
607 returnObjById("AllBugsCheck").checked = true;
611 SetDisplay(ClassName, "none");
613 returnObjById("AllBugsCheck").checked = false;
617 <!-- SUMMARYENDHEAD -->
620 <h1>${HtmlTitle}</h1>
623 <tr><th>User:</th><td>${UserName}\@${HostName}</td></tr>
624 <tr><th>Working Directory:</th><td>${CurrentDir}</td></tr>
625 <tr><th>Command Line:</th><td>${CmdArgs}</td></tr>
626 <tr><th>Clang Version:</th><td>${ClangVersion}</td></tr>
627 <tr><th>Date:</th><td>${Date}</td></tr>
630 print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n"
631 if (defined($BuildName) && defined($BuildDate));
637 if (scalar(@filesFound)) {
638 # Print out the summary table.
641 for my $row ( @Index ) {
642 my $bug_type = ($row->[2]);
643 my $bug_category = ($row->[1]);
644 my $key = "$bug_category:$bug_type";
646 if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; }
647 else { $Totals{$key}->[0]++; }
650 print OUT "<h2>Bug Summary</h2>";
652 if (defined $BuildName) {
653 print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n"
656 my $TotalBugs = scalar(@Index);
659 <thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead>
660 <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>
669 my $res = $x->[1] cmp $y->[1];
670 $res = $x->[2] cmp $y->[2] if ($res == 0);
674 my $val = $Totals{$key};
675 my $category = $val->[1];
676 if (!defined $last_category or $last_category ne $category) {
677 $last_category = $category;
678 print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n";
681 $x =~ s/[ ,'":\/()]+/_/g;
682 print OUT "<tr><td class=\"SUMM_DESC\">";
684 print OUT "</td><td class=\"Q\">";
686 print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n";
689 # Print out the table of errors.
695 <table class="sortable" style="table-layout:automatic">
698 <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind"> ▾</span></td>
700 <td class="Q">Line</td>
701 <td class="Q">Path Length</td>
702 <td class="sorttable_nosort"></td>
703 <!-- REPORTBUGCOL -->
708 my $prefix = GetPrefix();
711 my $InFilePrefix = "File:</td><td>";
713 if (defined $prefix) {
714 $regex = qr/^\Q$prefix\E/is;
715 $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
718 for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
719 my $x = "$row->[1]:$row->[2]";
721 $x =~ s/[ ,'":\/()]+/_/g;
723 my $ReportFile = $row->[0];
725 print OUT "<tr class=\"bt_$x\">";
726 print OUT "<td class=\"DESC\">";
729 print OUT "<td class=\"DESC\">";
733 # Update the file prefix.
734 my $fname = $row->[3];
736 if (defined $regex) {
737 $fname =~ s/$regex//;
738 UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
742 my @fname = split /\//,$fname;
744 while ($#fname >= 0) {
745 my $x = shift @fname;
748 print OUT "<span class=\"W\"> </span>/";
757 # Print out the quantities.
758 for my $j ( 4 .. 5 ) {
759 print OUT "<td class=\"Q\">$row->[$j]</td>";
762 # Print the rest of the columns.
763 for (my $j = 6; $j <= $#{$row}; ++$j) {
764 print OUT "<td>$row->[$j]</td>"
767 # Emit the "View" link.
768 print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
770 # Emit REPORTBUG markers.
771 print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
777 print OUT "</tbody>\n</table>\n\n";
780 if (scalar (@failures) || scalar(@attributes_ignored)) {
781 print OUT "<h2>Analyzer Failures</h2>\n";
783 if (scalar @attributes_ignored) {
784 print OUT "The analyzer's parser ignored the following attributes:<p>\n";
785 print OUT "<table>\n";
786 print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
787 foreach my $file (sort @attributes_ignored) {
788 die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
790 # Open the attribute file to get the first file that failed.
791 next if (!open (ATTR, "$Dir/failures/$file"));
795 next if (! -e "$Dir/failures/$ppfile");
796 # Open the info file and get the name of the source file.
797 open (INFO, "$Dir/failures/$ppfile.info.txt") or
798 die "Cannot open $Dir/failures/$ppfile.info.txt\n";
799 my $srcfile = <INFO>;
802 # Print the information in the table.
803 my $prefix = GetPrefix();
804 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
805 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";
806 my $ppfile_clang = $ppfile;
807 $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
808 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
810 print OUT "</table>\n";
813 if (scalar @failures) {
814 print OUT "<p>The analyzer had problems processing the following files:</p>\n";
815 print OUT "<table>\n";
816 print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
817 foreach my $file (sort @failures) {
818 $file =~ /(.+).info.txt$/;
819 # Get the preprocessed file.
821 # Open the info file and get the name of the source file.
822 open (INFO, "$Dir/failures/$file") or
823 die "Cannot open $Dir/failures/$file\n";
824 my $srcfile = <INFO>;
826 my $problem = <INFO>;
829 # Print the information in the table.
830 my $prefix = GetPrefix();
831 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
832 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";
833 my $ppfile_clang = $ppfile;
834 $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
835 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
837 print OUT "</table>\n";
839 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";
842 print OUT "</body></html>\n";
846 # Make sure $Dir and $BaseDir are world readable/executable.
847 system("chmod", "755", $Dir);
848 if (defined $BaseDir) { system("chmod", "755", $BaseDir); }
851 print CalcStats(\@Stats) if $AnalyzerStats;
853 my $Num = scalar(@Index);
854 Diag("$Num bugs found.\n");
855 if ($Num > 0 && -r "$Dir/index.html") {
856 Diag("Run 'scan-view $Dir' to examine bug reports.\n");
859 DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
864 ##----------------------------------------------------------------------------##
865 # RunBuildCommand - Run the build command.
866 ##----------------------------------------------------------------------------##
868 sub AddIfNotPresent {
873 foreach my $k (@$Args) {
886 my $Options = shift @_;
887 foreach my $opt ('CC', 'CXX', 'CLANG', 'CLANG_CXX',
888 'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS') {
889 die "$opt is undefined\n" if (!defined $opt);
890 $ENV{$opt} = $Options->{$opt};
892 foreach my $opt ('CCC_ANALYZER_STORE_MODEL',
893 'CCC_ANALYZER_PLUGINS',
894 'CCC_ANALYZER_INTERNAL_STATS',
895 'CCC_ANALYZER_OUTPUT_FORMAT') {
896 my $x = $Options->{$opt};
897 if (defined $x) { $ENV{$opt} = $x }
899 my $Verbose = $Options->{'VERBOSE'};
901 $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
904 $ENV{'CCC_ANALYZER_LOG'} = 1;
908 # The flag corresponding to the --override-compiler command line option.
909 my $OverrideCompiler = 0;
913 my $IgnoreErrors = shift;
914 my $CCAnalyzer = shift;
915 my $CXXAnalyzer = shift;
919 AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
922 # Detect the version of Xcode. If Xcode 4.6 or higher, use new
923 # in situ support for analyzer interposition without needed to override
925 open(DETECT_XCODE, "-|", $Args->[0], "-version") or
926 die "error: cannot detect version of xcodebuild\n";
930 while(<DETECT_XCODE>) {
931 if (/^Xcode (.+)$/) {
933 if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) {
943 # If --override-compiler is explicitely requested, resort to the old
944 # behavior regardless of Xcode version.
945 if ($OverrideCompiler) {
949 if ($oldBehavior == 0) {
950 my $OutputDir = $Options->{"OUTPUT_DIR"};
951 my $CLANG = $Options->{"CLANG"};
952 my $OtherFlags = $Options->{"CCC_ANALYZER_ANALYSIS"};
954 "RUN_CLANG_STATIC_ANALYZER=YES",
955 "CLANG_ANALYZER_OUTPUT=plist-html",
956 "CLANG_ANALYZER_EXEC=$CLANG",
957 "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir",
958 "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags";
960 return (system(@$Args) >> 8);
963 # Default to old behavior where we insert a bogus compiler.
966 # Check if using iPhone SDK 3.0 (simulator). If so the compiler being
967 # used should be gcc-4.2.
968 if (!defined $ENV{"CCC_CC"}) {
969 for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
970 if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
971 if (@$Args[$i+1] =~ /^iphonesimulator3/) {
972 $ENV{"CCC_CC"} = "gcc-4.2";
973 $ENV{"CCC_CXX"} = "g++-4.2";
979 # Disable PCH files until clang supports them.
980 AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
982 # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
983 # linking C++ object files. Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
984 # (via c++-analyzer) when linking such files.
985 $ENV{"LDPLUSPLUS"} = $CXXAnalyzer;
987 return (system(@$Args) >> 8);
990 sub RunBuildCommand {
992 my $IgnoreErrors = shift;
993 my $Cmd = $Args->[0];
994 my $CCAnalyzer = shift;
995 my $CXXAnalyzer = shift;
998 if ($Cmd =~ /\bxcodebuild$/) {
999 return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $Options);
1002 # Setup the environment.
1005 if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or
1006 $Cmd =~ /(.*\/?cc[^\/]*$)/ or
1007 $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
1008 $Cmd =~ /(.*\/?clang$)/ or
1009 $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
1011 if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
1012 $ENV{"CCC_CC"} = $1;
1016 unshift @$Args, $CCAnalyzer;
1018 elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or
1019 $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or
1020 $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or
1021 $Cmd =~ /(.*\/?clang\+\+$)/ or
1022 $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) {
1023 if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) {
1024 $ENV{"CCC_CXX"} = $1;
1027 unshift @$Args, $CXXAnalyzer;
1029 elsif ($Cmd eq "make" or $Cmd eq "gmake") {
1030 AddIfNotPresent($Args, "CC=$CCAnalyzer");
1031 AddIfNotPresent($Args, "CXX=$CXXAnalyzer");
1032 if ($IgnoreErrors) {
1033 AddIfNotPresent($Args,"-k");
1034 AddIfNotPresent($Args,"-i");
1038 return (system(@$Args) >> 8);
1041 ##----------------------------------------------------------------------------##
1042 # DisplayHelp - Utility function to display all help options.
1043 ##----------------------------------------------------------------------------##
1048 USAGE: $Prog [options] <build command> [build options]
1052 if (defined $BuildName) {
1053 print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
1061 Also analyze functions in #included files. By default, such functions
1062 are skipped unless they are called by functions within the main source file.
1064 -o <output location>
1066 Specifies the output directory for analyzer reports. Subdirectories will be
1067 created as needed to represent separate "runs" of the analyzer. If this
1068 option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X)
1069 to store the reports.
1074 Display this message.
1079 Add a "keep on going" option to the specified build command. This option
1080 currently supports make and xcodebuild. This is a convenience option; one
1081 can specify this behavior directly using build options.
1083 --html-title [title]
1084 --html-title=[title]
1086 Specify the title used on generated HTML pages. If not specified, a default
1091 By default the output of scan-build is a set of HTML files. This option
1092 outputs the results as a set of .plist files.
1096 By default the output of scan-build is a set of HTML files. This option
1097 outputs the results as a set of HTML and .plist files.
1101 By default, the exit status of scan-build is the same as the executed build
1102 command. Specifying this option causes the exit status of scan-build to be 1
1103 if it found potential bugs and 0 otherwise.
1105 --use-cc [compiler path]
1106 --use-cc=[compiler path]
1108 scan-build analyzes a project by interposing a "fake compiler", which
1109 executes a real compiler for compilation and the static analyzer for analysis.
1110 Because of the current implementation of interposition, scan-build does not
1111 know what compiler your project normally uses. Instead, it simply overrides
1112 the CC environment variable, and guesses your default compiler.
1114 In the future, this interposition mechanism to be improved, but if you need
1115 scan-build to use a specific compiler for *compilation* then you can use
1116 this option to specify a path to that compiler.
1118 --use-c++ [compiler path]
1119 --use-c++=[compiler path]
1121 This is the same as "-use-cc" but for C++ code.
1125 Enable verbose output from scan-build. A second and third '-v' increases
1131 View analysis results in a web browser when the build completes.
1137 Do not create a 'failures' subdirectory that includes analyzer crash reports
1138 and preprocessed source files.
1142 Generates visitation statistics for the project being analyzed.
1144 -maxloop <loop count>
1146 Specifiy the number of times a block can be visited before giving up.
1147 Default is 4. Increase for more comprehensive coverage at a cost of speed.
1151 Generate internal analyzer statistics.
1153 --use-analyzer [Xcode|path to clang]
1154 --use-analyzer=[Xcode|path to clang]
1156 scan-build uses the 'clang' executable relative to itself for static
1157 analysis. One can override this behavior with this option by using the
1158 'clang' packaged with Xcode (on OS X) or from the PATH.
1162 Don't remove the build results directory even if no issues were reported.
1165 Always resort to the ccc-analyzer even when better interposition methods
1168 CONTROLLING CHECKERS:
1170 A default group of checkers are always run unless explicitly disabled.
1171 Checkers may be enabled/disabled using the following options:
1173 -enable-checker [checker name]
1174 -disable-checker [checker name]
1178 Loading external checkers using the clang plugin interface:
1180 -load-plugin [plugin library]
1183 # Query clang for list of checkers that are enabled.
1185 # create a list to load the plugins via the 'Xclang' command line
1187 my @PluginLoadCommandline_xclang;
1188 foreach my $param ( @PluginsToLoad ) {
1189 push ( @PluginLoadCommandline_xclang, "-Xclang" );
1190 push ( @PluginLoadCommandline_xclang, $param );
1192 my %EnabledCheckers;
1193 foreach my $lang ("c", "objective-c", "objective-c++", "c++") {
1194 pipe(FROM_CHILD, TO_PARENT);
1198 open(STDOUT,">&", \*TO_PARENT);
1199 open(STDERR,">&", \*TO_PARENT);
1200 exec $Clang, ( @PluginLoadCommandline_xclang, '--analyze', '-x', $lang, '-', '-###');
1203 while(<FROM_CHILD>) {
1204 foreach my $val (split /\s+/) {
1206 if ($val =~ /-analyzer-checker\=([^\s]+)/) {
1207 $EnabledCheckers{$1} = 1;
1215 # Query clang for complete list of checkers.
1216 if (defined $Clang && -x $Clang) {
1217 pipe(FROM_CHILD, TO_PARENT);
1221 open(STDOUT,">&", \*TO_PARENT);
1222 open(STDERR,">&", \*TO_PARENT);
1223 exec $Clang, ('-cc1', @PluginsToLoad , '-analyzer-checker-help');
1226 my $foundCheckers = 0;
1227 while(<FROM_CHILD>) {
1233 if (!$foundCheckers) {
1234 print " *** Could not query Clang for the list of available checkers.";
1237 print("\nAVAILABLE CHECKERS:\n\n");
1239 while(<FROM_CHILD>) {
1240 if (/experimental/) {
1245 next if (!/^\s\s[^\s]/);
1250 # Is the checker enabled?
1254 foreach my $domain (split /\./, $checker) {
1255 $aggregate .= $domain;
1256 if ($EnabledCheckers{$aggregate}) {
1260 # append a dot, if an additional domain is added in the next iteration
1276 print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n"
1286 You can specify any build option acceptable to the build command.
1290 scan-build -o /tmp/myhtmldir make -j4
1292 The above example causes analysis reports to be deposited into a subdirectory
1293 of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different
1294 subdirectory is created each time scan-build analyzes a project. The analyzer
1295 should support most parallel builds, but not distributed builds.
1300 ##----------------------------------------------------------------------------##
1301 # HtmlEscape - HTML entity encode characters that are special in HTML
1302 ##----------------------------------------------------------------------------##
1305 # copy argument to new variable so we don't clobber the original
1306 my $arg = shift || '';
1308 $tmp =~ s/&/&/g;
1314 ##----------------------------------------------------------------------------##
1315 # ShellEscape - backslash escape characters that are special to the shell
1316 ##----------------------------------------------------------------------------##
1319 # copy argument to new variable so we don't clobber the original
1320 my $arg = shift || '';
1321 if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
1325 ##----------------------------------------------------------------------------##
1326 # Process command-line arguments.
1327 ##----------------------------------------------------------------------------##
1329 my $AnalyzeHeaders = 0;
1330 my $HtmlDir; # Parent directory to store HTML files.
1331 my $IgnoreErrors = 0; # Ignore build errors.
1332 my $ViewResults = 0; # View results when the build terminates.
1333 my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found
1334 my $KeepEmpty = 0; # Don't remove output directory even with 0 results.
1337 my $ConstraintsModel;
1339 my $OutputFormat = "html";
1340 my $AnalyzerStats = 0;
1342 my $RequestDisplayHelp = 0;
1343 my $ForceDisplayHelp = 0;
1344 my $AnalyzerDiscoveryMethod;
1347 $ForceDisplayHelp = 1
1352 # Scan for options we recognize.
1356 if ($arg eq "-h" or $arg eq "--help") {
1357 $RequestDisplayHelp = 1;
1362 if ($arg eq '-analyze-headers') {
1364 $AnalyzeHeaders = 1;
1372 DieDiag("'-o' option requires a target directory name.\n");
1375 # Construct an absolute path. Uses the current working directory
1376 # as a base if the original path was not absolute.
1377 $HtmlDir = abs_path(shift @ARGV);
1382 if ($arg =~ /^--html-title(=(.+))?$/) {
1385 if (!defined $2 || $2 eq '') {
1387 DieDiag("'--html-title' option requires a string.\n");
1390 $HtmlTitle = shift @ARGV;
1398 if ($arg eq "-k" or $arg eq "--keep-going") {
1404 if ($arg =~ /^--use-cc(=(.+))?$/) {
1408 if (!defined $2 || $2 eq "") {
1410 DieDiag("'--use-cc' option requires a compiler executable name.\n");
1418 $ENV{"CCC_CC"} = $cc;
1422 if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1426 if (!defined $2 || $2 eq "") {
1428 DieDiag("'--use-c++' option requires a compiler executable name.\n");
1436 $ENV{"CCC_CXX"} = $cxx;
1446 if ($arg eq "-V" or $arg eq "--view") {
1452 if ($arg eq "--status-bugs") {
1454 $ExitStatusFoundBugs = 1;
1458 if ($arg eq "-store") {
1460 $StoreModel = shift @ARGV;
1464 if ($arg eq "-constraints") {
1466 $ConstraintsModel = shift @ARGV;
1470 if ($arg eq "-internal-stats") {
1476 if ($arg eq "-plist") {
1478 $OutputFormat = "plist";
1481 if ($arg eq "-plist-html") {
1483 $OutputFormat = "plist-html";
1487 if ($arg eq "-no-failure-reports") {
1488 $ENV{"CCC_REPORT_FAILURES"} = 0;
1491 if ($arg eq "-stats") {
1496 if ($arg eq "-maxloop") {
1498 $MaxLoop = shift @ARGV;
1501 if ($arg eq "-enable-checker") {
1503 push @AnalysesToRun, "-analyzer-checker", shift @ARGV;
1506 if ($arg eq "-disable-checker") {
1508 push @AnalysesToRun, "-analyzer-disable-checker", shift @ARGV;
1511 if ($arg eq "-load-plugin") {
1513 push @PluginsToLoad, "-load", shift @ARGV;
1516 if ($arg eq "--use-analyzer") {
1518 $AnalyzerDiscoveryMethod = shift @ARGV;
1521 if ($arg =~ /^--use-analyzer=(.+)$/) {
1523 $AnalyzerDiscoveryMethod = $1;
1526 if ($arg eq "--keep-empty") {
1532 if ($arg eq "--override-compiler") {
1534 $OverrideCompiler = 1;
1538 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1543 if (!@ARGV and !$RequestDisplayHelp) {
1544 ErrorDiag("No build command specified.\n\n");
1545 $ForceDisplayHelp = 1;
1549 if (!defined $AnalyzerDiscoveryMethod) {
1550 $Clang = Cwd::realpath("$RealBin/bin/clang");
1551 if (!defined $Clang || ! -x $Clang) {
1552 $Clang = Cwd::realpath("$RealBin/clang");
1554 if (!defined $Clang || ! -x $Clang) {
1555 if (!$RequestDisplayHelp && !$ForceDisplayHelp) {
1556 DieDiag("error: Cannot find an executable 'clang' relative to scan-build." .
1557 " Consider using --use-analyzer to pick a version of 'clang' to use for static analysis.\n");
1562 if ($AnalyzerDiscoveryMethod =~ /^[Xx]code$/) {
1563 my $xcrun = `which xcrun`;
1566 DieDiag("Cannot find 'xcrun' to find 'clang' for analysis.\n");
1568 $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
1571 DieDiag("No 'clang' executable found by 'xcrun'\n");
1575 $Clang = Cwd::realpath($AnalyzerDiscoveryMethod);
1576 if (!defined $Clang or not -x $Clang) {
1577 DieDiag("Cannot find an executable clang at '$AnalyzerDiscoveryMethod'\n");
1582 if ($ForceDisplayHelp || $RequestDisplayHelp) {
1584 exit $ForceDisplayHelp;
1588 # Determine operating system under which this copy of Perl was built.
1589 my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/);
1591 $ClangCXX =~ s/.exe$/++.exe/;
1594 $ClangCXX =~ s/\-\d+\.\d+$//;
1598 # Make sure to use "" to handle paths with spaces.
1599 $ClangVersion = HtmlEscape(`"$Clang" --version`);
1601 # Determine where results go.
1602 $CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1603 $HtmlTitle = "${CurrentDirSuffix} - scan-build results"
1604 unless (defined($HtmlTitle));
1606 # Determine the output directory for the HTML reports.
1607 my $BaseDir = $HtmlDir;
1608 $HtmlDir = GetHTMLRunDir($HtmlDir);
1610 # Determine the location of ccc-analyzer.
1611 my $AbsRealBin = Cwd::realpath($RealBin);
1612 my $Cmd = "$AbsRealBin/libexec/ccc-analyzer";
1613 my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer";
1615 # Portability: use less strict but portable check -e (file exists) instead of
1616 # non-portable -x (file is executable). On some windows ports -x just checks
1617 # file extension to determine if a file is executable (see Perl language
1618 # reference, perlport)
1619 if (!defined $Cmd || ! -e $Cmd) {
1620 $Cmd = "$AbsRealBin/ccc-analyzer";
1621 DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd);
1623 if (!defined $CmdCXX || ! -e $CmdCXX) {
1624 $CmdCXX = "$AbsRealBin/c++-analyzer";
1625 DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX);
1628 Diag("Using '$Clang' for static analysis\n");
1630 SetHtmlEnv(\@ARGV, $HtmlDir);
1631 if ($AnalyzeHeaders) { push @AnalysesToRun,"-analyzer-opt-analyze-headers"; }
1632 if ($AnalyzerStats) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
1633 if ($MaxLoop > 0) { push @AnalysesToRun, "-analyzer-max-loop $MaxLoop"; }
1635 # Delay setting up other environment variables in case we can do true
1637 my $CCC_ANALYZER_ANALYSIS = join ' ',@AnalysesToRun;
1638 my $CCC_ANALYZER_PLUGINS = join ' ',@PluginsToLoad;
1643 'CLANG_CXX' => $ClangCXX,
1644 'VERBOSE' => $Verbose,
1645 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
1646 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
1647 'OUTPUT_DIR' => $HtmlDir
1650 if (defined $StoreModel) {
1651 $Options{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel;
1653 if (defined $ConstraintsModel) {
1654 $Options{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel;
1656 if (defined $InternalStats) {
1657 $Options{'CCC_ANALYZER_INTERNAL_STATS'} = 1;
1659 if (defined $OutputFormat) {
1660 $Options{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat;
1664 my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd, $CmdCXX,
1667 if (defined $OutputFormat) {
1668 if ($OutputFormat =~ /plist/) {
1669 Diag "Analysis run complete.\n";
1670 Diag "Analysis results (plist files) deposited in '$HtmlDir'\n";
1672 if ($OutputFormat =~ /html/) {
1673 # Postprocess the HTML directory.
1674 my $NumBugs = Postprocess($HtmlDir, $BaseDir, $AnalyzerStats, $KeepEmpty);
1676 if ($ViewResults and -r "$HtmlDir/index.html") {
1677 Diag "Analysis run complete.\n";
1678 Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n";
1679 my $ScanView = Cwd::realpath("$RealBin/scan-view");
1680 if (! -x $ScanView) { $ScanView = "scan-view"; }
1681 exec $ScanView, "$HtmlDir";
1684 if ($ExitStatusFoundBugs) {
1685 exit 1 if ($NumBugs > 0);