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