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