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