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