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