blob: ed68b3b5a4e42f7a748c9474d4d39ab94b941774 [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##----------------------------------------------------------------------------##
1463# FindClang - searches for 'clang' executable.
1464##----------------------------------------------------------------------------##
1465
1466sub FindClang {
1467 if (!defined $Options{AnalyzerDiscoveryMethod}) {
1468 $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang");
1469 if (!defined $Clang || ! -x $Clang) {
1470 $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang");
1471 }
1472 if (!defined $Clang || ! -x $Clang) {
1473 return "error: Cannot find an executable 'clang' relative to" .
1474 " scan-build. Consider using --use-analyzer to pick a version of" .
1475 " 'clang' to use for static analysis.\n";
1476 }
1477 }
1478 else {
1479 if ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) {
1480 my $xcrun = `which xcrun`;
1481 chomp $xcrun;
1482 if ($xcrun eq "") {
1483 return "Cannot find 'xcrun' to find 'clang' for analysis.\n";
1484 }
1485 $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
1486 chomp $Clang;
1487 if ($Clang eq "") {
1488 return "No 'clang' executable found by 'xcrun'\n";
1489 }
1490 }
1491 else {
1492 $Clang = $Options{AnalyzerDiscoveryMethod};
1493 if (!defined $Clang or not -x $Clang) {
1494 return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n";
1495 }
1496 }
1497 }
1498 return undef;
1499}
1500
1501##----------------------------------------------------------------------------##
1502# Process command-line arguments.
1503##----------------------------------------------------------------------------##
1504
1505my $RequestDisplayHelp = 0;
1506my $ForceDisplayHelp = 0;
1507
1508sub ProcessArgs {
1509 my $Args = shift;
1510 my $NumArgs = 0;
1511
1512 while (@$Args) {
1513
1514 $NumArgs++;
1515
1516 # Scan for options we recognize.
1517
1518 my $arg = $Args->[0];
1519
1520 if ($arg eq "-h" or $arg eq "--help") {
1521 $RequestDisplayHelp = 1;
1522 shift @$Args;
1523 next;
1524 }
1525
1526 if ($arg eq '-analyze-headers') {
1527 shift @$Args;
1528 $Options{AnalyzeHeaders} = 1;
1529 next;
1530 }
1531
1532 if ($arg eq "-o") {
1533 shift @$Args;
1534
1535 if (!@$Args) {
1536 DieDiag("'-o' option requires a target directory name.\n");
1537 }
1538
1539 # Construct an absolute path. Uses the current working directory
1540 # as a base if the original path was not absolute.
1541 my $OutDir = shift @$Args;
1542 mkpath($OutDir) unless (-e $OutDir); # abs_path wants existing dir
1543 $Options{OutputDir} = abs_path($OutDir);
1544
1545 next;
1546 }
1547
1548 if ($arg =~ /^--html-title(=(.+))?$/) {
1549 shift @$Args;
1550
1551 if (!defined $2 || $2 eq '') {
1552 if (!@$Args) {
1553 DieDiag("'--html-title' option requires a string.\n");
1554 }
1555
1556 $Options{HtmlTitle} = shift @$Args;
1557 } else {
1558 $Options{HtmlTitle} = $2;
1559 }
1560
1561 next;
1562 }
1563
1564 if ($arg eq "-k" or $arg eq "--keep-going") {
1565 shift @$Args;
1566 $Options{IgnoreErrors} = 1;
1567 next;
1568 }
1569
1570 if ($arg eq "--keep-cc") {
1571 shift @$Args;
1572 $Options{KeepCC} = 1;
1573 next;
1574 }
1575
1576 if ($arg =~ /^--use-cc(=(.+))?$/) {
1577 shift @$Args;
1578 my $cc;
1579
1580 if (!defined $2 || $2 eq "") {
1581 if (!@$Args) {
1582 DieDiag("'--use-cc' option requires a compiler executable name.\n");
1583 }
1584 $cc = shift @$Args;
1585 }
1586 else {
1587 $cc = $2;
1588 }
1589
1590 $Options{UseCC} = $cc;
1591 next;
1592 }
1593
1594 if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1595 shift @$Args;
1596 my $cxx;
1597
1598 if (!defined $2 || $2 eq "") {
1599 if (!@$Args) {
1600 DieDiag("'--use-c++' option requires a compiler executable name.\n");
1601 }
1602 $cxx = shift @$Args;
1603 }
1604 else {
1605 $cxx = $2;
1606 }
1607
1608 $Options{UseCXX} = $cxx;
1609 next;
1610 }
1611
1612 if ($arg =~ /^--analyzer-target(=(.+))?$/) {
1613 shift @ARGV;
1614 my $AnalyzerTarget;
1615
1616 if (!defined $2 || $2 eq "") {
1617 if (!@ARGV) {
1618 DieDiag("'--analyzer-target' option requires a target triple name.\n");
1619 }
1620 $AnalyzerTarget = shift @ARGV;
1621 }
1622 else {
1623 $AnalyzerTarget = $2;
1624 }
1625
1626 $Options{AnalyzerTarget} = $AnalyzerTarget;
1627 next;
1628 }
1629
1630 if ($arg eq "-v") {
1631 shift @$Args;
1632 $Options{Verbose}++;
1633 next;
1634 }
1635
1636 if ($arg eq "-V" or $arg eq "--view") {
1637 shift @$Args;
1638 $Options{ViewResults} = 1;
1639 next;
1640 }
1641
1642 if ($arg eq "--status-bugs") {
1643 shift @$Args;
1644 $Options{ExitStatusFoundBugs} = 1;
1645 next;
1646 }
1647
1648 if ($arg eq "--show-description") {
1649 shift @$Args;
1650 $Options{ShowDescription} = 1;
1651 next;
1652 }
1653
1654 if ($arg eq "-store") {
1655 shift @$Args;
1656 $Options{StoreModel} = shift @$Args;
1657 next;
1658 }
1659
1660 if ($arg eq "-constraints") {
1661 shift @$Args;
1662 $Options{ConstraintsModel} = shift @$Args;
1663 next;
1664 }
1665
1666 if ($arg eq "-internal-stats") {
1667 shift @$Args;
1668 $Options{InternalStats} = 1;
1669 next;
1670 }
1671
Andrew Walbran16937d02019-10-22 13:54:20 +01001672 if ($arg eq "-sarif") {
1673 shift @$Args;
1674 $Options{OutputFormat} = "sarif";
1675 next;
1676 }
1677
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001678 if ($arg eq "-plist") {
1679 shift @$Args;
1680 $Options{OutputFormat} = "plist";
1681 next;
1682 }
1683
1684 if ($arg eq "-plist-html") {
1685 shift @$Args;
1686 $Options{OutputFormat} = "plist-html";
1687 next;
1688 }
1689
1690 if ($arg eq "-analyzer-config") {
1691 shift @$Args;
1692 push @{$Options{ConfigOptions}}, shift @$Args;
1693 next;
1694 }
1695
1696 if ($arg eq "-no-failure-reports") {
1697 shift @$Args;
1698 $Options{ReportFailures} = 0;
1699 next;
1700 }
1701
1702 if ($arg eq "-stats") {
1703 shift @$Args;
1704 $Options{AnalyzerStats} = 1;
1705 next;
1706 }
1707
1708 if ($arg eq "-maxloop") {
1709 shift @$Args;
1710 $Options{MaxLoop} = shift @$Args;
1711 next;
1712 }
1713
1714 if ($arg eq "-enable-checker") {
1715 shift @$Args;
1716 my $Checker = shift @$Args;
1717 # Store $NumArgs to preserve the order the checkers were enabled.
1718 $Options{EnableCheckers}{$Checker} = $NumArgs;
1719 delete $Options{DisableCheckers}{$Checker};
1720 next;
1721 }
1722
1723 if ($arg eq "-disable-checker") {
1724 shift @$Args;
1725 my $Checker = shift @$Args;
1726 # Store $NumArgs to preserve the order the checkers were disabled.
1727 $Options{DisableCheckers}{$Checker} = $NumArgs;
1728 delete $Options{EnableCheckers}{$Checker};
1729 next;
1730 }
1731
Andrew Scull0372a572018-11-16 15:47:06 +00001732 if ($arg eq "--exclude") {
1733 shift @$Args;
1734 my $arg = shift @$Args;
1735 # Remove the trailing slash if any
1736 $arg =~ s|/$||;
1737 push @{$Options{Excludes}}, $arg;
1738 next;
1739 }
1740
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001741 if ($arg eq "-load-plugin") {
1742 shift @$Args;
1743 push @{$Options{PluginsToLoad}}, shift @$Args;
1744 next;
1745 }
1746
1747 if ($arg eq "--use-analyzer") {
1748 shift @$Args;
1749 $Options{AnalyzerDiscoveryMethod} = shift @$Args;
1750 next;
1751 }
1752
1753 if ($arg =~ /^--use-analyzer=(.+)$/) {
1754 shift @$Args;
1755 $Options{AnalyzerDiscoveryMethod} = $1;
1756 next;
1757 }
1758
1759 if ($arg eq "--keep-empty") {
1760 shift @$Args;
1761 $Options{KeepEmpty} = 1;
1762 next;
1763 }
1764
1765 if ($arg eq "--override-compiler") {
1766 shift @$Args;
1767 $Options{OverrideCompiler} = 1;
1768 next;
1769 }
1770
1771 if ($arg eq "--force-analyze-debug-code") {
1772 shift @$Args;
1773 $Options{ForceAnalyzeDebugCode} = 1;
1774 next;
1775 }
1776
1777 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1778
1779 $NumArgs--;
1780 last;
1781 }
1782 return $NumArgs;
1783}
1784
1785if (!@ARGV) {
1786 $ForceDisplayHelp = 1
1787}
1788
1789ProcessArgs(\@ARGV);
1790# All arguments are now shifted from @ARGV. The rest is a build command, if any.
1791
1792if (!@ARGV and !$RequestDisplayHelp) {
1793 ErrorDiag("No build command specified.\n\n");
1794 $ForceDisplayHelp = 1;
1795}
1796
1797my $ClangNotFoundErrMsg = FindClang();
1798
1799if ($ForceDisplayHelp || $RequestDisplayHelp) {
1800 DisplayHelp($ClangNotFoundErrMsg);
1801 exit $ForceDisplayHelp;
1802}
1803
1804DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg);
1805
1806$ClangCXX = $Clang;
1807if ($Clang !~ /\+\+(\.exe)?$/) {
1808 # If $Clang holds the name of the clang++ executable then we leave
1809 # $ClangCXX and $Clang equal, otherwise construct the name of the clang++
1810 # executable from the clang executable name.
1811
1812 # Determine operating system under which this copy of Perl was built.
1813 my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/);
1814 if($IsWinBuild) {
1815 $ClangCXX =~ s/.exe$/++.exe/;
1816 }
1817 else {
1818 $ClangCXX =~ s/\-\d+\.\d+$//;
1819 $ClangCXX .= "++";
1820 }
1821}
1822
1823# Make sure to use "" to handle paths with spaces.
1824$ClangVersion = HtmlEscape(`"$Clang" --version`);
1825
1826# Determine where results go.
1827$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1828
1829# Determine the output directory for the HTML reports.
1830my $BaseDir = $Options{OutputDir};
1831$Options{OutputDir} = GetHTMLRunDir($Options{OutputDir});
1832
1833# Determine the location of ccc-analyzer.
1834my $AbsRealBin = Cwd::realpath($RealBin);
1835my $Cmd = "$AbsRealBin/../libexec/ccc-analyzer";
1836my $CmdCXX = "$AbsRealBin/../libexec/c++-analyzer";
1837
1838# Portability: use less strict but portable check -e (file exists) instead of
1839# non-portable -x (file is executable). On some windows ports -x just checks
1840# file extension to determine if a file is executable (see Perl language
1841# reference, perlport)
1842if (!defined $Cmd || ! -e $Cmd) {
1843 $Cmd = "$AbsRealBin/ccc-analyzer";
1844 DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd);
1845}
1846if (!defined $CmdCXX || ! -e $CmdCXX) {
1847 $CmdCXX = "$AbsRealBin/c++-analyzer";
1848 DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX);
1849}
1850
1851Diag("Using '$Clang' for static analysis\n");
1852
1853SetHtmlEnv(\@ARGV, $Options{OutputDir});
1854
1855my @AnalysesToRun;
Andrew Scull0372a572018-11-16 15:47:06 +00001856foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} }
1857 keys %{$Options{EnableCheckers}}) {
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001858 # Push checkers in order they were enabled.
1859 push @AnalysesToRun, "-analyzer-checker", $_;
1860}
Andrew Scull0372a572018-11-16 15:47:06 +00001861foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} }
1862 keys %{$Options{DisableCheckers}}) {
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001863 # Push checkers in order they were disabled.
1864 push @AnalysesToRun, "-analyzer-disable-checker", $_;
1865}
1866if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; }
1867if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
1868if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; }
1869
1870# Delay setting up other environment variables in case we can do true
1871# interposition.
1872my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun;
1873my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}};
1874my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}};
1875my %EnvVars = (
1876 'CC' => $Cmd,
1877 'CXX' => $CmdCXX,
1878 'CLANG' => $Clang,
1879 'CLANG_CXX' => $ClangCXX,
1880 'VERBOSE' => $Options{Verbose},
1881 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
1882 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
1883 'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG,
1884 'OUTPUT_DIR' => $Options{OutputDir},
1885 'CCC_CC' => $Options{UseCC},
1886 'CCC_CXX' => $Options{UseCXX},
1887 'CCC_REPORT_FAILURES' => $Options{ReportFailures},
1888 'CCC_ANALYZER_STORE_MODEL' => $Options{StoreModel},
1889 'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel},
1890 'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats},
1891 'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat},
1892 'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget},
1893 'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE' => $Options{ForceAnalyzeDebugCode}
1894);
1895
1896# Run the build.
1897my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Options{KeepCC},
1898 $Cmd, $CmdCXX, \%EnvVars);
1899
1900if (defined $Options{OutputFormat}) {
Andrew Walbran16937d02019-10-22 13:54:20 +01001901 if ($Options{OutputFormat} =~ /plist/ ||
1902 $Options{OutputFormat} =~ /sarif/) {
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001903 Diag "Analysis run complete.\n";
Andrew Walbran16937d02019-10-22 13:54:20 +01001904 Diag "Analysis results (" .
1905 ($Options{OutputFormat} =~ /plist/ ? "plist" : "sarif") .
1906 " files) deposited in '$Options{OutputDir}'\n";
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001907 }
1908 if ($Options{OutputFormat} =~ /html/) {
1909 # Postprocess the HTML directory.
1910 my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir,
1911 $Options{AnalyzerStats}, $Options{KeepEmpty});
1912
1913 if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") {
1914 Diag "Analysis run complete.\n";
1915 Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n";
1916 my $ScanView = Cwd::realpath("$RealBin/scan-view");
1917 if (! -x $ScanView) { $ScanView = "scan-view"; }
1918 if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); }
1919 exec $ScanView, "$Options{OutputDir}";
1920 }
1921
1922 if ($Options{ExitStatusFoundBugs}) {
1923 exit 1 if ($NumBugs > 0);
Andrew Scull0372a572018-11-16 15:47:06 +00001924 exit $ExitStatus;
Andrew Scull5e1ddfa2018-08-14 10:06:54 +01001925 }
1926 }
1927}
1928
1929exit $ExitStatus;