Home | History | Annotate | Download | only in bin
      1 #!/usr/bin/env perl
      2 #
      3 #                     The LLVM Compiler Infrastructure
      4 #
      5 # This file is distributed under the University of Illinois Open Source
      6 # License. See LICENSE.TXT for details.
      7 #
      8 ##===----------------------------------------------------------------------===##
      9 #
     10 # A script designed to wrap a build so that all calls to gcc are intercepted
     11 # and piped to the static analyzer.
     12 #
     13 ##===----------------------------------------------------------------------===##
     14 
     15 use strict;
     16 use warnings;
     17 use FindBin qw($RealBin);
     18 use Digest::MD5;
     19 use File::Basename;
     20 use File::Find;
     21 use File::Copy qw(copy);
     22 use File::Path qw( rmtree mkpath );
     23 use Term::ANSIColor;
     24 use Term::ANSIColor qw(:constants);
     25 use Cwd qw/ getcwd abs_path /;
     26 use Sys::Hostname;
     27 use Hash::Util qw(lock_keys);
     28 
     29 my $Prog = "scan-build";
     30 my $BuildName;
     31 my $BuildDate;
     32 
     33 my $TERM = $ENV{'TERM'};
     34 my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT
     35                 and defined $ENV{'SCAN_BUILD_COLOR'});
     36 
     37 # Portability: getpwuid is not implemented for Win32 (see Perl language
     38 # reference, perlport), use getlogin instead.
     39 my $UserName = HtmlEscape(getlogin() || getpwuid($<) || 'unknown');
     40 my $HostName = HtmlEscape(hostname() || 'unknown');
     41 my $CurrentDir = HtmlEscape(getcwd());
     42 
     43 my $CmdArgs;
     44 
     45 my $Date = localtime();
     46 
     47 # Command-line/config arguments.
     48 my %Options = (
     49   Verbose => 0,              # Verbose output from this script.
     50   AnalyzeHeaders => 0,
     51   OutputDir => undef,        # Parent directory to store HTML files.
     52   HtmlTitle => basename($CurrentDir)." - scan-build results",
     53   IgnoreErrors => 0,         # Ignore build errors.
     54   ViewResults => 0,          # View results when the build terminates.
     55   ExitStatusFoundBugs => 0,  # Exit status reflects whether bugs were found
     56   KeepEmpty => 0,            # Don't remove output directory even with 0 results.
     57   EnableCheckers => {},
     58   DisableCheckers => {},
     59   UseCC => undef,            # C compiler to use for compilation.
     60   UseCXX => undef,           # C++ compiler to use for compilation.
     61   AnalyzerTarget => undef,
     62   StoreModel => undef,
     63   ConstraintsModel => undef,
     64   InternalStats => undef,
     65   OutputFormat => "html",
     66   ConfigOptions => [],       # Options to pass through to the analyzer's -analyzer-config flag.
     67   ReportFailures => undef,
     68   AnalyzerStats => 0,
     69   MaxLoop => 0,
     70   PluginsToLoad => [],
     71   AnalyzerDiscoveryMethod => undef,
     72   OverrideCompiler => 0      # The flag corresponding to the --override-compiler command line option.
     73 );
     74 lock_keys(%Options);
     75 
     76 ##----------------------------------------------------------------------------##
     77 # Diagnostics
     78 ##----------------------------------------------------------------------------##
     79 
     80 sub Diag {
     81   if ($UseColor) {
     82     print BOLD, MAGENTA "$Prog: @_";
     83     print RESET;
     84   }
     85   else {
     86     print "$Prog: @_";
     87   }
     88 }
     89 
     90 sub ErrorDiag {
     91   if ($UseColor) {
     92     print STDERR BOLD, RED "$Prog: ";
     93     print STDERR RESET, RED @_;
     94     print STDERR RESET;
     95   } else {
     96     print STDERR "$Prog: @_";
     97   }
     98 }
     99 
    100 sub DiagCrashes {
    101   my $Dir = shift;
    102   Diag ("The analyzer encountered problems on some source files.\n");
    103   Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
    104   Diag ("Please consider submitting a bug report using these files:\n");
    105   Diag ("  http://clang-analyzer.llvm.org/filing_bugs.html\n")
    106 }
    107 
    108 sub DieDiag {
    109   if ($UseColor) {
    110     print STDERR BOLD, RED "$Prog: ";
    111     print STDERR RESET, RED @_;
    112     print STDERR RESET;
    113   }
    114   else {
    115     print STDERR "$Prog: ", @_;
    116   }
    117   exit 1;
    118 }
    119 
    120 ##----------------------------------------------------------------------------##
    121 # Print default checker names
    122 ##----------------------------------------------------------------------------##
    123 
    124 if (grep /^--help-checkers$/, @ARGV) {
    125     my @options = qx($0 -h);
    126     foreach (@options) {
    127     next unless /^ \+/;
    128     s/^\s*//;
    129     my ($sign, $name, @text) = split ' ', $_;
    130     print $name, $/ if $sign eq '+';
    131     }
    132     exit 0;
    133 }
    134 
    135 ##----------------------------------------------------------------------------##
    136 # Declaration of Clang options.  Populated later.
    137 ##----------------------------------------------------------------------------##
    138 
    139 my $Clang;
    140 my $ClangSB;
    141 my $ClangCXX;
    142 my $ClangVersion;
    143 
    144 ##----------------------------------------------------------------------------##
    145 # GetHTMLRunDir - Construct an HTML directory name for the current sub-run.
    146 ##----------------------------------------------------------------------------##
    147 
    148 sub GetHTMLRunDir {
    149   die "Not enough arguments." if (@_ == 0);
    150   my $Dir = shift @_;
    151   my $TmpMode = 0;
    152   if (!defined $Dir) {
    153     $Dir = $ENV{'TMPDIR'} || $ENV{'TEMP'} || $ENV{'TMP'} || "/tmp";
    154     $TmpMode = 1;
    155   }
    156 
    157   # Chop off any trailing '/' characters.
    158   while ($Dir =~ /\/$/) { chop $Dir; }
    159 
    160   # Get current date and time.
    161   my @CurrentTime = localtime();
    162   my $year  = $CurrentTime[5] + 1900;
    163   my $day   = $CurrentTime[3];
    164   my $month = $CurrentTime[4] + 1;
    165   my $hour =  $CurrentTime[2];
    166   my $min =   $CurrentTime[1];
    167   my $sec =   $CurrentTime[0];
    168 
    169   my $TimeString = sprintf("%02d%02d%02d", $hour, $min, $sec);
    170   my $DateString = sprintf("%d-%02d-%02d-%s-$$",
    171                            $year, $month, $day, $TimeString);
    172 
    173   # Determine the run number.
    174   my $RunNumber;
    175 
    176   if (-d $Dir) {
    177     if (! -r $Dir) {
    178       DieDiag("directory '$Dir' exists but is not readable.\n");
    179     }
    180     # Iterate over all files in the specified directory.
    181     my $max = 0;
    182     opendir(DIR, $Dir);
    183     my @FILES = grep { -d "$Dir/$_" } readdir(DIR);
    184     closedir(DIR);
    185 
    186     foreach my $f (@FILES) {
    187       # Strip the prefix '$Prog-' if we are dumping files to /tmp.
    188       if ($TmpMode) {
    189         next if (!($f =~ /^$Prog-(.+)/));
    190         $f = $1;
    191       }
    192 
    193       my @x = split/-/, $f;
    194       next if (scalar(@x) != 4);
    195       next if ($x[0] != $year);
    196       next if ($x[1] != $month);
    197       next if ($x[2] != $day);
    198       next if ($x[3] != $TimeString);
    199       next if ($x[4] != $$);
    200 
    201       if ($x[5] > $max) {
    202         $max = $x[5];
    203       }
    204     }
    205 
    206     $RunNumber = $max + 1;
    207   }
    208   else {
    209 
    210     if (-x $Dir) {
    211       DieDiag("'$Dir' exists but is not a directory.\n");
    212     }
    213 
    214     if ($TmpMode) {
    215       DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n");
    216     }
    217 
    218     # $Dir does not exist.  It will be automatically created by the
    219     # clang driver.  Set the run number to 1.
    220 
    221     $RunNumber = 1;
    222   }
    223 
    224   die "RunNumber must be defined!" if (!defined $RunNumber);
    225 
    226   # Append the run number.
    227   my $NewDir;
    228   if ($TmpMode) {
    229     $NewDir = "$Dir/$Prog-$DateString-$RunNumber";
    230   }
    231   else {
    232     $NewDir = "$Dir/$DateString-$RunNumber";
    233   }
    234 
    235   # Make sure that the directory does not exist in order to avoid hijack.
    236   if (-e $NewDir) {
    237       DieDiag("The directory '$NewDir' already exists.\n");
    238   }
    239 
    240   mkpath($NewDir);
    241   return $NewDir;
    242 }
    243 
    244 sub SetHtmlEnv {
    245 
    246   die "Wrong number of arguments." if (scalar(@_) != 2);
    247 
    248   my $Args = shift;
    249   my $Dir = shift;
    250 
    251   die "No build command." if (scalar(@$Args) == 0);
    252 
    253   my $Cmd = $$Args[0];
    254 
    255   if ($Cmd =~ /configure/ || $Cmd =~ /autogen/) {
    256     return;
    257   }
    258 
    259   if ($Options{Verbose}) {
    260     Diag("Emitting reports for this run to '$Dir'.\n");
    261   }
    262 
    263   $ENV{'CCC_ANALYZER_HTML'} = $Dir;
    264 }
    265 
    266 ##----------------------------------------------------------------------------##
    267 # ComputeDigest - Compute a digest of the specified file.
    268 ##----------------------------------------------------------------------------##
    269 
    270 sub ComputeDigest {
    271   my $FName = shift;
    272   DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);
    273 
    274   # Use Digest::MD5.  We don't have to be cryptographically secure.  We're
    275   # just looking for duplicate files that come from a non-malicious source.
    276   # We use Digest::MD5 because it is a standard Perl module that should
    277   # come bundled on most systems.
    278   open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
    279   binmode FILE;
    280   my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
    281   close(FILE);
    282 
    283   # Return the digest.
    284   return $Result;
    285 }
    286 
    287 ##----------------------------------------------------------------------------##
    288 #  UpdatePrefix - Compute the common prefix of files.
    289 ##----------------------------------------------------------------------------##
    290 
    291 my $Prefix;
    292 
    293 sub UpdatePrefix {
    294   my $x = shift;
    295   my $y = basename($x);
    296   $x =~ s/\Q$y\E$//;
    297 
    298   if (!defined $Prefix) {
    299     $Prefix = $x;
    300     return;
    301   }
    302 
    303   chop $Prefix while (!($x =~ /^\Q$Prefix/));
    304 }
    305 
    306 sub GetPrefix {
    307   return $Prefix;
    308 }
    309 
    310 ##----------------------------------------------------------------------------##
    311 #  UpdateInFilePath - Update the path in the report file.
    312 ##----------------------------------------------------------------------------##
    313 
    314 sub UpdateInFilePath {
    315   my $fname = shift;
    316   my $regex = shift;
    317   my $newtext = shift;
    318 
    319   open (RIN, $fname) or die "cannot open $fname";
    320   open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
    321 
    322   while (<RIN>) {
    323     s/$regex/$newtext/;
    324     print ROUT $_;
    325   }
    326 
    327   close (ROUT);
    328   close (RIN);
    329   rename("$fname.tmp", $fname)
    330 }
    331 
    332 ##----------------------------------------------------------------------------##
    333 # AddStatLine - Decode and insert a statistics line into the database.
    334 ##----------------------------------------------------------------------------##
    335 
    336 sub AddStatLine {
    337   my $Line  = shift;
    338   my $Stats = shift;
    339   my $File  = shift;
    340 
    341   print $Line . "\n";
    342 
    343   my $Regex = qr/(.*?)\ ->\ Total\ CFGBlocks:\ (\d+)\ \|\ Unreachable
    344       \ CFGBlocks:\ (\d+)\ \|\ Exhausted\ Block:\ (yes|no)\ \|\ Empty\ WorkList:
    345       \ (yes|no)/x;
    346 
    347   if ($Line !~ $Regex) {
    348     return;
    349   }
    350 
    351   # Create a hash of the interesting fields
    352   my $Row = {
    353     Filename    => $File,
    354     Function    => $1,
    355     Total       => $2,
    356     Unreachable => $3,
    357     Aborted     => $4,
    358     Empty       => $5
    359   };
    360 
    361   # Add them to the stats array
    362   push @$Stats, $Row;
    363 }
    364 
    365 ##----------------------------------------------------------------------------##
    366 # ScanFile - Scan a report file for various identifying attributes.
    367 ##----------------------------------------------------------------------------##
    368 
    369 # Sometimes a source file is scanned more than once, and thus produces
    370 # multiple error reports.  We use a cache to solve this problem.
    371 
    372 my %AlreadyScanned;
    373 
    374 sub ScanFile {
    375 
    376   my $Index = shift;
    377   my $Dir = shift;
    378   my $FName = shift;
    379   my $Stats = shift;
    380 
    381   # Compute a digest for the report file.  Determine if we have already
    382   # scanned a file that looks just like it.
    383 
    384   my $digest = ComputeDigest("$Dir/$FName");
    385 
    386   if (defined $AlreadyScanned{$digest}) {
    387     # Redundant file.  Remove it.
    388     unlink("$Dir/$FName");
    389     return;
    390   }
    391 
    392   $AlreadyScanned{$digest} = 1;
    393 
    394   # At this point the report file is not world readable.  Make it happen.
    395   chmod(0644, "$Dir/$FName");
    396 
    397   # Scan the report file for tags.
    398   open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
    399 
    400   my $BugType        = "";
    401   my $BugFile        = "";
    402   my $BugFunction    = "";
    403   my $BugCategory    = "";
    404   my $BugDescription = "";
    405   my $BugPathLength  = 1;
    406   my $BugLine        = 0;
    407 
    408   while (<IN>) {
    409     last if (/<!-- BUGMETAEND -->/);
    410 
    411     if (/<!-- BUGTYPE (.*) -->$/) {
    412       $BugType = $1;
    413     }
    414     elsif (/<!-- BUGFILE (.*) -->$/) {
    415       $BugFile = abs_path($1);
    416       if (!defined $BugFile) {
    417          # The file no longer exists: use the original path.
    418          $BugFile = $1;
    419       }
    420       UpdatePrefix($BugFile);
    421     }
    422     elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
    423       $BugPathLength = $1;
    424     }
    425     elsif (/<!-- BUGLINE (.*) -->$/) {
    426       $BugLine = $1;
    427     }
    428     elsif (/<!-- BUGCATEGORY (.*) -->$/) {
    429       $BugCategory = $1;
    430     }
    431     elsif (/<!-- BUGDESC (.*) -->$/) {
    432       $BugDescription = $1;
    433     }
    434     elsif (/<!-- FUNCTIONNAME (.*) -->$/) {
    435       $BugFunction = $1;
    436     }
    437 
    438   }
    439 
    440 
    441   close(IN);
    442 
    443   if (!defined $BugCategory) {
    444     $BugCategory = "Other";
    445   }
    446 
    447   # Don't add internal statistics to the bug reports
    448   if ($BugCategory =~ /statistics/i) {
    449     AddStatLine($BugDescription, $Stats, $BugFile);
    450     return;
    451   }
    452 
    453   push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugFunction, $BugLine,
    454                  $BugPathLength ];
    455 }
    456 
    457 ##----------------------------------------------------------------------------##
    458 # CopyFiles - Copy resource files to target directory.
    459 ##----------------------------------------------------------------------------##
    460 
    461 sub CopyFiles {
    462 
    463   my $Dir = shift;
    464 
    465   my $JS = Cwd::realpath("$RealBin/../share/scan-build/sorttable.js");
    466 
    467   DieDiag("Cannot find 'sorttable.js'.\n")
    468     if (! -r $JS);
    469 
    470   copy($JS, "$Dir");
    471 
    472   DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
    473     if (! -r "$Dir/sorttable.js");
    474 
    475   my $CSS = Cwd::realpath("$RealBin/../share/scan-build/scanview.css");
    476 
    477   DieDiag("Cannot find 'scanview.css'.\n")
    478     if (! -r $CSS);
    479 
    480   copy($CSS, "$Dir");
    481 
    482   DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
    483     if (! -r $CSS);
    484 }
    485 
    486 ##----------------------------------------------------------------------------##
    487 # CalcStats - Calculates visitation statistics and returns the string.
    488 ##----------------------------------------------------------------------------##
    489 
    490 sub CalcStats {
    491   my $Stats = shift;
    492 
    493   my $TotalBlocks = 0;
    494   my $UnreachedBlocks = 0;
    495   my $TotalFunctions = scalar(@$Stats);
    496   my $BlockAborted = 0;
    497   my $WorkListAborted = 0;
    498   my $Aborted = 0;
    499 
    500   # Calculate the unique files
    501   my $FilesHash = {};
    502 
    503   foreach my $Row (@$Stats) {
    504     $FilesHash->{$Row->{Filename}} = 1;
    505     $TotalBlocks += $Row->{Total};
    506     $UnreachedBlocks += $Row->{Unreachable};
    507     $BlockAborted++ if $Row->{Aborted} eq 'yes';
    508     $WorkListAborted++ if $Row->{Empty} eq 'no';
    509     $Aborted++ if $Row->{Aborted} eq 'yes' || $Row->{Empty} eq 'no';
    510   }
    511 
    512   my $TotalFiles = scalar(keys(%$FilesHash));
    513 
    514   # Calculations
    515   my $PercentAborted = sprintf("%.2f", $Aborted / $TotalFunctions * 100);
    516   my $PercentBlockAborted = sprintf("%.2f", $BlockAborted / $TotalFunctions
    517       * 100);
    518   my $PercentWorkListAborted = sprintf("%.2f", $WorkListAborted /
    519       $TotalFunctions * 100);
    520   my $PercentBlocksUnreached = sprintf("%.2f", $UnreachedBlocks / $TotalBlocks
    521       * 100);
    522 
    523   my $StatsString = "Analyzed $TotalBlocks blocks in $TotalFunctions functions"
    524     . " in $TotalFiles files\n"
    525     . "$Aborted functions aborted early ($PercentAborted%)\n"
    526     . "$BlockAborted had aborted blocks ($PercentBlockAborted%)\n"
    527     . "$WorkListAborted had unfinished worklists ($PercentWorkListAborted%)\n"
    528     . "$UnreachedBlocks blocks were never reached ($PercentBlocksUnreached%)\n";
    529 
    530   return $StatsString;
    531 }
    532 
    533 ##----------------------------------------------------------------------------##
    534 # Postprocess - Postprocess the results of an analysis scan.
    535 ##----------------------------------------------------------------------------##
    536 
    537 my @filesFound;
    538 my $baseDir;
    539 sub FileWanted {
    540     my $baseDirRegEx = quotemeta $baseDir;
    541     my $file = $File::Find::name;
    542 
    543     # The name of the file is generated by clang binary (HTMLDiagnostics.cpp)
    544     if ($file =~ /report-.*\.html$/) {
    545        my $relative_file = $file;
    546        $relative_file =~ s/$baseDirRegEx//g;
    547        push @filesFound, $relative_file;
    548     }
    549 }
    550 
    551 sub Postprocess {
    552 
    553   my $Dir           = shift;
    554   my $BaseDir       = shift;
    555   my $AnalyzerStats = shift;
    556   my $KeepEmpty     = shift;
    557 
    558   die "No directory specified." if (!defined $Dir);
    559 
    560   if (! -d $Dir) {
    561     Diag("No bugs found.\n");
    562     return 0;
    563   }
    564 
    565   $baseDir = $Dir . "/";
    566   find({ wanted => \&FileWanted, follow => 0}, $Dir);
    567 
    568   if (scalar(@filesFound) == 0 and ! -e "$Dir/failures") {
    569     if (! $KeepEmpty) {
    570       Diag("Removing directory '$Dir' because it contains no reports.\n");
    571       rmtree($Dir) or die "Cannot rmtree '$Dir' : $!";
    572     }
    573     Diag("No bugs found.\n");
    574     return 0;
    575   }
    576 
    577   # Scan each report file and build an index.
    578   my @Index;
    579   my @Stats;
    580   foreach my $file (@filesFound) { ScanFile(\@Index, $Dir, $file, \@Stats); }
    581 
    582   # Scan the failures directory and use the information in the .info files
    583   # to update the common prefix directory.
    584   my @failures;
    585   my @attributes_ignored;
    586   if (-d "$Dir/failures") {
    587     opendir(DIR, "$Dir/failures");
    588     @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
    589     closedir(DIR);
    590     opendir(DIR, "$Dir/failures");
    591     @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
    592     closedir(DIR);
    593     foreach my $file (@failures) {
    594       open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
    595       my $Path = <IN>;
    596       if (defined $Path) { UpdatePrefix($Path); }
    597       close IN;
    598     }
    599   }
    600 
    601   # Generate an index.html file.
    602   my $FName = "$Dir/index.html";
    603   open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
    604 
    605   # Print out the header.
    606 
    607 print OUT <<ENDTEXT;
    608 <html>
    609 <head>
    610 <title>${Options{HtmlTitle}}</title>
    611 <link type="text/css" rel="stylesheet" href="scanview.css"/>
    612 <script src="sorttable.js"></script>
    613 <script language='javascript' type="text/javascript">
    614 function SetDisplay(RowClass, DisplayVal)
    615 {
    616   var Rows = document.getElementsByTagName("tr");
    617   for ( var i = 0 ; i < Rows.length; ++i ) {
    618     if (Rows[i].className == RowClass) {
    619       Rows[i].style.display = DisplayVal;
    620     }
    621   }
    622 }
    623 
    624 function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
    625   var Inputs = document.getElementsByTagName("input");
    626   for ( var i = 0 ; i < Inputs.length; ++i ) {
    627     if (Inputs[i].type == "checkbox") {
    628       if(Inputs[i] != SummaryCheckButton) {
    629         Inputs[i].checked = SummaryCheckButton.checked;
    630         Inputs[i].onclick();
    631       }
    632     }
    633   }
    634 }
    635 
    636 function returnObjById( id ) {
    637     if (document.getElementById)
    638         var returnVar = document.getElementById(id);
    639     else if (document.all)
    640         var returnVar = document.all[id];
    641     else if (document.layers)
    642         var returnVar = document.layers[id];
    643     return returnVar;
    644 }
    645 
    646 var NumUnchecked = 0;
    647 
    648 function ToggleDisplay(CheckButton, ClassName) {
    649   if (CheckButton.checked) {
    650     SetDisplay(ClassName, "");
    651     if (--NumUnchecked == 0) {
    652       returnObjById("AllBugsCheck").checked = true;
    653     }
    654   }
    655   else {
    656     SetDisplay(ClassName, "none");
    657     NumUnchecked++;
    658     returnObjById("AllBugsCheck").checked = false;
    659   }
    660 }
    661 </script>
    662 <!-- SUMMARYENDHEAD -->
    663 </head>
    664 <body>
    665 <h1>${Options{HtmlTitle}}</h1>
    666 
    667 <table>
    668 <tr><th>User:</th><td>${UserName}\@${HostName}</td></tr>
    669 <tr><th>Working Directory:</th><td>${CurrentDir}</td></tr>
    670 <tr><th>Command Line:</th><td>${CmdArgs}</td></tr>
    671 <tr><th>Clang Version:</th><td>${ClangVersion}</td></tr>
    672 <tr><th>Date:</th><td>${Date}</td></tr>
    673 ENDTEXT
    674 
    675 print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n"
    676   if (defined($BuildName) && defined($BuildDate));
    677 
    678 print OUT <<ENDTEXT;
    679 </table>
    680 ENDTEXT
    681 
    682   if (scalar(@filesFound)) {
    683     # Print out the summary table.
    684     my %Totals;
    685 
    686     for my $row ( @Index ) {
    687       my $bug_type = ($row->[2]);
    688       my $bug_category = ($row->[1]);
    689       my $key = "$bug_category:$bug_type";
    690 
    691       if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; }
    692       else { $Totals{$key}->[0]++; }
    693     }
    694 
    695     print OUT "<h2>Bug Summary</h2>";
    696 
    697     if (defined $BuildName) {
    698       print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n"
    699     }
    700 
    701   my $TotalBugs = scalar(@Index);
    702 print OUT <<ENDTEXT;
    703 <table>
    704 <thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead>
    705 <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>
    706 ENDTEXT
    707 
    708     my $last_category;
    709 
    710     for my $key (
    711       sort {
    712         my $x = $Totals{$a};
    713         my $y = $Totals{$b};
    714         my $res = $x->[1] cmp $y->[1];
    715         $res = $x->[2] cmp $y->[2] if ($res == 0);
    716         $res
    717       } keys %Totals )
    718     {
    719       my $val = $Totals{$key};
    720       my $category = $val->[1];
    721       if (!defined $last_category or $last_category ne $category) {
    722         $last_category = $category;
    723         print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n";
    724       }
    725       my $x = lc $key;
    726       $x =~ s/[ ,'":\/()]+/_/g;
    727       print OUT "<tr><td class=\"SUMM_DESC\">";
    728       print OUT $val->[2];
    729       print OUT "</td><td class=\"Q\">";
    730       print OUT $val->[0];
    731       print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n";
    732     }
    733 
    734   # Print out the table of errors.
    735 
    736 print OUT <<ENDTEXT;
    737 </table>
    738 <h2>Reports</h2>
    739 
    740 <table class="sortable" style="table-layout:automatic">
    741 <thead><tr>
    742   <td>Bug Group</td>
    743   <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind">&nbsp;&#x25BE;</span></td>
    744   <td>File</td>
    745   <td>Function/Method</td>
    746   <td class="Q">Line</td>
    747   <td class="Q">Path Length</td>
    748   <td class="sorttable_nosort"></td>
    749   <!-- REPORTBUGCOL -->
    750 </tr></thead>
    751 <tbody>
    752 ENDTEXT
    753 
    754     my $prefix = GetPrefix();
    755     my $regex;
    756     my $InFileRegex;
    757     my $InFilePrefix = "File:</td><td>";
    758 
    759     if (defined $prefix) {
    760       $regex = qr/^\Q$prefix\E/is;
    761       $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
    762     }
    763 
    764     for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
    765       my $x = "$row->[1]:$row->[2]";
    766       $x = lc $x;
    767       $x =~ s/[ ,'":\/()]+/_/g;
    768 
    769       my $ReportFile = $row->[0];
    770 
    771       print OUT "<tr class=\"bt_$x\">";
    772       print OUT "<td class=\"DESC\">";
    773       print OUT $row->[1];
    774       print OUT "</td>";
    775       print OUT "<td class=\"DESC\">";
    776       print OUT $row->[2];
    777       print OUT "</td>";
    778 
    779       # Update the file prefix.
    780       my $fname = $row->[3];
    781 
    782       if (defined $regex) {
    783         $fname =~ s/$regex//;
    784         UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
    785       }
    786 
    787       print OUT "<td>";
    788       my @fname = split /\//,$fname;
    789       if ($#fname > 0) {
    790         while ($#fname >= 0) {
    791           my $x = shift @fname;
    792           print OUT $x;
    793           if ($#fname >= 0) {
    794             print OUT "/";
    795           }
    796         }
    797       }
    798       else {
    799         print OUT $fname;
    800       }
    801       print OUT "</td>";
    802 
    803       print OUT "<td class=\"DESC\">";
    804       print OUT $row->[4];
    805       print OUT "</td>";
    806 
    807       # Print out the quantities.
    808       for my $j ( 5 .. 6 ) {
    809         print OUT "<td class=\"Q\">$row->[$j]</td>";
    810       }
    811 
    812       # Print the rest of the columns.
    813       for (my $j = 7; $j <= $#{$row}; ++$j) {
    814         print OUT "<td>$row->[$j]</td>"
    815       }
    816 
    817       # Emit the "View" link.
    818       print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
    819 
    820       # Emit REPORTBUG markers.
    821       print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
    822 
    823       # End the row.
    824       print OUT "</tr>\n";
    825     }
    826 
    827     print OUT "</tbody>\n</table>\n\n";
    828   }
    829 
    830   if (scalar (@failures) || scalar(@attributes_ignored)) {
    831     print OUT "<h2>Analyzer Failures</h2>\n";
    832 
    833     if (scalar @attributes_ignored) {
    834       print OUT "The analyzer's parser ignored the following attributes:<p>\n";
    835       print OUT "<table>\n";
    836       print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
    837       foreach my $file (sort @attributes_ignored) {
    838         die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
    839         my $attribute = $1;
    840         # Open the attribute file to get the first file that failed.
    841         next if (!open (ATTR, "$Dir/failures/$file"));
    842         my $ppfile = <ATTR>;
    843         chomp $ppfile;
    844         close ATTR;
    845         next if (! -e "$Dir/failures/$ppfile");
    846         # Open the info file and get the name of the source file.
    847         open (INFO, "$Dir/failures/$ppfile.info.txt") or
    848           die "Cannot open $Dir/failures/$ppfile.info.txt\n";
    849         my $srcfile = <INFO>;
    850         chomp $srcfile;
    851         close (INFO);
    852         # Print the information in the table.
    853         my $prefix = GetPrefix();
    854         if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
    855         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";
    856         my $ppfile_clang = $ppfile;
    857         $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
    858         print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
    859       }
    860       print OUT "</table>\n";
    861     }
    862 
    863     if (scalar @failures) {
    864       print OUT "<p>The analyzer had problems processing the following files:</p>\n";
    865       print OUT "<table>\n";
    866       print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
    867       foreach my $file (sort @failures) {
    868         $file =~ /(.+).info.txt$/;
    869         # Get the preprocessed file.
    870         my $ppfile = $1;
    871         # Open the info file and get the name of the source file.
    872         open (INFO, "$Dir/failures/$file") or
    873           die "Cannot open $Dir/failures/$file\n";
    874         my $srcfile = <INFO>;
    875         chomp $srcfile;
    876         my $problem = <INFO>;
    877         chomp $problem;
    878         close (INFO);
    879         # Print the information in the table.
    880         my $prefix = GetPrefix();
    881         if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
    882         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";
    883         my $ppfile_clang = $ppfile;
    884         $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
    885         print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
    886       }
    887       print OUT "</table>\n";
    888     }
    889     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";
    890   }
    891 
    892   print OUT "</body></html>\n";
    893   close(OUT);
    894   CopyFiles($Dir);
    895 
    896   # Make sure $Dir and $BaseDir are world readable/executable.
    897   chmod(0755, $Dir);
    898   if (defined $BaseDir) { chmod(0755, $BaseDir); }
    899 
    900   # Print statistics
    901   print CalcStats(\@Stats) if $AnalyzerStats;
    902 
    903   my $Num = scalar(@Index);
    904   if ($Num == 1) {
    905     Diag("$Num bug found.\n");
    906   } else {
    907     Diag("$Num bugs found.\n");
    908   }
    909   if ($Num > 0 && -r "$Dir/index.html") {
    910     Diag("Run 'scan-view $Dir' to examine bug reports.\n");
    911   }
    912 
    913   DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
    914 
    915   return $Num;
    916 }
    917 
    918 ##----------------------------------------------------------------------------##
    919 # RunBuildCommand - Run the build command.
    920 ##----------------------------------------------------------------------------##
    921 
    922 sub AddIfNotPresent {
    923   my $Args = shift;
    924   my $Arg = shift;
    925   my $found = 0;
    926 
    927   foreach my $k (@$Args) {
    928     if ($k eq $Arg) {
    929       $found = 1;
    930       last;
    931     }
    932   }
    933 
    934   if ($found == 0) {
    935     push @$Args, $Arg;
    936   }
    937 }
    938 
    939 sub SetEnv {
    940   my $EnvVars = shift @_;
    941   foreach my $var ('CC', 'CXX', 'CLANG', 'CLANG_CXX',
    942                    'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS',
    943                    'CCC_ANALYZER_CONFIG') {
    944     die "$var is undefined\n" if (!defined $var);
    945     $ENV{$var} = $EnvVars->{$var};
    946   }
    947   foreach my $var ('CCC_ANALYZER_STORE_MODEL',
    948                    'CCC_ANALYZER_CONSTRAINTS_MODEL',
    949                    'CCC_ANALYZER_INTERNAL_STATS',
    950                    'CCC_ANALYZER_OUTPUT_FORMAT',
    951                    'CCC_CC',
    952                    'CCC_CXX',
    953                    'CCC_REPORT_FAILURES',
    954                    'CLANG_ANALYZER_TARGET') {
    955     my $x = $EnvVars->{$var};
    956     if (defined $x) { $ENV{$var} = $x }
    957   }
    958   my $Verbose = $EnvVars->{'VERBOSE'};
    959   if ($Verbose >= 2) {
    960     $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
    961   }
    962   if ($Verbose >= 3) {
    963     $ENV{'CCC_ANALYZER_LOG'} = 1;
    964   }
    965 }
    966 
    967 sub RunXcodebuild {
    968   my $Args = shift;
    969   my $IgnoreErrors = shift;
    970   my $CCAnalyzer = shift;
    971   my $CXXAnalyzer = shift;
    972   my $EnvVars = shift;
    973 
    974   if ($IgnoreErrors) {
    975     AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
    976   }
    977 
    978   # Detect the version of Xcode.  If Xcode 4.6 or higher, use new
    979   # in situ support for analyzer interposition without needed to override
    980   # the compiler.
    981   open(DETECT_XCODE, "-|", $Args->[0], "-version") or
    982     die "error: cannot detect version of xcodebuild\n";
    983 
    984   my $oldBehavior = 1;
    985 
    986   while(<DETECT_XCODE>) {
    987     if (/^Xcode (.+)$/) {
    988       my $ver = $1;
    989       if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) {
    990         if ($1 >= 4.6) {
    991           $oldBehavior = 0;
    992           last;
    993         }
    994       }
    995     }
    996   }
    997   close(DETECT_XCODE);
    998 
    999   # If --override-compiler is explicitely requested, resort to the old
   1000   # behavior regardless of Xcode version.
   1001   if ($Options{OverrideCompiler}) {
   1002     $oldBehavior = 1;
   1003   }
   1004 
   1005   if ($oldBehavior == 0) {
   1006     my $OutputDir = $EnvVars->{"OUTPUT_DIR"};
   1007     my $CLANG = $EnvVars->{"CLANG"};
   1008     my $OtherFlags = $EnvVars->{"CCC_ANALYZER_ANALYSIS"};
   1009     push @$Args,
   1010         "RUN_CLANG_STATIC_ANALYZER=YES",
   1011         "CLANG_ANALYZER_OUTPUT=plist-html",
   1012         "CLANG_ANALYZER_EXEC=$CLANG",
   1013         "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir",
   1014         "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags";
   1015 
   1016     return (system(@$Args) >> 8);
   1017   }
   1018 
   1019   # Default to old behavior where we insert a bogus compiler.
   1020   SetEnv($EnvVars);
   1021 
   1022   # Check if using iPhone SDK 3.0 (simulator).  If so the compiler being
   1023   # used should be gcc-4.2.
   1024   if (!defined $ENV{"CCC_CC"}) {
   1025     for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
   1026       if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
   1027         if (@$Args[$i+1] =~ /^iphonesimulator3/) {
   1028           $ENV{"CCC_CC"} = "gcc-4.2";
   1029           $ENV{"CCC_CXX"} = "g++-4.2";
   1030         }
   1031       }
   1032     }
   1033   }
   1034 
   1035   # Disable PCH files until clang supports them.
   1036   AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
   1037 
   1038   # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
   1039   # linking C++ object files.  Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
   1040   # (via c++-analyzer) when linking such files.
   1041   $ENV{"LDPLUSPLUS"} = $CXXAnalyzer;
   1042 
   1043   return (system(@$Args) >> 8);
   1044 }
   1045 
   1046 sub RunBuildCommand {
   1047   my $Args = shift;
   1048   my $IgnoreErrors = shift;
   1049   my $Cmd = $Args->[0];
   1050   my $CCAnalyzer = shift;
   1051   my $CXXAnalyzer = shift;
   1052   my $EnvVars = shift;
   1053 
   1054   if ($Cmd =~ /\bxcodebuild$/) {
   1055     return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $EnvVars);
   1056   }
   1057 
   1058   # Setup the environment.
   1059   SetEnv($EnvVars);
   1060 
   1061   if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or
   1062       $Cmd =~ /(.*\/?cc[^\/]*$)/ or
   1063       $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
   1064       $Cmd =~ /(.*\/?clang$)/ or
   1065       $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
   1066 
   1067     if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
   1068       $ENV{"CCC_CC"} = $1;
   1069     }
   1070 
   1071     shift @$Args;
   1072     unshift @$Args, $CCAnalyzer;
   1073   }
   1074   elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or
   1075         $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or
   1076         $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or
   1077         $Cmd =~ /(.*\/?clang\+\+$)/ or
   1078         $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) {
   1079     if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) {
   1080       $ENV{"CCC_CXX"} = $1;
   1081     }
   1082     shift @$Args;
   1083     unshift @$Args, $CXXAnalyzer;
   1084   }
   1085   elsif ($Cmd eq "make" or $Cmd eq "gmake" or $Cmd eq "mingw32-make") {
   1086     AddIfNotPresent($Args, "CC=$CCAnalyzer");
   1087     AddIfNotPresent($Args, "CXX=$CXXAnalyzer");
   1088     if ($IgnoreErrors) {
   1089       AddIfNotPresent($Args,"-k");
   1090       AddIfNotPresent($Args,"-i");
   1091     }
   1092   }
   1093 
   1094   return (system(@$Args) >> 8);
   1095 }
   1096 
   1097 ##----------------------------------------------------------------------------##
   1098 # DisplayHelp - Utility function to display all help options.
   1099 ##----------------------------------------------------------------------------##
   1100 
   1101 sub DisplayHelp {
   1102 
   1103   my $ArgClangNotFoundErrMsg = shift;
   1104 print <<ENDTEXT;
   1105 USAGE: $Prog [options] <build command> [build options]
   1106 
   1107 ENDTEXT
   1108 
   1109   if (defined $BuildName) {
   1110     print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
   1111   }
   1112 
   1113 print <<ENDTEXT;
   1114 OPTIONS:
   1115 
   1116  -analyze-headers
   1117 
   1118    Also analyze functions in #included files.  By default, such functions
   1119    are skipped unless they are called by functions within the main source file.
   1120 
   1121  -o <output location>
   1122 
   1123    Specifies the output directory for analyzer reports. Subdirectories will be
   1124    created as needed to represent separate "runs" of the analyzer. If this
   1125    option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X)
   1126    to store the reports.
   1127 
   1128  -h
   1129  --help
   1130 
   1131    Display this message.
   1132 
   1133  -k
   1134  --keep-going
   1135 
   1136    Add a "keep on going" option to the specified build command. This option
   1137    currently supports make and xcodebuild. This is a convenience option; one
   1138    can specify this behavior directly using build options.
   1139 
   1140  --html-title [title]
   1141  --html-title=[title]
   1142 
   1143    Specify the title used on generated HTML pages. If not specified, a default
   1144    title will be used.
   1145 
   1146  -plist
   1147 
   1148    By default the output of scan-build is a set of HTML files. This option
   1149    outputs the results as a set of .plist files.
   1150 
   1151  -plist-html
   1152 
   1153    By default the output of scan-build is a set of HTML files. This option
   1154    outputs the results as a set of HTML and .plist files.
   1155 
   1156  --status-bugs
   1157 
   1158    By default, the exit status of scan-build is the same as the executed build
   1159    command. Specifying this option causes the exit status of scan-build to be 1
   1160    if it found potential bugs and 0 otherwise.
   1161 
   1162  --use-cc [compiler path]
   1163  --use-cc=[compiler path]
   1164 
   1165    scan-build analyzes a project by interposing a "fake compiler", which
   1166    executes a real compiler for compilation and the static analyzer for analysis.
   1167    Because of the current implementation of interposition, scan-build does not
   1168    know what compiler your project normally uses.  Instead, it simply overrides
   1169    the CC environment variable, and guesses your default compiler.
   1170 
   1171    In the future, this interposition mechanism to be improved, but if you need
   1172    scan-build to use a specific compiler for *compilation* then you can use
   1173    this option to specify a path to that compiler.
   1174 
   1175    If the given compiler is a cross compiler, you may also need to provide
   1176    --analyzer-target option to properly analyze the source code because static
   1177    analyzer runs as if the code is compiled for the host machine by default.
   1178 
   1179  --use-c++ [compiler path]
   1180  --use-c++=[compiler path]
   1181 
   1182    This is the same as "--use-cc" but for C++ code.
   1183 
   1184  --analyzer-target [target triple name for analysis]
   1185  --analyzer-target=[target triple name for analysis]
   1186 
   1187    This provides target triple information to clang static analyzer.
   1188    It only changes the target for analysis but doesn't change the target of a
   1189    real compiler given by --use-cc and --use-c++ options.
   1190 
   1191  -v
   1192 
   1193    Enable verbose output from scan-build. A second and third '-v' increases
   1194    verbosity.
   1195 
   1196  -V
   1197  --view
   1198 
   1199    View analysis results in a web browser when the build completes.
   1200 
   1201 ADVANCED OPTIONS:
   1202 
   1203  -no-failure-reports
   1204 
   1205    Do not create a 'failures' subdirectory that includes analyzer crash reports
   1206    and preprocessed source files.
   1207 
   1208  -stats
   1209 
   1210    Generates visitation statistics for the project being analyzed.
   1211 
   1212  -maxloop <loop count>
   1213 
   1214    Specifiy the number of times a block can be visited before giving up.
   1215    Default is 4. Increase for more comprehensive coverage at a cost of speed.
   1216 
   1217  -internal-stats
   1218 
   1219    Generate internal analyzer statistics.
   1220 
   1221  --use-analyzer [Xcode|path to clang]
   1222  --use-analyzer=[Xcode|path to clang]
   1223 
   1224    scan-build uses the 'clang' executable relative to itself for static
   1225    analysis. One can override this behavior with this option by using the
   1226    'clang' packaged with Xcode (on OS X) or from the PATH.
   1227 
   1228  --keep-empty
   1229 
   1230    Don't remove the build results directory even if no issues were reported.
   1231 
   1232  --override-compiler
   1233    Always resort to the ccc-analyzer even when better interposition methods
   1234    are available.
   1235 
   1236  -analyzer-config <options>
   1237 
   1238    Provide options to pass through to the analyzer's -analyzer-config flag.
   1239    Several options are separated with comma: 'key1=val1,key2=val2'
   1240 
   1241    Available options:
   1242      * stable-report-filename=true or false (default)
   1243        Switch the page naming to:
   1244        report-<filename>-<function/method name>-<id>.html
   1245        instead of report-XXXXXX.html
   1246 
   1247 CONTROLLING CHECKERS:
   1248 
   1249  A default group of checkers are always run unless explicitly disabled.
   1250  Checkers may be enabled/disabled using the following options:
   1251 
   1252  -enable-checker [checker name]
   1253  -disable-checker [checker name]
   1254 
   1255 LOADING CHECKERS:
   1256 
   1257  Loading external checkers using the clang plugin interface:
   1258 
   1259  -load-plugin [plugin library]
   1260 ENDTEXT
   1261 
   1262   if (defined $Clang && -x $Clang) {
   1263     # Query clang for list of checkers that are enabled.
   1264 
   1265     # create a list to load the plugins via the 'Xclang' command line
   1266     # argument
   1267     my @PluginLoadCommandline_xclang;
   1268     foreach my $param ( @{$Options{PluginsToLoad}} ) {
   1269       push ( @PluginLoadCommandline_xclang, "-Xclang" );
   1270       push ( @PluginLoadCommandline_xclang, "-load" );
   1271       push ( @PluginLoadCommandline_xclang, "-Xclang" );
   1272       push ( @PluginLoadCommandline_xclang, $param );
   1273     }
   1274 
   1275     my %EnabledCheckers;
   1276     foreach my $lang ("c", "objective-c", "objective-c++", "c++") {
   1277       my $ExecLine = join(' ', qq/"$Clang"/, @PluginLoadCommandline_xclang, "--analyze", "-x", $lang, "-", "-###", "2>&1", "|");
   1278       open(PS, $ExecLine);
   1279       while (<PS>) {
   1280         foreach my $val (split /\s+/) {
   1281           $val =~ s/\"//g;
   1282           if ($val =~ /-analyzer-checker\=([^\s]+)/) {
   1283             $EnabledCheckers{$1} = 1;
   1284           }
   1285         }
   1286       }
   1287     }
   1288 
   1289     # Query clang for complete list of checkers.
   1290     my @PluginLoadCommandline;
   1291     foreach my $param ( @{$Options{PluginsToLoad}} ) {
   1292       push ( @PluginLoadCommandline, "-load" );
   1293       push ( @PluginLoadCommandline, $param );
   1294     }
   1295 
   1296     my $ExecLine = join(' ', qq/"$Clang"/, "-cc1", @PluginLoadCommandline, "-analyzer-checker-help", "2>&1", "|");
   1297     open(PS, $ExecLine);
   1298     my $foundCheckers = 0;
   1299     while (<PS>) {
   1300       if (/CHECKERS:/) {
   1301         $foundCheckers = 1;
   1302         last;
   1303       }
   1304     }
   1305     if (!$foundCheckers) {
   1306       print "  *** Could not query Clang for the list of available checkers.";
   1307     }
   1308     else {
   1309       print("\nAVAILABLE CHECKERS:\n\n");
   1310       my $skip = 0;
   1311        while(<PS>) {
   1312         if (/experimental/) {
   1313           $skip = 1;
   1314           next;
   1315         }
   1316         if ($skip) {
   1317           next if (!/^\s\s[^\s]/);
   1318           $skip = 0;
   1319         }
   1320         s/^\s\s//;
   1321         if (/^([^\s]+)/) {
   1322           # Is the checker enabled?
   1323           my $checker = $1;
   1324           my $enabled = 0;
   1325           my $aggregate = "";
   1326           foreach my $domain (split /\./, $checker) {
   1327             $aggregate .= $domain;
   1328             if ($EnabledCheckers{$aggregate}) {
   1329               $enabled =1;
   1330               last;
   1331             }
   1332             # append a dot, if an additional domain is added in the next iteration
   1333             $aggregate .= ".";
   1334           }
   1335 
   1336           if ($enabled) {
   1337             print " + ";
   1338           }
   1339           else {
   1340             print "   ";
   1341           }
   1342         }
   1343         else {
   1344           print "   ";
   1345         }
   1346         print $_;
   1347       }
   1348       print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n";
   1349     }
   1350     close PS;
   1351   }
   1352   else {
   1353     print "  *** Could not query Clang for the list of available checkers.\n";
   1354     if (defined  $ArgClangNotFoundErrMsg) {
   1355       print "  *** Reason: $ArgClangNotFoundErrMsg\n";
   1356     }
   1357   }
   1358 
   1359 print <<ENDTEXT
   1360 
   1361 BUILD OPTIONS
   1362 
   1363  You can specify any build option acceptable to the build command.
   1364 
   1365 EXAMPLE
   1366 
   1367  scan-build -o /tmp/myhtmldir make -j4
   1368 
   1369 The above example causes analysis reports to be deposited into a subdirectory
   1370 of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different
   1371 subdirectory is created each time scan-build analyzes a project. The analyzer
   1372 should support most parallel builds, but not distributed builds.
   1373 
   1374 ENDTEXT
   1375 }
   1376 
   1377 ##----------------------------------------------------------------------------##
   1378 # HtmlEscape - HTML entity encode characters that are special in HTML
   1379 ##----------------------------------------------------------------------------##
   1380 
   1381 sub HtmlEscape {
   1382   # copy argument to new variable so we don't clobber the original
   1383   my $arg = shift || '';
   1384   my $tmp = $arg;
   1385   $tmp =~ s/&/&amp;/g;
   1386   $tmp =~ s/</&lt;/g;
   1387   $tmp =~ s/>/&gt;/g;
   1388   return $tmp;
   1389 }
   1390 
   1391 ##----------------------------------------------------------------------------##
   1392 # ShellEscape - backslash escape characters that are special to the shell
   1393 ##----------------------------------------------------------------------------##
   1394 
   1395 sub ShellEscape {
   1396   # copy argument to new variable so we don't clobber the original
   1397   my $arg = shift || '';
   1398   if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
   1399   return $arg;
   1400 }
   1401 
   1402 ##----------------------------------------------------------------------------##
   1403 # FindClang - searches for 'clang' executable.
   1404 ##----------------------------------------------------------------------------##
   1405 
   1406 sub FindClang {
   1407   if (!defined $Options{AnalyzerDiscoveryMethod}) {
   1408     $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang");
   1409     if (!defined $Clang || ! -x $Clang) {
   1410       $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang");
   1411     }
   1412     if (!defined $Clang || ! -x $Clang) {
   1413       return "error: Cannot find an executable 'clang' relative to" .
   1414              " scan-build. Consider using --use-analyzer to pick a version of" .
   1415              " 'clang' to use for static analysis.\n";
   1416     }
   1417   }
   1418   else {
   1419     if ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) {
   1420       my $xcrun = `which xcrun`;
   1421       chomp $xcrun;
   1422       if ($xcrun eq "") {
   1423         return "Cannot find 'xcrun' to find 'clang' for analysis.\n";
   1424       }
   1425       $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
   1426       chomp $Clang;
   1427       if ($Clang eq "") {
   1428         return "No 'clang' executable found by 'xcrun'\n";
   1429       }
   1430     }
   1431     else {
   1432       $Clang = $Options{AnalyzerDiscoveryMethod};
   1433       if (!defined $Clang or not -x $Clang) {
   1434         return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n";
   1435       }
   1436     }
   1437   }
   1438   return undef;
   1439 }
   1440 
   1441 ##----------------------------------------------------------------------------##
   1442 # Process command-line arguments.
   1443 ##----------------------------------------------------------------------------##
   1444 
   1445 my $RequestDisplayHelp = 0;
   1446 my $ForceDisplayHelp = 0;
   1447 
   1448 sub ProcessArgs {
   1449   my $Args = shift;
   1450   my $NumArgs = 0;
   1451 
   1452   while (@$Args) {
   1453 
   1454     $NumArgs++;
   1455 
   1456     # Scan for options we recognize.
   1457 
   1458     my $arg = $Args->[0];
   1459 
   1460     if ($arg eq "-h" or $arg eq "--help") {
   1461       $RequestDisplayHelp = 1;
   1462       shift @$Args;
   1463       next;
   1464     }
   1465 
   1466     if ($arg eq '-analyze-headers') {
   1467       shift @$Args;
   1468       $Options{AnalyzeHeaders} = 1;
   1469       next;
   1470     }
   1471 
   1472     if ($arg eq "-o") {
   1473       shift @$Args;
   1474 
   1475       if (!@$Args) {
   1476         DieDiag("'-o' option requires a target directory name.\n");
   1477       }
   1478 
   1479       # Construct an absolute path.  Uses the current working directory
   1480       # as a base if the original path was not absolute.
   1481       my $OutDir = shift @$Args;
   1482       mkpath($OutDir) unless (-e $OutDir);  # abs_path wants existing dir
   1483       $Options{OutputDir} = abs_path($OutDir);
   1484 
   1485       next;
   1486     }
   1487 
   1488     if ($arg =~ /^--html-title(=(.+))?$/) {
   1489       shift @$Args;
   1490 
   1491       if (!defined $2 || $2 eq '') {
   1492         if (!@$Args) {
   1493           DieDiag("'--html-title' option requires a string.\n");
   1494         }
   1495 
   1496         $Options{HtmlTitle} = shift @$Args;
   1497       } else {
   1498         $Options{HtmlTitle} = $2;
   1499       }
   1500 
   1501       next;
   1502     }
   1503 
   1504     if ($arg eq "-k" or $arg eq "--keep-going") {
   1505       shift @$Args;
   1506       $Options{IgnoreErrors} = 1;
   1507       next;
   1508     }
   1509 
   1510     if ($arg =~ /^--use-cc(=(.+))?$/) {
   1511       shift @$Args;
   1512       my $cc;
   1513 
   1514       if (!defined $2 || $2 eq "") {
   1515         if (!@$Args) {
   1516           DieDiag("'--use-cc' option requires a compiler executable name.\n");
   1517         }
   1518         $cc = shift @$Args;
   1519       }
   1520       else {
   1521         $cc = $2;
   1522       }
   1523 
   1524       $Options{UseCC} = $cc;
   1525       next;
   1526     }
   1527 
   1528     if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
   1529       shift @$Args;
   1530       my $cxx;
   1531 
   1532       if (!defined $2 || $2 eq "") {
   1533         if (!@$Args) {
   1534           DieDiag("'--use-c++' option requires a compiler executable name.\n");
   1535         }
   1536         $cxx = shift @$Args;
   1537       }
   1538       else {
   1539         $cxx = $2;
   1540       }
   1541 
   1542       $Options{UseCXX} = $cxx;
   1543       next;
   1544     }
   1545 
   1546     if ($arg =~ /^--analyzer-target(=(.+))?$/) {
   1547       shift @ARGV;
   1548       my $AnalyzerTarget;
   1549 
   1550       if (!defined $2 || $2 eq "") {
   1551         if (!@ARGV) {
   1552           DieDiag("'--analyzer-target' option requires a target triple name.\n");
   1553         }
   1554         $AnalyzerTarget = shift @ARGV;
   1555       }
   1556       else {
   1557         $AnalyzerTarget = $2;
   1558       }
   1559 
   1560       $Options{AnalyzerTarget} = $AnalyzerTarget;
   1561       next;
   1562     }
   1563 
   1564     if ($arg eq "-v") {
   1565       shift @$Args;
   1566       $Options{Verbose}++;
   1567       next;
   1568     }
   1569 
   1570     if ($arg eq "-V" or $arg eq "--view") {
   1571       shift @$Args;
   1572       $Options{ViewResults} = 1;
   1573       next;
   1574     }
   1575 
   1576     if ($arg eq "--status-bugs") {
   1577       shift @$Args;
   1578       $Options{ExitStatusFoundBugs} = 1;
   1579       next;
   1580     }
   1581 
   1582     if ($arg eq "-store") {
   1583       shift @$Args;
   1584       $Options{StoreModel} = shift @$Args;
   1585       next;
   1586     }
   1587 
   1588     if ($arg eq "-constraints") {
   1589       shift @$Args;
   1590       $Options{ConstraintsModel} = shift @$Args;
   1591       next;
   1592     }
   1593 
   1594     if ($arg eq "-internal-stats") {
   1595       shift @$Args;
   1596       $Options{InternalStats} = 1;
   1597       next;
   1598     }
   1599 
   1600     if ($arg eq "-plist") {
   1601       shift @$Args;
   1602       $Options{OutputFormat} = "plist";
   1603       next;
   1604     }
   1605 
   1606     if ($arg eq "-plist-html") {
   1607       shift @$Args;
   1608       $Options{OutputFormat} = "plist-html";
   1609       next;
   1610     }
   1611 
   1612     if ($arg eq "-analyzer-config") {
   1613       shift @$Args;
   1614       push @{$Options{ConfigOptions}}, shift @$Args;
   1615       next;
   1616     }
   1617 
   1618     if ($arg eq "-no-failure-reports") {
   1619       shift @$Args;
   1620       $Options{ReportFailures} = 0;
   1621       next;
   1622     }
   1623 
   1624     if ($arg eq "-stats") {
   1625       shift @$Args;
   1626       $Options{AnalyzerStats} = 1;
   1627       next;
   1628     }
   1629 
   1630     if ($arg eq "-maxloop") {
   1631       shift @$Args;
   1632       $Options{MaxLoop} = shift @$Args;
   1633       next;
   1634     }
   1635 
   1636     if ($arg eq "-enable-checker") {
   1637       shift @$Args;
   1638       my $Checker = shift @$Args;
   1639       # Store $NumArgs to preserve the order the checkers were enabled.
   1640       $Options{EnableCheckers}{$Checker} = $NumArgs;
   1641       delete $Options{DisableCheckers}{$Checker};
   1642       next;
   1643     }
   1644 
   1645     if ($arg eq "-disable-checker") {
   1646       shift @$Args;
   1647       my $Checker = shift @$Args;
   1648       # Store $NumArgs to preserve the order the checkers were disabled.
   1649       $Options{DisableCheckers}{$Checker} = $NumArgs;
   1650       delete $Options{EnableCheckers}{$Checker};
   1651       next;
   1652     }
   1653 
   1654     if ($arg eq "-load-plugin") {
   1655       shift @$Args;
   1656       push @{$Options{PluginsToLoad}}, shift @$Args;
   1657       next;
   1658     }
   1659 
   1660     if ($arg eq "--use-analyzer") {
   1661       shift @$Args;
   1662       $Options{AnalyzerDiscoveryMethod} = shift @$Args;
   1663       next;
   1664     }
   1665 
   1666     if ($arg =~ /^--use-analyzer=(.+)$/) {
   1667       shift @$Args;
   1668       $Options{AnalyzerDiscoveryMethod} = $1;
   1669       next;
   1670     }
   1671 
   1672     if ($arg eq "--keep-empty") {
   1673       shift @$Args;
   1674       $Options{KeepEmpty} = 1;
   1675       next;
   1676     }
   1677 
   1678     if ($arg eq "--override-compiler") {
   1679       shift @$Args;
   1680       $Options{OverrideCompiler} = 1;
   1681       next;
   1682     }
   1683 
   1684     DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
   1685 
   1686     $NumArgs--;
   1687     last;
   1688   }
   1689   return $NumArgs;
   1690 }
   1691 
   1692 if (!@ARGV) {
   1693   $ForceDisplayHelp = 1
   1694 }
   1695 
   1696 ProcessArgs(\@ARGV);
   1697 # All arguments are now shifted from @ARGV. The rest is a build command, if any.
   1698 
   1699 if (!@ARGV and !$RequestDisplayHelp) {
   1700   ErrorDiag("No build command specified.\n\n");
   1701   $ForceDisplayHelp = 1;
   1702 }
   1703 
   1704 my $ClangNotFoundErrMsg = FindClang();
   1705 
   1706 if ($ForceDisplayHelp || $RequestDisplayHelp) {
   1707   DisplayHelp($ClangNotFoundErrMsg);
   1708   exit $ForceDisplayHelp;
   1709 }
   1710 
   1711 DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg);
   1712 
   1713 $ClangCXX = $Clang;
   1714 if ($Clang !~ /\+\+(\.exe)?$/) {
   1715   # If $Clang holds the name of the clang++ executable then we leave
   1716   # $ClangCXX and $Clang equal, otherwise construct the name of the clang++
   1717   # executable from the clang executable name.
   1718 
   1719   # Determine operating system under which this copy of Perl was built.
   1720   my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/);
   1721   if($IsWinBuild) {
   1722     $ClangCXX =~ s/.exe$/++.exe/;
   1723   }
   1724   else {
   1725     $ClangCXX =~ s/\-\d+\.\d+$//;
   1726     $ClangCXX .= "++";
   1727   }
   1728 }
   1729 
   1730 # Make sure to use "" to handle paths with spaces.
   1731 $ClangVersion = HtmlEscape(`"$Clang" --version`);
   1732 
   1733 # Determine where results go.
   1734 $CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
   1735 
   1736 # Determine the output directory for the HTML reports.
   1737 my $BaseDir = $Options{OutputDir};
   1738 $Options{OutputDir} = GetHTMLRunDir($Options{OutputDir});
   1739 
   1740 # Determine the location of ccc-analyzer.
   1741 my $AbsRealBin = Cwd::realpath($RealBin);
   1742 my $Cmd = "$AbsRealBin/../libexec/ccc-analyzer";
   1743 my $CmdCXX = "$AbsRealBin/../libexec/c++-analyzer";
   1744 
   1745 # Portability: use less strict but portable check -e (file exists) instead of
   1746 # non-portable -x (file is executable). On some windows ports -x just checks
   1747 # file extension to determine if a file is executable (see Perl language
   1748 # reference, perlport)
   1749 if (!defined $Cmd || ! -e $Cmd) {
   1750   $Cmd = "$AbsRealBin/ccc-analyzer";
   1751   DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd);
   1752 }
   1753 if (!defined $CmdCXX || ! -e $CmdCXX) {
   1754   $CmdCXX = "$AbsRealBin/c++-analyzer";
   1755   DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX);
   1756 }
   1757 
   1758 Diag("Using '$Clang' for static analysis\n");
   1759 
   1760 SetHtmlEnv(\@ARGV, $Options{OutputDir});
   1761 
   1762 my @AnalysesToRun;
   1763 foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} } 
   1764          keys %{$Options{EnableCheckers}}) { 
   1765   # Push checkers in order they were enabled.
   1766   push @AnalysesToRun, "-analyzer-checker", $_;
   1767 }
   1768 foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} } 
   1769          keys %{$Options{DisableCheckers}}) { 
   1770   # Push checkers in order they were disabled.
   1771   push @AnalysesToRun, "-analyzer-disable-checker", $_;
   1772 }
   1773 if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; }
   1774 if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
   1775 if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; }
   1776 
   1777 # Delay setting up other environment variables in case we can do true
   1778 # interposition.
   1779 my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun;
   1780 my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}};
   1781 my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}};
   1782 my %EnvVars = (
   1783   'CC' => $Cmd,
   1784   'CXX' => $CmdCXX,
   1785   'CLANG' => $Clang,
   1786   'CLANG_CXX' => $ClangCXX,
   1787   'VERBOSE' => $Options{Verbose},
   1788   'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
   1789   'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
   1790   'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG,
   1791   'OUTPUT_DIR' => $Options{OutputDir},
   1792   'CCC_CC' => $Options{UseCC},
   1793   'CCC_CXX' => $Options{UseCXX},
   1794   'CCC_REPORT_FAILURES' => $Options{ReportFailures},
   1795   'CCC_ANALYZER_STORE_MODEL' => $Options{StoreModel},
   1796   'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel},
   1797   'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats},
   1798   'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat},
   1799   'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget}
   1800 );
   1801 
   1802 # Run the build.
   1803 my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Cmd, $CmdCXX,
   1804                                 \%EnvVars);
   1805 
   1806 if (defined $Options{OutputFormat}) {
   1807   if ($Options{OutputFormat} =~ /plist/) {
   1808     Diag "Analysis run complete.\n";
   1809     Diag "Analysis results (plist files) deposited in '$Options{OutputDir}'\n";
   1810   }
   1811   if ($Options{OutputFormat} =~ /html/) {
   1812     # Postprocess the HTML directory.
   1813     my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir,
   1814                               $Options{AnalyzerStats}, $Options{KeepEmpty});
   1815 
   1816     if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") {
   1817       Diag "Analysis run complete.\n";
   1818       Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n";
   1819       my $ScanView = Cwd::realpath("$RealBin/scan-view");
   1820       if (! -x $ScanView) { $ScanView = "scan-view"; }
   1821       if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); }
   1822       exec $ScanView, "$Options{OutputDir}";
   1823     }
   1824 
   1825     if ($Options{ExitStatusFoundBugs}) {
   1826       exit 1 if ($NumBugs > 0);
   1827       exit 0;
   1828     }
   1829   }
   1830 }
   1831 
   1832 exit $ExitStatus;
   1833