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