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