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