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