blob: 903e19a2909a68c968886547130e72c59a31b24c [file] [log] [blame]
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001#!/usr/bin/env perl
2#
Andrew Walbran16937d02019-10-22 13:54:20 +01003# 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
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01006#
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 => {},
Andrew Scull0372a572018-11-16 15:47:06 +000060 Excludes => [],
Andrew Scull5e1ddfa2018-08-14 10:06:54 +010061 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 }
Andrew Scull0372a572018-11-16 15:47:06 +0000423
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
Andrew Scull5e1ddfa2018-08-14 10:06:54 +0100437 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
Andrew Scullcdfcccc2018-10-05 20:58:37 +01001030 # If --override-compiler is explicitly requested, resort to the old
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001031 # 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
Andrew Scull0372a572018-11-16 15:47:06 +00001096 $Cmd =~ /(.*\/?clang[^\/]*$)/ or
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001097 $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
Andrew Walbran16937d02019-10-22 13:54:20 +01001195 -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
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001200 -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
Andrew Scull0372a572018-11-16 15:47:06 +00001214 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.
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001221
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
Andrew Scullcdfcccc2018-10-05 20:58:37 +01001274 Specify the number of times a block can be visited before giving up.
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001275 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##----------------------------------------------------------------------------##
Andrew Walbran3d2c1972020-04-07 12:24:26 +01001463# 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##----------------------------------------------------------------------------##
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001473# 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");
Andrew Walbran3d2c1972020-04-07 12:24:26 +01001481 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 }
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001491 }
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$/) {
Andrew Walbran3d2c1972020-04-07 12:24:26 +01001500 my $xcrun = FindXcrun();
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001501 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
Andrew Walbran16937d02019-10-22 13:54:20 +01001691 if ($arg eq "-sarif") {
1692 shift @$Args;
1693 $Options{OutputFormat} = "sarif";
1694 next;
1695 }
1696
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001697 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
Andrew Scull0372a572018-11-16 15:47:06 +00001751 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
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001760 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;
Andrew Scull0372a572018-11-16 15:47:06 +00001875foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} }
1876 keys %{$Options{EnableCheckers}}) {
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001877 # Push checkers in order they were enabled.
1878 push @AnalysesToRun, "-analyzer-checker", $_;
1879}
Andrew Scull0372a572018-11-16 15:47:06 +00001880foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} }
1881 keys %{$Options{DisableCheckers}}) {
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001882 # 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}) {
Andrew Walbran16937d02019-10-22 13:54:20 +01001920 if ($Options{OutputFormat} =~ /plist/ ||
1921 $Options{OutputFormat} =~ /sarif/) {
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001922 Diag "Analysis run complete.\n";
Andrew Walbran16937d02019-10-22 13:54:20 +01001923 Diag "Analysis results (" .
1924 ($Options{OutputFormat} =~ /plist/ ? "plist" : "sarif") .
1925 " files) deposited in '$Options{OutputDir}'\n";
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001926 }
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);
Andrew Scull0372a572018-11-16 15:47:06 +00001943 exit $ExitStatus;
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001944 }
1945 }
1946}
1947
1948exit $ExitStatus;