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