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