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