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