scan-build revision f190f6b88e0648ebb5f49bab3c788e03e13b9069
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
899sub RunXcodebuild {
900  my $Args = shift;
901  my $IgnoreErrors = shift;
902  my $CCAnalyzer = shift;
903  my $CXXAnalyzer = shift;
904  my $Options = shift;
905
906  if ($IgnoreErrors) {
907    AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
908  }
909
910  # Detect the version of Xcode.  If Xcode 4.6 or higher, use new
911  # in situ support for analyzer interposition without needed to override
912  # the compiler.
913  open(DETECT_XCODE, "xcodebuild -version |") or
914    die "error: cannot detect version of xcodebuild\n";
915
916  my $oldBehavior = 1;
917
918  while(<DETECT_XCODE>) {
919    if (/^Xcode (.+)$/) {
920      my $ver = $1;
921      if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) {
922        if ($1 >= 4.6) {
923          $oldBehavior = 0;
924          last;
925        }
926      }
927    }
928  }
929  close(DETECT_XCODE);
930  
931  if ($oldBehavior == 0) {
932    my $OutputDir = $Options->{"OUTPUT_DIR"};
933    my $CLANG = $Options->{"CLANG"};
934    push @$Args,
935        "RUN_CLANG_STATIC_ANALYZER=YES",
936        "CLANG_ANALYZER_OUTPUT=plist-html",
937        "CLANG_ANALYZER_EXEC=$CLANG",
938        "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir";
939
940    return (system(@$Args) >> 8);
941  }
942  
943  # Default to old behavior where we insert a bogus compiler.
944  SetEnv($Options);
945  
946  # Check if using iPhone SDK 3.0 (simulator).  If so the compiler being
947  # used should be gcc-4.2.
948  if (!defined $ENV{"CCC_CC"}) {
949    for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
950      if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
951        if (@$Args[$i+1] =~ /^iphonesimulator3/) {
952          $ENV{"CCC_CC"} = "gcc-4.2";
953          $ENV{"CCC_CXX"} = "g++-4.2";
954        }
955      }
956    }
957  }
958
959  # Disable PCH files until clang supports them.
960  AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
961  
962  # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
963  # linking C++ object files.  Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
964  # (via c++-analyzer) when linking such files.
965  $ENV{"LDPLUSPLUS"} = $CXXAnalyzer;
966 
967  return (system(@$Args) >> 8); 
968}
969
970sub RunBuildCommand {  
971  my $Args = shift;
972  my $IgnoreErrors = shift;
973  my $Cmd = $Args->[0];
974  my $CCAnalyzer = shift;
975  my $CXXAnalyzer = shift;
976  my $Options = shift;
977  
978  # Get only the part of the command after the last '/'.
979  if ($Cmd =~ /\/([^\/]+)$/) {
980    $Cmd = $1;
981  }
982  
983  if ($Cmd eq "xcodebuild") {
984    return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $Options);
985  }
986  
987  # Setup the environment.
988  SetEnv($Options);
989  
990  if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 
991      $Cmd =~ /(.*\/?cc[^\/]*$)/ or
992      $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
993      $Cmd =~ /(.*\/?clang$)/ or 
994      $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
995
996    if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
997      $ENV{"CCC_CC"} = $1;      
998    }
999        
1000    shift @$Args;
1001    unshift @$Args, $CCAnalyzer;
1002  }
1003  elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or 
1004        $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or
1005        $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or
1006        $Cmd =~ /(.*\/?clang\+\+$)/ or
1007        $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) {
1008    if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) {
1009      $ENV{"CCC_CXX"} = $1;      
1010    }        
1011    shift @$Args;
1012    unshift @$Args, $CXXAnalyzer;
1013  }
1014  elsif ($IgnoreErrors) {
1015    if ($Cmd eq "make" or $Cmd eq "gmake") {
1016      AddIfNotPresent($Args, "CC=$CCAnalyzer");
1017      AddIfNotPresent($Args, "CXX=$CXXAnalyzer");
1018      AddIfNotPresent($Args,"-k");
1019      AddIfNotPresent($Args,"-i");
1020    }
1021  } 
1022
1023  return (system(@$Args) >> 8);
1024}
1025
1026##----------------------------------------------------------------------------##
1027# DisplayHelp - Utility function to display all help options.
1028##----------------------------------------------------------------------------##
1029
1030sub DisplayHelp {
1031  
1032print <<ENDTEXT;
1033USAGE: $Prog [options] <build command> [build options]
1034
1035ENDTEXT
1036
1037  if (defined $BuildName) {
1038    print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
1039  }
1040
1041print <<ENDTEXT;
1042OPTIONS:
1043
1044 -analyze-headers
1045 
1046   Also analyze functions in #included files.  By default, such functions
1047   are skipped unless they are called by functions within the main source file.
1048 
1049 -o <output location>
1050  
1051   Specifies the output directory for analyzer reports. Subdirectories will be
1052   created as needed to represent separate "runs" of the analyzer. If this
1053   option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X)
1054   to store the reports.
1055
1056 -h             
1057 --help
1058
1059   Display this message.
1060
1061 -k
1062 --keep-going
1063				  
1064   Add a "keep on going" option to the specified build command. This option
1065   currently supports make and xcodebuild. This is a convenience option; one
1066   can specify this behavior directly using build options.
1067
1068 --html-title [title]
1069 --html-title=[title]
1070
1071   Specify the title used on generated HTML pages. If not specified, a default
1072   title will be used.
1073
1074 -plist
1075 
1076   By default the output of scan-build is a set of HTML files. This option
1077   outputs the results as a set of .plist files.
1078 
1079 -plist-html
1080 
1081   By default the output of scan-build is a set of HTML files. This option
1082   outputs the results as a set of HTML and .plist files.
1083 
1084 --status-bugs
1085 
1086   By default, the exit status of scan-build is the same as the executed build
1087   command. Specifying this option causes the exit status of scan-build to be 1
1088   if it found potential bugs and 0 otherwise.
1089
1090 --use-cc [compiler path]   
1091 --use-cc=[compiler path]
1092  
1093   scan-build analyzes a project by interposing a "fake compiler", which
1094   executes a real compiler for compilation and the static analyzer for analysis.
1095   Because of the current implementation of interposition, scan-build does not
1096   know what compiler your project normally uses.  Instead, it simply overrides
1097   the CC environment variable, and guesses your default compiler.
1098   
1099   In the future, this interposition mechanism to be improved, but if you need
1100   scan-build to use a specific compiler for *compilation* then you can use
1101   this option to specify a path to that compiler.
1102
1103 --use-c++ [compiler path]
1104 --use-c++=[compiler path]
1105 
1106   This is the same as "-use-cc" but for C++ code.
1107 
1108 -v
1109 
1110   Enable verbose output from scan-build. A second and third '-v' increases
1111   verbosity.
1112
1113 -V
1114 --view
1115
1116   View analysis results in a web browser when the build completes.
1117
1118ADVANCED OPTIONS:
1119
1120 -no-failure-reports
1121 
1122   Do not create a 'failures' subdirectory that includes analyzer crash reports
1123   and preprocessed source files.
1124
1125 -stats
1126 
1127   Generates visitation statistics for the project being analyzed.
1128
1129 -maxloop <loop count>
1130 
1131   Specifiy the number of times a block can be visited before giving up.
1132   Default is 4. Increase for more comprehensive coverage at a cost of speed.
1133  
1134 -internal-stats
1135 
1136   Generate internal analyzer statistics.
1137 
1138 --use-analyzer [Xcode|path to clang] 
1139 --use-analyzer=[Xcode|path to clang]
1140 
1141   scan-build uses the 'clang' executable relative to itself for static
1142   analysis. One can override this behavior with this option by using the
1143   'clang' packaged with Xcode (on OS X) or from the PATH.
1144
1145 --keep-empty
1146
1147   Don't remove the build results directory even if no issues were reported.
1148
1149CONTROLLING CHECKERS:
1150
1151 A default group of checkers are always run unless explicitly disabled.
1152 Checkers may be enabled/disabled using the following options:
1153
1154 -enable-checker [checker name]
1155 -disable-checker [checker name]
1156 
1157LOADING CHECKERS:
1158
1159 Loading external checkers using the clang plugin interface:
1160
1161 -load-plugin [plugin library]
1162ENDTEXT
1163
1164# Query clang for list of checkers that are enabled.
1165
1166# create a list to load the plugins via the 'Xclang' command line
1167# argument
1168my @PluginLoadCommandline_xclang;
1169foreach my $param ( @PluginsToLoad ) {
1170  push ( @PluginLoadCommandline_xclang, "-Xclang" );
1171  push ( @PluginLoadCommandline_xclang, $param );
1172}
1173my %EnabledCheckers;
1174foreach my $lang ("c", "objective-c", "objective-c++", "c++") {
1175  pipe(FROM_CHILD, TO_PARENT);
1176  my $pid = fork();
1177  if ($pid == 0) {
1178    close FROM_CHILD;
1179    open(STDOUT,">&", \*TO_PARENT);
1180    open(STDERR,">&", \*TO_PARENT);
1181    exec $Clang, ( @PluginLoadCommandline_xclang, '--analyze', '-x', $lang, '-', '-###'); 
1182  }
1183  close(TO_PARENT);
1184  while(<FROM_CHILD>) {
1185    foreach my $val (split /\s+/) {
1186      $val =~ s/\"//g;
1187      if ($val =~ /-analyzer-checker\=([^\s]+)/) {
1188        $EnabledCheckers{$1} = 1;
1189      }
1190    }
1191  }
1192  waitpid($pid,0);
1193  close(FROM_CHILD);
1194}
1195
1196# Query clang for complete list of checkers.
1197if (defined $Clang && -x $Clang) {
1198  pipe(FROM_CHILD, TO_PARENT);
1199  my $pid = fork();
1200  if ($pid == 0) {
1201    close FROM_CHILD;
1202    open(STDOUT,">&", \*TO_PARENT);
1203    open(STDERR,">&", \*TO_PARENT);
1204    exec $Clang, ('-cc1', @PluginsToLoad , '-analyzer-checker-help');
1205  }
1206  close(TO_PARENT);
1207  my $foundCheckers = 0;
1208  while(<FROM_CHILD>) {
1209    if (/CHECKERS:/) {
1210      $foundCheckers = 1;
1211      last;
1212    }
1213  }
1214  if (!$foundCheckers) {
1215    print "  *** Could not query Clang for the list of available checkers.";
1216  }
1217  else {
1218    print("\nAVAILABLE CHECKERS:\n\n");
1219    my $skip = 0;
1220    while(<FROM_CHILD>) {
1221      if (/experimental/) {
1222        $skip = 1;
1223        next;
1224      }
1225      if ($skip) {
1226        next if (!/^\s\s[^\s]/);
1227        $skip = 0;
1228      }
1229      s/^\s\s//;
1230      if (/^([^\s]+)/) {
1231        # Is the checker enabled?
1232        my $checker = $1;
1233        my $enabled = 0;
1234        my $aggregate = "";
1235        foreach my $domain (split /\./, $checker) {
1236          $aggregate .= $domain;
1237          if ($EnabledCheckers{$aggregate}) {
1238            $enabled =1;
1239            last;
1240          }
1241          # append a dot, if an additional domain is added in the next iteration
1242          $aggregate .= ".";
1243        }
1244      
1245        if ($enabled) {
1246          print " + ";
1247        }
1248        else {
1249          print "   ";
1250        }
1251      }
1252      else {
1253        print "   ";
1254      }
1255      print $_;
1256    }
1257    print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n"
1258  }
1259  waitpid($pid,0);
1260  close(FROM_CHILD);
1261}
1262
1263print <<ENDTEXT
1264
1265BUILD OPTIONS
1266
1267 You can specify any build option acceptable to the build command.
1268
1269EXAMPLE
1270
1271 scan-build -o /tmp/myhtmldir make -j4
1272     
1273The above example causes analysis reports to be deposited into a subdirectory
1274of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different
1275subdirectory is created each time scan-build analyzes a project. The analyzer
1276should support most parallel builds, but not distributed builds.
1277
1278ENDTEXT
1279}
1280
1281##----------------------------------------------------------------------------##
1282# HtmlEscape - HTML entity encode characters that are special in HTML
1283##----------------------------------------------------------------------------##
1284
1285sub HtmlEscape {
1286  # copy argument to new variable so we don't clobber the original
1287  my $arg = shift || '';
1288  my $tmp = $arg;
1289  $tmp =~ s/&/&amp;/g;
1290  $tmp =~ s/</&lt;/g;
1291  $tmp =~ s/>/&gt;/g;
1292  return $tmp;
1293}
1294
1295##----------------------------------------------------------------------------##
1296# ShellEscape - backslash escape characters that are special to the shell
1297##----------------------------------------------------------------------------##
1298
1299sub ShellEscape {
1300  # copy argument to new variable so we don't clobber the original
1301  my $arg = shift || '';
1302  if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
1303  return $arg;
1304}
1305
1306##----------------------------------------------------------------------------##
1307# Process command-line arguments.
1308##----------------------------------------------------------------------------##
1309
1310my $AnalyzeHeaders = 0;
1311my $HtmlDir;           # Parent directory to store HTML files.
1312my $IgnoreErrors = 0;  # Ignore build errors.
1313my $ViewResults  = 0;  # View results when the build terminates.
1314my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found
1315my $KeepEmpty    = 0;  # Don't remove output directory even with 0 results.
1316my @AnalysesToRun;
1317my $StoreModel;
1318my $ConstraintsModel;
1319my $InternalStats;
1320my $OutputFormat = "html";
1321my $AnalyzerStats = 0;
1322my $MaxLoop = 0;
1323my $RequestDisplayHelp = 0;
1324my $ForceDisplayHelp = 0;
1325my $AnalyzerDiscoveryMethod;
1326
1327if (!@ARGV) {
1328  $ForceDisplayHelp = 1
1329}
1330
1331while (@ARGV) {
1332  
1333  # Scan for options we recognize.
1334  
1335  my $arg = $ARGV[0];
1336
1337  if ($arg eq "-h" or $arg eq "--help") {
1338    $RequestDisplayHelp = 1;
1339    shift @ARGV;
1340    next;
1341  }
1342  
1343  if ($arg eq '-analyze-headers') {
1344    shift @ARGV;    
1345    $AnalyzeHeaders = 1;
1346    next;
1347  }
1348  
1349  if ($arg eq "-o") {
1350    shift @ARGV;
1351        
1352    if (!@ARGV) {
1353      DieDiag("'-o' option requires a target directory name.\n");
1354    }
1355    
1356    # Construct an absolute path.  Uses the current working directory
1357    # as a base if the original path was not absolute.
1358    $HtmlDir = abs_path(shift @ARGV);
1359    
1360    next;
1361  }
1362
1363  if ($arg =~ /^--html-title(=(.+))?$/) {
1364    shift @ARGV;
1365
1366    if (!defined $2 || $2 eq '') {
1367      if (!@ARGV) {
1368        DieDiag("'--html-title' option requires a string.\n");
1369      }
1370
1371      $HtmlTitle = shift @ARGV;
1372    } else {
1373      $HtmlTitle = $2;
1374    }
1375
1376    next;
1377  }
1378  
1379  if ($arg eq "-k" or $arg eq "--keep-going") {
1380    shift @ARGV;
1381    $IgnoreErrors = 1;
1382    next;
1383  }
1384
1385  if ($arg =~ /^--use-cc(=(.+))?$/) {
1386    shift @ARGV;
1387    my $cc;
1388    
1389    if (!defined $2 || $2 eq "") {
1390      if (!@ARGV) {
1391        DieDiag("'--use-cc' option requires a compiler executable name.\n");
1392      }
1393      $cc = shift @ARGV;
1394    }
1395    else {
1396      $cc = $2;
1397    }
1398    
1399    $ENV{"CCC_CC"} = $cc;
1400    next;
1401  }
1402  
1403  if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1404    shift @ARGV;
1405    my $cxx;    
1406    
1407    if (!defined $2 || $2 eq "") {
1408      if (!@ARGV) {
1409        DieDiag("'--use-c++' option requires a compiler executable name.\n");
1410      }
1411      $cxx = shift @ARGV;
1412    }
1413    else {
1414      $cxx = $2;
1415    }
1416    
1417    $ENV{"CCC_CXX"} = $cxx;
1418    next;
1419  }
1420  
1421  if ($arg eq "-v") {
1422    shift @ARGV;
1423    $Verbose++;
1424    next;
1425  }
1426  
1427  if ($arg eq "-V" or $arg eq "--view") {
1428    shift @ARGV;
1429    $ViewResults = 1;    
1430    next;
1431  }
1432  
1433  if ($arg eq "--status-bugs") {
1434    shift @ARGV;
1435    $ExitStatusFoundBugs = 1;
1436    next;
1437  }
1438
1439  if ($arg eq "-store") {
1440    shift @ARGV;
1441    $StoreModel = shift @ARGV;
1442    next;
1443  }
1444  
1445  if ($arg eq "-constraints") {
1446    shift @ARGV;
1447    $ConstraintsModel = shift @ARGV;
1448    next;
1449  }
1450
1451  if ($arg eq "-internal-stats") {
1452    shift @ARGV;
1453    $InternalStats = 1;
1454    next;
1455  }
1456  
1457  if ($arg eq "-plist") {
1458    shift @ARGV;
1459    $OutputFormat = "plist";
1460    next;
1461  }
1462  if ($arg eq "-plist-html") {
1463    shift @ARGV;
1464    $OutputFormat = "plist-html";
1465    next;
1466  }
1467  
1468  if ($arg eq "-no-failure-reports") {
1469    $ENV{"CCC_REPORT_FAILURES"} = 0;
1470    next;
1471  }
1472  if ($arg eq "-stats") {
1473    shift @ARGV;
1474    $AnalyzerStats = 1;
1475    next;
1476  }
1477  if ($arg eq "-maxloop") {
1478    shift @ARGV;
1479    $MaxLoop = shift @ARGV;
1480    next;
1481  }
1482  if ($arg eq "-enable-checker") {
1483    shift @ARGV;
1484    push @AnalysesToRun, "-analyzer-checker", shift @ARGV;
1485    next;
1486  }
1487  if ($arg eq "-disable-checker") {
1488    shift @ARGV;
1489    push @AnalysesToRun, "-analyzer-disable-checker", shift @ARGV;
1490    next;
1491  }
1492  if ($arg eq "-load-plugin") {
1493    shift @ARGV;
1494    push @PluginsToLoad, "-load", shift @ARGV;
1495    next;
1496  }
1497  if ($arg eq "--use-analyzer") {
1498 	shift @ARGV;
1499  	$AnalyzerDiscoveryMethod = shift @ARGV;
1500	next;
1501  }
1502  if ($arg =~ /^--use-analyzer=(.+)$/) {
1503    shift @ARGV;
1504	$AnalyzerDiscoveryMethod = $1;
1505	next;
1506  }
1507  if ($arg eq "--keep-empty") {
1508    shift @ARGV;
1509    $KeepEmpty = 1;
1510    next;
1511  }
1512  
1513  DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1514  
1515  last;
1516}
1517
1518if (!@ARGV and !$RequestDisplayHelp) {
1519  ErrorDiag("No build command specified.\n\n");
1520  $ForceDisplayHelp = 1;
1521}
1522
1523# Find 'clang'
1524if (!defined $AnalyzerDiscoveryMethod) {
1525  $Clang = Cwd::realpath("$RealBin/bin/clang");
1526  if (!defined $Clang || ! -x $Clang) {
1527    $Clang = Cwd::realpath("$RealBin/clang");
1528  }
1529  if (!defined $Clang || ! -x $Clang) {
1530    if (!$RequestDisplayHelp && !$ForceDisplayHelp) {
1531      DieDiag("error: Cannot find an executable 'clang' relative to scan-build." .
1532   	          "  Consider using --use-analyzer to pick a version of 'clang' to use for static analysis.\n");
1533    }
1534  }
1535} 
1536else {  
1537  if ($AnalyzerDiscoveryMethod =~ /^[Xx]code$/) {
1538	my $xcrun = `which xcrun`;
1539    chomp $xcrun;
1540	if ($xcrun eq "") {
1541  	  DieDiag("Cannot find 'xcrun' to find 'clang' for analysis.\n");
1542	}
1543    $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
1544    chomp $Clang;  
1545    if ($Clang eq "") {
1546      DieDiag("No 'clang' executable found by 'xcrun'\n"); 
1547    }
1548  }
1549  else {
1550    $Clang = Cwd::realpath($AnalyzerDiscoveryMethod);
1551	if (!defined $Clang or not -x $Clang) {
1552   	  DieDiag("Cannot find an executable clang at '$AnalyzerDiscoveryMethod'\n");
1553	}
1554  }
1555}
1556
1557if ($ForceDisplayHelp || $RequestDisplayHelp) {
1558  DisplayHelp();
1559  exit $ForceDisplayHelp;
1560}
1561
1562$ClangCXX = $Clang;
1563$ClangCXX =~ s/\-\d+\.\d+$//;
1564$ClangCXX .= "++";
1565# Make sure to use "" to handle paths with spaces.
1566$ClangVersion = HtmlEscape(`"$Clang" --version`);
1567
1568# Determine where results go.
1569$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1570$HtmlTitle = "${CurrentDirSuffix} - scan-build results"
1571  unless (defined($HtmlTitle));
1572
1573# Determine the output directory for the HTML reports.
1574my $BaseDir = $HtmlDir;
1575$HtmlDir = GetHTMLRunDir($HtmlDir);
1576
1577# Determine the location of ccc-analyzer.
1578my $AbsRealBin = Cwd::realpath($RealBin);
1579my $Cmd = "$AbsRealBin/libexec/ccc-analyzer";
1580my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer";
1581
1582if (!defined $Cmd || ! -x $Cmd) {
1583  $Cmd = "$AbsRealBin/ccc-analyzer";
1584  DieDiag("Executable 'ccc-analyzer' does not exist at '$Cmd'\n") if(! -x $Cmd);
1585}
1586if (!defined $CmdCXX || ! -x $CmdCXX) {
1587  $CmdCXX = "$AbsRealBin/c++-analyzer";
1588  DieDiag("Executable 'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -x $CmdCXX);
1589}
1590
1591Diag("Using '$Clang' for static analysis\n");
1592
1593SetHtmlEnv(\@ARGV, $HtmlDir);
1594if ($AnalyzeHeaders) { push @AnalysesToRun,"-analyzer-opt-analyze-headers"; }
1595if ($AnalyzerStats) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
1596if ($MaxLoop > 0) { push @AnalysesToRun, "-analyzer-max-loop $MaxLoop"; }
1597
1598# Delay setting up other environment variables in case we can do true
1599# interposition.
1600my $CCC_ANALYZER_ANALYSIS = join ' ',@AnalysesToRun;
1601my $CCC_ANALYZER_PLUGINS = join ' ',@PluginsToLoad;
1602my %Options = (
1603  'CC' => $Cmd,
1604  'CXX' => $CmdCXX,
1605  'CLANG' => $Clang,
1606  'CLANG_CXX' => $ClangCXX,
1607  'VERBOSE' => $Verbose,
1608  'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
1609  'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
1610  'OUTPUT_DIR' => $HtmlDir
1611);
1612
1613if (defined $StoreModel) {
1614  $Options{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel;
1615}
1616if (defined $ConstraintsModel) {
1617  $Options{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel;
1618}
1619if (defined $InternalStats) {
1620  $Options{'CCC_ANALYZER_INTERNAL_STATS'} = 1;
1621}
1622if (defined $OutputFormat) {
1623  $Options{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat;
1624}
1625
1626# Run the build.
1627my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd, $CmdCXX,
1628                                \%Options);
1629
1630if (defined $OutputFormat) {
1631  if ($OutputFormat =~ /plist/) {
1632    Diag "Analysis run complete.\n";
1633    Diag "Analysis results (plist files) deposited in '$HtmlDir'\n";
1634  }
1635  if ($OutputFormat =~ /html/) {
1636    # Postprocess the HTML directory.
1637    my $NumBugs = Postprocess($HtmlDir, $BaseDir, $AnalyzerStats, $KeepEmpty);
1638
1639    if ($ViewResults and -r "$HtmlDir/index.html") {
1640      Diag "Analysis run complete.\n";
1641      Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n";
1642      my $ScanView = Cwd::realpath("$RealBin/scan-view");
1643      if (! -x $ScanView) { $ScanView = "scan-view"; }
1644      exec $ScanView, "$HtmlDir";
1645    }
1646
1647    if ($ExitStatusFoundBugs) {
1648      exit 1 if ($NumBugs > 0);
1649      exit 0;
1650    }
1651  }
1652}
1653
1654exit $ExitStatus;
1655
1656