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