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