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