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