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