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