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