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<h2>Reports</h2>
726
727<table class="sortable" style="table-layout:automatic">
728<thead><tr>
729  <td>Bug Group</td>
730  <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind">&nbsp;&#x25BE;</span></td>
731  <td>File</td>
732  <td>Function/Method</td>
733  <td class="Q">Line</td>
734  <td class="Q">Path Length</td>
735ENDTEXT
736
737if ($Options{ShowDescription}) {
738print OUT <<ENDTEXT;
739    <td class="Q">Description</td>
740ENDTEXT
741}
742
743print OUT <<ENDTEXT;
744  <td class="sorttable_nosort"></td>
745  <!-- REPORTBUGCOL -->
746</tr></thead>
747<tbody>
748ENDTEXT
749
750    my $prefix = GetPrefix();
751    my $regex;
752    my $InFileRegex;
753    my $InFilePrefix = "File:</td><td>";
754
755    if (defined $prefix) {
756      $regex = qr/^\Q$prefix\E/is;
757      $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
758    }
759
760    for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
761      my $x = "$row->[1]:$row->[2]";
762      $x = lc $x;
763      $x =~ s/[ ,'":\/()]+/_/g;
764
765      my $ReportFile = $row->[0];
766
767      print OUT "<tr class=\"bt_$x\">";
768      print OUT "<td class=\"DESC\">";
769      print OUT $row->[1]; # $BugCategory
770      print OUT "</td>";
771      print OUT "<td class=\"DESC\">";
772      print OUT $row->[2]; # $BugType
773      print OUT "</td>";
774
775      # Update the file prefix.
776      my $fname = $row->[3];
777
778      if (defined $regex) {
779        $fname =~ s/$regex//;
780        UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
781      }
782
783      print OUT "<td>";
784      my @fname = split /\//,$fname;
785      if ($#fname > 0) {
786        while ($#fname >= 0) {
787          my $x = shift @fname;
788          print OUT $x;
789          if ($#fname >= 0) {
790            print OUT "/";
791          }
792        }
793      }
794      else {
795        print OUT $fname;
796      }
797      print OUT "</td>";
798
799      print OUT "<td class=\"DESC\">";
800      print OUT $row->[4]; # Function
801      print OUT "</td>";
802
803      # Print out the quantities.
804      for my $j ( 5 .. 6 ) { # Line & Path length
805        print OUT "<td class=\"Q\">$row->[$j]</td>";
806      }
807
808      # Print the rest of the columns.
809      for (my $j = 7; $j <= $#{$row}; ++$j) {
810        print OUT "<td>$row->[$j]</td>"
811      }
812
813      # Emit the "View" link.
814      print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
815
816      # Emit REPORTBUG markers.
817      print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
818
819      # End the row.
820      print OUT "</tr>\n";
821    }
822
823    print OUT "</tbody>\n</table>\n\n";
824  }
825
826  if (scalar (@failures) || scalar(@attributes_ignored)) {
827    print OUT "<h2>Analyzer Failures</h2>\n";
828
829    if (scalar @attributes_ignored) {
830      print OUT "The analyzer's parser ignored the following attributes:<p>\n";
831      print OUT "<table>\n";
832      print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
833      foreach my $file (sort @attributes_ignored) {
834        die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
835        my $attribute = $1;
836        # Open the attribute file to get the first file that failed.
837        next if (!open (ATTR, "$Dir/failures/$file"));
838        my $ppfile = <ATTR>;
839        chomp $ppfile;
840        close ATTR;
841        next if (! -e "$Dir/failures/$ppfile");
842        # Open the info file and get the name of the source file.
843        open (INFO, "$Dir/failures/$ppfile.info.txt") or
844          die "Cannot open $Dir/failures/$ppfile.info.txt\n";
845        my $srcfile = <INFO>;
846        chomp $srcfile;
847        close (INFO);
848        # Print the information in the table.
849        my $prefix = GetPrefix();
850        if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
851        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";
852        my $ppfile_clang = $ppfile;
853        $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
854        print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
855      }
856      print OUT "</table>\n";
857    }
858
859    if (scalar @failures) {
860      print OUT "<p>The analyzer had problems processing the following files:</p>\n";
861      print OUT "<table>\n";
862      print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
863      foreach my $file (sort @failures) {
864        $file =~ /(.+).info.txt$/;
865        # Get the preprocessed file.
866        my $ppfile = $1;
867        # Open the info file and get the name of the source file.
868        open (INFO, "$Dir/failures/$file") or
869          die "Cannot open $Dir/failures/$file\n";
870        my $srcfile = <INFO>;
871        chomp $srcfile;
872        my $problem = <INFO>;
873        chomp $problem;
874        close (INFO);
875        # Print the information in the table.
876        my $prefix = GetPrefix();
877        if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
878        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";
879        my $ppfile_clang = $ppfile;
880        $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
881        print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
882      }
883      print OUT "</table>\n";
884    }
885    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";
886  }
887
888  print OUT "</body></html>\n";
889  close(OUT);
890  CopyFiles($Dir);
891
892  # Make sure $Dir and $BaseDir are world readable/executable.
893  chmod(0755, $Dir);
894  if (defined $BaseDir) { chmod(0755, $BaseDir); }
895
896  # Print statistics
897  print CalcStats(\@Stats) if $AnalyzerStats;
898
899  my $Num = scalar(@Index);
900  if ($Num == 1) {
901    Diag("$Num bug found.\n");
902  } else {
903    Diag("$Num bugs found.\n");
904  }
905  if ($Num > 0 && -r "$Dir/index.html") {
906    Diag("Run 'scan-view $Dir' to examine bug reports.\n");
907  }
908
909  DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
910
911  return $Num;
912}
913
914sub Finalize {
915  my $BaseDir = shift;
916  my $ExitStatus = shift;
917
918  Diag "Analysis run complete.\n";
919  if (defined $Options{OutputFormat}) {
920    if ($Options{OutputFormat} =~ /plist/ ||
921        $Options{OutputFormat} =~ /sarif/) {
922      Diag "Analysis results (" .
923        ($Options{OutputFormat} =~ /plist/ ? "plist" : "sarif") .
924        " files) deposited in '$Options{OutputDir}'\n";
925    }
926    if ($Options{OutputFormat} =~ /html/) {
927      # Postprocess the HTML directory.
928      my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir,
929                                $Options{AnalyzerStats}, $Options{KeepEmpty});
930
931      if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") {
932        Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n";
933        my $ScanView = Cwd::realpath("$RealBin/scan-view");
934        if (! -x $ScanView) { $ScanView = "scan-view"; }
935        if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); }
936        if (! -x $ScanView) { $ScanView = `which scan-view`; chomp $ScanView; }
937        exec $ScanView, "$Options{OutputDir}";
938      }
939
940      if ($Options{ExitStatusFoundBugs}) {
941        exit 1 if ($NumBugs > 0);
942        exit $ExitStatus;
943      }
944    }
945  }
946
947  exit $ExitStatus;
948}
949
950##----------------------------------------------------------------------------##
951# RunBuildCommand - Run the build command.
952##----------------------------------------------------------------------------##
953
954sub AddIfNotPresent {
955  my $Args = shift;
956  my $Arg = shift;
957  my $found = 0;
958
959  foreach my $k (@$Args) {
960    if ($k eq $Arg) {
961      $found = 1;
962      last;
963    }
964  }
965
966  if ($found == 0) {
967    push @$Args, $Arg;
968  }
969}
970
971sub SetEnv {
972  my $EnvVars = shift @_;
973  foreach my $var ('CC', 'CXX', 'CLANG', 'CLANG_CXX',
974                   'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS',
975                   'CCC_ANALYZER_CONFIG') {
976    die "$var is undefined\n" if (!defined $var);
977    $ENV{$var} = $EnvVars->{$var};
978  }
979  foreach my $var ('CCC_ANALYZER_CONSTRAINTS_MODEL',
980                   'CCC_ANALYZER_INTERNAL_STATS',
981                   'CCC_ANALYZER_OUTPUT_FORMAT',
982                   'CCC_CC',
983                   'CCC_CXX',
984                   'CCC_REPORT_FAILURES',
985                   'CLANG_ANALYZER_TARGET',
986                   'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE') {
987    my $x = $EnvVars->{$var};
988    if (defined $x) { $ENV{$var} = $x }
989  }
990  my $Verbose = $EnvVars->{'VERBOSE'};
991  if ($Verbose >= 2) {
992    $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
993  }
994  if ($Verbose >= 3) {
995    $ENV{'CCC_ANALYZER_LOG'} = 1;
996  }
997}
998
999sub RunXcodebuild {
1000  my $Args = shift;
1001  my $IgnoreErrors = shift;
1002  my $CCAnalyzer = shift;
1003  my $CXXAnalyzer = shift;
1004  my $EnvVars = shift;
1005
1006  if ($IgnoreErrors) {
1007    AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
1008  }
1009
1010  # Detect the version of Xcode.  If Xcode 4.6 or higher, use new
1011  # in situ support for analyzer interposition without needed to override
1012  # the compiler.
1013  open(DETECT_XCODE, "-|", $Args->[0], "-version") or
1014    die "error: cannot detect version of xcodebuild\n";
1015
1016  my $oldBehavior = 1;
1017
1018  while(<DETECT_XCODE>) {
1019    if (/^Xcode (.+)$/) {
1020      my $ver = $1;
1021      if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) {
1022        if ($1 >= 4.6) {
1023          $oldBehavior = 0;
1024          last;
1025        }
1026      }
1027    }
1028  }
1029  close(DETECT_XCODE);
1030
1031  # If --override-compiler is explicitly requested, resort to the old
1032  # behavior regardless of Xcode version.
1033  if ($Options{OverrideCompiler}) {
1034    $oldBehavior = 1;
1035  }
1036
1037  if ($oldBehavior == 0) {
1038    my $OutputDir = $EnvVars->{"OUTPUT_DIR"};
1039    my $CLANG = $EnvVars->{"CLANG"};
1040    my $OtherFlags = $EnvVars->{"CCC_ANALYZER_ANALYSIS"} . " "
1041                   . $EnvVars->{"CCC_ANALYZER_CONFIG"};
1042    push @$Args,
1043        "RUN_CLANG_STATIC_ANALYZER=YES",
1044        "CLANG_ANALYZER_OUTPUT=plist-html",
1045        "CLANG_ANALYZER_EXEC=$CLANG",
1046        "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir",
1047        "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags";
1048
1049    return (system(@$Args) >> 8);
1050  }
1051
1052  # Default to old behavior where we insert a bogus compiler.
1053  SetEnv($EnvVars);
1054
1055  # Check if using iPhone SDK 3.0 (simulator).  If so the compiler being
1056  # used should be gcc-4.2.
1057  if (!defined $ENV{"CCC_CC"}) {
1058    for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
1059      if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
1060        if (@$Args[$i+1] =~ /^iphonesimulator3/) {
1061          $ENV{"CCC_CC"} = "gcc-4.2";
1062          $ENV{"CCC_CXX"} = "g++-4.2";
1063        }
1064      }
1065    }
1066  }
1067
1068  # Disable PCH files until clang supports them.
1069  AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
1070
1071  # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
1072  # linking C++ object files.  Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
1073  # (via c++-analyzer) when linking such files.
1074  $ENV{"LDPLUSPLUS"} = $CXXAnalyzer;
1075
1076  return (system(@$Args) >> 8);
1077}
1078
1079sub RunBuildCommand {
1080  my $Args = shift;
1081  my $IgnoreErrors = shift;
1082  my $KeepCC = shift;
1083  my $Cmd = $Args->[0];
1084  my $CCAnalyzer = shift;
1085  my $CXXAnalyzer = shift;
1086  my $EnvVars = shift;
1087
1088  if ($Cmd =~ /\bxcodebuild$/) {
1089    return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $EnvVars);
1090  }
1091
1092  # Setup the environment.
1093  SetEnv($EnvVars);
1094
1095  if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or
1096      $Cmd =~ /(.*\/?cc[^\/]*$)/ or
1097      $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
1098      $Cmd =~ /(.*\/?clang[^\/]*$)/ or
1099      $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
1100
1101    if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
1102      $ENV{"CCC_CC"} = $1;
1103    }
1104
1105    shift @$Args;
1106    unshift @$Args, $CCAnalyzer;
1107  }
1108  elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or
1109        $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or
1110        $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or
1111        $Cmd =~ /(.*\/?clang\+\+$)/ or
1112        $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) {
1113    if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) {
1114      $ENV{"CCC_CXX"} = $1;
1115    }
1116    shift @$Args;
1117    unshift @$Args, $CXXAnalyzer;
1118  }
1119  elsif ($Cmd eq "make" or $Cmd eq "gmake" or $Cmd eq "mingw32-make") {
1120    if (!$KeepCC) {
1121      AddIfNotPresent($Args, "CC=$CCAnalyzer");
1122      AddIfNotPresent($Args, "CXX=$CXXAnalyzer");
1123    }
1124    if ($IgnoreErrors) {
1125      AddIfNotPresent($Args,"-k");
1126      AddIfNotPresent($Args,"-i");
1127    }
1128  }
1129
1130  return (system(@$Args) >> 8);
1131}
1132
1133##----------------------------------------------------------------------------##
1134# DisplayHelp - Utility function to display all help options.
1135##----------------------------------------------------------------------------##
1136
1137sub DisplayHelp {
1138
1139  my $ArgClangNotFoundErrMsg = shift;
1140print <<ENDTEXT;
1141USAGE: $Prog [options] <build command> [build options]
1142
1143ENDTEXT
1144
1145  if (defined $BuildName) {
1146    print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
1147  }
1148
1149print <<ENDTEXT;
1150OPTIONS:
1151
1152 -analyze-headers
1153
1154   Also analyze functions in #included files.  By default, such functions
1155   are skipped unless they are called by functions within the main source file.
1156
1157 --force-analyze-debug-code
1158
1159   Tells analyzer to enable assertions in code even if they were disabled
1160   during compilation to enable more precise results.
1161
1162 -o <output location>
1163
1164   Specifies the output directory for analyzer reports. Subdirectories will be
1165   created as needed to represent separate "runs" of the analyzer. If this
1166   option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X)
1167   to store the reports.
1168
1169 -h
1170 --help
1171
1172   Display this message.
1173
1174 -k
1175 --keep-going
1176
1177   Add a "keep on going" option to the specified build command. This option
1178   currently supports make and xcodebuild. This is a convenience option; one
1179   can specify this behavior directly using build options.
1180
1181 --keep-cc
1182
1183   Do not override CC and CXX make variables. Useful when running make in
1184   autoconf-based (and similar) projects where configure can add extra flags
1185   to those variables.
1186
1187 --html-title [title]
1188 --html-title=[title]
1189
1190   Specify the title used on generated HTML pages. If not specified, a default
1191   title will be used.
1192
1193 --show-description
1194
1195   Display the description of defects in the list
1196
1197 -sarif
1198
1199  By default the output of scan-build is a set of HTML files. This option
1200  outputs the results in SARIF format.
1201
1202 -plist
1203
1204   By default the output of scan-build is a set of HTML files. This option
1205   outputs the results as a set of .plist files.
1206
1207 -plist-html
1208
1209   By default the output of scan-build is a set of HTML files. This option
1210   outputs the results as a set of HTML and .plist files.
1211
1212 --status-bugs
1213
1214   By default, the exit status of scan-build is the same as the executed build
1215   command. Specifying this option causes the exit status of scan-build to be 1
1216   if it found potential bugs and the exit status of the build itself otherwise.
1217
1218 --exclude <path>
1219
1220   Do not run static analyzer against files found in this
1221   directory (You can specify this option multiple times).
1222   Could be useful when project contains 3rd party libraries.
1223
1224 --use-cc [compiler path]
1225 --use-cc=[compiler path]
1226
1227   scan-build analyzes a project by interposing a "fake compiler", which
1228   executes a real compiler for compilation and the static analyzer for analysis.
1229   Because of the current implementation of interposition, scan-build does not
1230   know what compiler your project normally uses.  Instead, it simply overrides
1231   the CC environment variable, and guesses your default compiler.
1232
1233   In the future, this interposition mechanism to be improved, but if you need
1234   scan-build to use a specific compiler for *compilation* then you can use
1235   this option to specify a path to that compiler.
1236
1237   If the given compiler is a cross compiler, you may also need to provide
1238   --analyzer-target option to properly analyze the source code because static
1239   analyzer runs as if the code is compiled for the host machine by default.
1240
1241 --use-c++ [compiler path]
1242 --use-c++=[compiler path]
1243
1244   This is the same as "--use-cc" but for C++ code.
1245
1246 --analyzer-target [target triple name for analysis]
1247 --analyzer-target=[target triple name for analysis]
1248
1249   This provides target triple information to clang static analyzer.
1250   It only changes the target for analysis but doesn't change the target of a
1251   real compiler given by --use-cc and --use-c++ options.
1252
1253 -v
1254
1255   Enable verbose output from scan-build. A second and third '-v' increases
1256   verbosity.
1257
1258 -V
1259 --view
1260
1261   View analysis results in a web browser when the build completes.
1262
1263 --generate-index-only <output location>
1264
1265   Do not perform the analysis, but only regenerate the index.html file
1266   from existing report.html files. Useful for making a custom Static Analyzer
1267   integration into a build system that isn't otherwise supported by scan-build.
1268
1269ADVANCED OPTIONS:
1270
1271 -no-failure-reports
1272
1273   Do not create a 'failures' subdirectory that includes analyzer crash reports
1274   and preprocessed source files.
1275
1276 -stats
1277
1278   Generates visitation statistics for the project being analyzed.
1279
1280 -maxloop <loop count>
1281
1282   Specify the number of times a block can be visited before giving up.
1283   Default is 4. Increase for more comprehensive coverage at a cost of speed.
1284
1285 -internal-stats
1286
1287   Generate internal analyzer statistics.
1288
1289 --use-analyzer [Xcode|path to clang]
1290 --use-analyzer=[Xcode|path to clang]
1291
1292   scan-build uses the 'clang' executable relative to itself for static
1293   analysis. One can override this behavior with this option by using the
1294   'clang' packaged with Xcode (on OS X) or from the PATH.
1295
1296 --keep-empty
1297
1298   Don't remove the build results directory even if no issues were reported.
1299
1300 --override-compiler
1301   Always resort to the ccc-analyzer even when better interposition methods
1302   are available.
1303
1304 -analyzer-config <options>
1305
1306   Provide options to pass through to the analyzer's -analyzer-config flag.
1307   Several options are separated with comma: 'key1=val1,key2=val2'
1308
1309   Available options:
1310     * stable-report-filename=true or false (default)
1311       Switch the page naming to:
1312       report-<filename>-<function/method name>-<id>.html
1313       instead of report-XXXXXX.html
1314
1315CONTROLLING CHECKERS:
1316
1317 A default group of checkers are always run unless explicitly disabled.
1318 Checkers may be enabled/disabled using the following options:
1319
1320 -enable-checker [checker name]
1321 -disable-checker [checker name]
1322
1323LOADING CHECKERS:
1324
1325 Loading external checkers using the clang plugin interface:
1326
1327 -load-plugin [plugin library]
1328ENDTEXT
1329
1330  if (defined $Clang && -x $Clang) {
1331    # Query clang for list of checkers that are enabled.
1332
1333    # create a list to load the plugins via the 'Xclang' command line
1334    # argument
1335    my @PluginLoadCommandline_xclang;
1336    foreach my $param ( @{$Options{PluginsToLoad}} ) {
1337      push ( @PluginLoadCommandline_xclang, "-Xclang" );
1338      push ( @PluginLoadCommandline_xclang, "-load" );
1339      push ( @PluginLoadCommandline_xclang, "-Xclang" );
1340      push ( @PluginLoadCommandline_xclang, $param );
1341    }
1342
1343    my %EnabledCheckers;
1344    foreach my $lang ("c", "objective-c", "objective-c++", "c++") {
1345      my $ExecLine = join(' ', qq/"$Clang"/, @PluginLoadCommandline_xclang, "--analyze", "-x", $lang, "-", "-###", "2>&1", "|");
1346      open(PS, $ExecLine);
1347      while (<PS>) {
1348        foreach my $val (split /\s+/) {
1349          $val =~ s/\"//g;
1350          if ($val =~ /-analyzer-checker\=([^\s]+)/) {
1351            $EnabledCheckers{$1} = 1;
1352          }
1353        }
1354      }
1355    }
1356
1357    # Query clang for complete list of checkers.
1358    my @PluginLoadCommandline;
1359    foreach my $param ( @{$Options{PluginsToLoad}} ) {
1360      push ( @PluginLoadCommandline, "-load" );
1361      push ( @PluginLoadCommandline, $param );
1362    }
1363
1364    my $ExecLine = join(' ', qq/"$Clang"/, "-cc1", @PluginLoadCommandline, "-analyzer-checker-help", "2>&1", "|");
1365    open(PS, $ExecLine);
1366    my $foundCheckers = 0;
1367    while (<PS>) {
1368      if (/CHECKERS:/) {
1369        $foundCheckers = 1;
1370        last;
1371      }
1372    }
1373    if (!$foundCheckers) {
1374      print "  *** Could not query Clang for the list of available checkers.";
1375    }
1376    else {
1377      print("\nAVAILABLE CHECKERS:\n\n");
1378      my $skip = 0;
1379       while(<PS>) {
1380        if (/experimental/) {
1381          $skip = 1;
1382          next;
1383        }
1384        if ($skip) {
1385          next if (!/^\s\s[^\s]/);
1386          $skip = 0;
1387        }
1388        s/^\s\s//;
1389        if (/^([^\s]+)/) {
1390          # Is the checker enabled?
1391          my $checker = $1;
1392          my $enabled = 0;
1393          my $aggregate = "";
1394          foreach my $domain (split /\./, $checker) {
1395            $aggregate .= $domain;
1396            if ($EnabledCheckers{$aggregate}) {
1397              $enabled =1;
1398              last;
1399            }
1400            # append a dot, if an additional domain is added in the next iteration
1401            $aggregate .= ".";
1402          }
1403
1404          if ($enabled) {
1405            print " + ";
1406          }
1407          else {
1408            print "   ";
1409          }
1410        }
1411        else {
1412          print "   ";
1413        }
1414        print $_;
1415      }
1416      print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n";
1417    }
1418    close PS;
1419  }
1420  else {
1421    print "  *** Could not query Clang for the list of available checkers.\n";
1422    if (defined  $ArgClangNotFoundErrMsg) {
1423      print "  *** Reason: $ArgClangNotFoundErrMsg\n";
1424    }
1425  }
1426
1427print <<ENDTEXT
1428
1429BUILD OPTIONS
1430
1431 You can specify any build option acceptable to the build command.
1432
1433EXAMPLE
1434
1435 scan-build -o /tmp/myhtmldir make -j4
1436
1437The above example causes analysis reports to be deposited into a subdirectory
1438of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different
1439subdirectory is created each time scan-build analyzes a project. The analyzer
1440should support most parallel builds, but not distributed builds.
1441
1442ENDTEXT
1443}
1444
1445##----------------------------------------------------------------------------##
1446# HtmlEscape - HTML entity encode characters that are special in HTML
1447##----------------------------------------------------------------------------##
1448
1449sub HtmlEscape {
1450  # copy argument to new variable so we don't clobber the original
1451  my $arg = shift || '';
1452  my $tmp = $arg;
1453  $tmp =~ s/&/&amp;/g;
1454  $tmp =~ s/</&lt;/g;
1455  $tmp =~ s/>/&gt;/g;
1456  return $tmp;
1457}
1458
1459##----------------------------------------------------------------------------##
1460# ShellEscape - backslash escape characters that are special to the shell
1461##----------------------------------------------------------------------------##
1462
1463sub ShellEscape {
1464  # copy argument to new variable so we don't clobber the original
1465  my $arg = shift || '';
1466  if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
1467  return $arg;
1468}
1469
1470##----------------------------------------------------------------------------##
1471# FindXcrun - searches for the 'xcrun' executable. Returns "" if not found.
1472##----------------------------------------------------------------------------##
1473
1474sub FindXcrun {
1475  my $xcrun = `which xcrun`;
1476  chomp $xcrun;
1477  return $xcrun;
1478}
1479
1480##----------------------------------------------------------------------------##
1481# FindClang - searches for 'clang' executable.
1482##----------------------------------------------------------------------------##
1483
1484sub FindClang {
1485  if (!defined $Options{AnalyzerDiscoveryMethod}) {
1486    $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang");
1487    if (!defined $Clang || ! -x $Clang) {
1488      $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang");
1489      if (!defined $Clang || ! -x $Clang) {
1490        # When an Xcode toolchain is present, look for a clang in the sibling bin
1491        # of the parent of the bin directory. So if scan-build is at
1492        # $TOOLCHAIN/usr/local/bin/scan-build look for clang at
1493        # $TOOLCHAIN/usr/bin/clang.
1494        my $has_xcode_toolchain = FindXcrun() ne "";
1495        if ($has_xcode_toolchain && -f "$RealBin/../../bin/clang") {
1496          $Clang = Cwd::realpath("$RealBin/../../bin/clang");
1497        }
1498      }
1499    }
1500    if (!defined $Clang || ! -x $Clang) {
1501      return "error: Cannot find an executable 'clang' relative to" .
1502             " scan-build. Consider using --use-analyzer to pick a version of" .
1503             " 'clang' to use for static analysis.\n";
1504    }
1505  }
1506  else {
1507    if ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) {
1508      my $xcrun = FindXcrun();
1509      if ($xcrun eq "") {
1510        return "Cannot find 'xcrun' to find 'clang' for analysis.\n";
1511      }
1512      $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
1513      chomp $Clang;
1514      if ($Clang eq "") {
1515        return "No 'clang' executable found by 'xcrun'\n";
1516      }
1517    }
1518    else {
1519      $Clang = $Options{AnalyzerDiscoveryMethod};
1520      if (!defined $Clang or not -x $Clang) {
1521        return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n";
1522      }
1523    }
1524  }
1525  return undef;
1526}
1527
1528##----------------------------------------------------------------------------##
1529# Process command-line arguments.
1530##----------------------------------------------------------------------------##
1531
1532my $RequestDisplayHelp = 0;
1533my $ForceDisplayHelp = 0;
1534
1535sub ProcessArgs {
1536  my $Args = shift;
1537  my $NumArgs = 0;
1538
1539  while (@$Args) {
1540
1541    $NumArgs++;
1542
1543    # Scan for options we recognize.
1544
1545    my $arg = $Args->[0];
1546
1547    if ($arg eq "-h" or $arg eq "--help") {
1548      $RequestDisplayHelp = 1;
1549      shift @$Args;
1550      next;
1551    }
1552
1553    if ($arg eq '-analyze-headers') {
1554      shift @$Args;
1555      $Options{AnalyzeHeaders} = 1;
1556      next;
1557    }
1558
1559    if ($arg eq "-o") {
1560      if (defined($Options{OutputDir})) {
1561        DieDiag("Only one of '-o' or '--generate-index-only' can be specified.\n");
1562      }
1563
1564      shift @$Args;
1565
1566      if (!@$Args) {
1567        DieDiag("'-o' option requires a target directory name.\n");
1568      }
1569
1570      # Construct an absolute path.  Uses the current working directory
1571      # as a base if the original path was not absolute.
1572      my $OutDir = shift @$Args;
1573      mkpath($OutDir) unless (-e $OutDir);  # abs_path wants existing dir
1574      $Options{OutputDir} = abs_path($OutDir);
1575
1576      next;
1577    }
1578
1579    if ($arg eq "--generate-index-only") {
1580      if (defined($Options{OutputDir})) {
1581        DieDiag("Only one of '-o' or '--generate-index-only' can be specified.\n");
1582      }
1583
1584      shift @$Args;
1585
1586      if (!@$Args) {
1587        DieDiag("'--generate-index-only' option requires a target directory name.\n");
1588      }
1589
1590      # Construct an absolute path.  Uses the current working directory
1591      # as a base if the original path was not absolute.
1592      my $OutDir = shift @$Args;
1593      mkpath($OutDir) unless (-e $OutDir);  # abs_path wants existing dir
1594      $Options{OutputDir} = abs_path($OutDir);
1595      $Options{GenerateIndex} = 1;
1596
1597      next;
1598    }
1599
1600    if ($arg =~ /^--html-title(=(.+))?$/) {
1601      shift @$Args;
1602
1603      if (!defined $2 || $2 eq '') {
1604        if (!@$Args) {
1605          DieDiag("'--html-title' option requires a string.\n");
1606        }
1607
1608        $Options{HtmlTitle} = shift @$Args;
1609      } else {
1610        $Options{HtmlTitle} = $2;
1611      }
1612
1613      next;
1614    }
1615
1616    if ($arg eq "-k" or $arg eq "--keep-going") {
1617      shift @$Args;
1618      $Options{IgnoreErrors} = 1;
1619      next;
1620    }
1621
1622    if ($arg eq "--keep-cc") {
1623      shift @$Args;
1624      $Options{KeepCC} = 1;
1625      next;
1626    }
1627
1628    if ($arg =~ /^--use-cc(=(.+))?$/) {
1629      shift @$Args;
1630      my $cc;
1631
1632      if (!defined $2 || $2 eq "") {
1633        if (!@$Args) {
1634          DieDiag("'--use-cc' option requires a compiler executable name.\n");
1635        }
1636        $cc = shift @$Args;
1637      }
1638      else {
1639        $cc = $2;
1640      }
1641
1642      $Options{UseCC} = $cc;
1643      next;
1644    }
1645
1646    if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1647      shift @$Args;
1648      my $cxx;
1649
1650      if (!defined $2 || $2 eq "") {
1651        if (!@$Args) {
1652          DieDiag("'--use-c++' option requires a compiler executable name.\n");
1653        }
1654        $cxx = shift @$Args;
1655      }
1656      else {
1657        $cxx = $2;
1658      }
1659
1660      $Options{UseCXX} = $cxx;
1661      next;
1662    }
1663
1664    if ($arg =~ /^--analyzer-target(=(.+))?$/) {
1665      shift @ARGV;
1666      my $AnalyzerTarget;
1667
1668      if (!defined $2 || $2 eq "") {
1669        if (!@ARGV) {
1670          DieDiag("'--analyzer-target' option requires a target triple name.\n");
1671        }
1672        $AnalyzerTarget = shift @ARGV;
1673      }
1674      else {
1675        $AnalyzerTarget = $2;
1676      }
1677
1678      $Options{AnalyzerTarget} = $AnalyzerTarget;
1679      next;
1680    }
1681
1682    if ($arg eq "-v") {
1683      shift @$Args;
1684      $Options{Verbose}++;
1685      next;
1686    }
1687
1688    if ($arg eq "-V" or $arg eq "--view") {
1689      shift @$Args;
1690      $Options{ViewResults} = 1;
1691      next;
1692    }
1693
1694    if ($arg eq "--status-bugs") {
1695      shift @$Args;
1696      $Options{ExitStatusFoundBugs} = 1;
1697      next;
1698    }
1699
1700    if ($arg eq "--show-description") {
1701      shift @$Args;
1702      $Options{ShowDescription} = 1;
1703      next;
1704    }
1705
1706    if ($arg eq "-constraints") {
1707      shift @$Args;
1708      $Options{ConstraintsModel} = shift @$Args;
1709      next;
1710    }
1711
1712    if ($arg eq "-internal-stats") {
1713      shift @$Args;
1714      $Options{InternalStats} = 1;
1715      next;
1716    }
1717
1718    if ($arg eq "-sarif") {
1719      shift @$Args;
1720      $Options{OutputFormat} = "sarif";
1721      next;
1722    }
1723
1724    if ($arg eq "-plist") {
1725      shift @$Args;
1726      $Options{OutputFormat} = "plist";
1727      next;
1728    }
1729
1730    if ($arg eq "-plist-html") {
1731      shift @$Args;
1732      $Options{OutputFormat} = "plist-html";
1733      next;
1734    }
1735
1736    if ($arg eq "-analyzer-config") {
1737      shift @$Args;
1738      push @{$Options{ConfigOptions}}, shift @$Args;
1739      next;
1740    }
1741
1742    if ($arg eq "-no-failure-reports") {
1743      shift @$Args;
1744      $Options{ReportFailures} = 0;
1745      next;
1746    }
1747
1748    if ($arg eq "-stats") {
1749      shift @$Args;
1750      $Options{AnalyzerStats} = 1;
1751      next;
1752    }
1753
1754    if ($arg eq "-maxloop") {
1755      shift @$Args;
1756      $Options{MaxLoop} = shift @$Args;
1757      next;
1758    }
1759
1760    if ($arg eq "-enable-checker") {
1761      shift @$Args;
1762      my $Checker = shift @$Args;
1763      # Store $NumArgs to preserve the order the checkers were enabled.
1764      $Options{EnableCheckers}{$Checker} = $NumArgs;
1765      delete $Options{DisableCheckers}{$Checker};
1766      next;
1767    }
1768
1769    if ($arg eq "-disable-checker") {
1770      shift @$Args;
1771      my $Checker = shift @$Args;
1772      # Store $NumArgs to preserve the order the checkers are disabled/silenced.
1773      # See whether it is a core checker to disable. That means we do not want
1774      # to emit a report from that checker so we have to silence it.
1775      if (index($Checker, "core") == 0) {
1776        $Options{SilenceCheckers}{$Checker} = $NumArgs;
1777      } else {
1778        $Options{DisableCheckers}{$Checker} = $NumArgs;
1779        delete $Options{EnableCheckers}{$Checker};
1780      }
1781      next;
1782    }
1783
1784    if ($arg eq "--exclude") {
1785      shift @$Args;
1786      my $arg = shift @$Args;
1787      # Remove the trailing slash if any
1788      $arg =~ s|/$||;
1789      push @{$Options{Excludes}}, $arg;
1790      next;
1791    }
1792
1793    if ($arg eq "-load-plugin") {
1794      shift @$Args;
1795      push @{$Options{PluginsToLoad}}, shift @$Args;
1796      next;
1797    }
1798
1799    if ($arg eq "--use-analyzer") {
1800      shift @$Args;
1801      $Options{AnalyzerDiscoveryMethod} = shift @$Args;
1802      next;
1803    }
1804
1805    if ($arg =~ /^--use-analyzer=(.+)$/) {
1806      shift @$Args;
1807      $Options{AnalyzerDiscoveryMethod} = $1;
1808      next;
1809    }
1810
1811    if ($arg eq "--keep-empty") {
1812      shift @$Args;
1813      $Options{KeepEmpty} = 1;
1814      next;
1815    }
1816
1817    if ($arg eq "--override-compiler") {
1818      shift @$Args;
1819      $Options{OverrideCompiler} = 1;
1820      next;
1821    }
1822
1823    if ($arg eq "--force-analyze-debug-code") {
1824      shift @$Args;
1825      $Options{ForceAnalyzeDebugCode} = 1;
1826      next;
1827    }
1828
1829    DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1830
1831    $NumArgs--;
1832    last;
1833  }
1834  return $NumArgs;
1835}
1836
1837if (!@ARGV) {
1838  $ForceDisplayHelp = 1
1839}
1840
1841ProcessArgs(\@ARGV);
1842# All arguments are now shifted from @ARGV. The rest is a build command, if any.
1843
1844my $ClangNotFoundErrMsg = FindClang();
1845
1846if ($ForceDisplayHelp || $RequestDisplayHelp) {
1847  DisplayHelp($ClangNotFoundErrMsg);
1848  exit $ForceDisplayHelp;
1849}
1850
1851$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1852
1853if ($Options{GenerateIndex}) {
1854  $ClangVersion = "unknown";
1855  Finalize($Options{OutputDir}, 0);
1856}
1857
1858# Make sure to use "" to handle paths with spaces.
1859$ClangVersion = HtmlEscape(`"$Clang" --version`);
1860
1861if (!@ARGV and !$RequestDisplayHelp) {
1862  ErrorDiag("No build command specified.\n\n");
1863  $ForceDisplayHelp = 1;
1864}
1865
1866# Determine the output directory for the HTML reports.
1867my $BaseDir = $Options{OutputDir};
1868$Options{OutputDir} = GetHTMLRunDir($Options{OutputDir});
1869
1870DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg);
1871
1872$ClangCXX = $Clang;
1873if ($Clang !~ /\+\+(\.exe)?$/) {
1874  # If $Clang holds the name of the clang++ executable then we leave
1875  # $ClangCXX and $Clang equal, otherwise construct the name of the clang++
1876  # executable from the clang executable name.
1877
1878  # Determine operating system under which this copy of Perl was built.
1879  my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/);
1880  if($IsWinBuild) {
1881    $ClangCXX =~ s/.exe$/++.exe/;
1882  }
1883  else {
1884    $ClangCXX =~ s/\-\d+(\.\d+)?$//;
1885    $ClangCXX .= "++";
1886  }
1887}
1888
1889# Determine the location of ccc-analyzer.
1890my $AbsRealBin = Cwd::realpath($RealBin);
1891my $Cmd = "$AbsRealBin/../libexec/ccc-analyzer";
1892my $CmdCXX = "$AbsRealBin/../libexec/c++-analyzer";
1893
1894# Portability: use less strict but portable check -e (file exists) instead of
1895# non-portable -x (file is executable). On some windows ports -x just checks
1896# file extension to determine if a file is executable (see Perl language
1897# reference, perlport)
1898if (!defined $Cmd || ! -e $Cmd) {
1899  $Cmd = "$AbsRealBin/ccc-analyzer";
1900  DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd);
1901}
1902if (!defined $CmdCXX || ! -e $CmdCXX) {
1903  $CmdCXX = "$AbsRealBin/c++-analyzer";
1904  DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX);
1905}
1906
1907Diag("Using '$Clang' for static analysis\n");
1908
1909SetHtmlEnv(\@ARGV, $Options{OutputDir});
1910
1911my @AnalysesToRun;
1912foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} }
1913         keys %{$Options{EnableCheckers}}) {
1914  # Push checkers in order they were enabled.
1915  push @AnalysesToRun, "-analyzer-checker", $_;
1916}
1917foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} }
1918         keys %{$Options{DisableCheckers}}) {
1919  # Push checkers in order they were disabled.
1920  push @AnalysesToRun, "-analyzer-disable-checker", $_;
1921}
1922if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; }
1923if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
1924if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; }
1925
1926# Delay setting up other environment variables in case we can do true
1927# interposition.
1928my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun;
1929my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}};
1930my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}};
1931
1932if (%{$Options{SilenceCheckers}}) {
1933  $CCC_ANALYZER_CONFIG =
1934      $CCC_ANALYZER_CONFIG." -analyzer-config silence-checkers="
1935                          .join(';', sort {
1936                                            $Options{SilenceCheckers}{$a} <=>
1937                                            $Options{SilenceCheckers}{$b}
1938                                          } keys %{$Options{SilenceCheckers}});
1939}
1940
1941my %EnvVars = (
1942  'CC' => $Cmd,
1943  'CXX' => $CmdCXX,
1944  'CLANG' => $Clang,
1945  'CLANG_CXX' => $ClangCXX,
1946  'VERBOSE' => $Options{Verbose},
1947  'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
1948  'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
1949  'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG,
1950  'OUTPUT_DIR' => $Options{OutputDir},
1951  'CCC_CC' => $Options{UseCC},
1952  'CCC_CXX' => $Options{UseCXX},
1953  'CCC_REPORT_FAILURES' => $Options{ReportFailures},
1954  'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel},
1955  'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats},
1956  'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat},
1957  'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget},
1958  'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE' => $Options{ForceAnalyzeDebugCode}
1959);
1960
1961# Run the build.
1962my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Options{KeepCC},
1963	                        $Cmd, $CmdCXX, \%EnvVars);
1964
1965Finalize($BaseDir, $ExitStatus);
1966