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