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