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