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