Clang Project

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