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