scan-build revision f190f6b88e0648ebb5f49bab3c788e03e13b9069
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 15use strict; 16use warnings; 17use FindBin qw($RealBin); 18use Digest::MD5; 19use File::Basename; 20use File::Find; 21use Term::ANSIColor; 22use Term::ANSIColor qw(:constants); 23use Cwd qw/ getcwd abs_path /; 24use Sys::Hostname; 25 26my $Verbose = 0; # Verbose output from this script. 27my $Prog = "scan-build"; 28my $BuildName; 29my $BuildDate; 30 31my $TERM = $ENV{'TERM'}; 32my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT 33 and defined $ENV{'SCAN_BUILD_COLOR'}); 34 35my $UserName = HtmlEscape(getpwuid($<) || 'unknown'); 36my $HostName = HtmlEscape(hostname() || 'unknown'); 37my $CurrentDir = HtmlEscape(getcwd()); 38my $CurrentDirSuffix = basename($CurrentDir); 39 40my @PluginsToLoad; 41my $CmdArgs; 42 43my $HtmlTitle; 44 45my $Date = localtime(); 46 47##----------------------------------------------------------------------------## 48# Diagnostics 49##----------------------------------------------------------------------------## 50 51sub Diag { 52 if ($UseColor) { 53 print BOLD, MAGENTA "$Prog: @_"; 54 print RESET; 55 } 56 else { 57 print "$Prog: @_"; 58 } 59} 60 61sub 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 71sub 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 79sub 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 95if (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 110my $Clang; 111my $ClangSB; 112my $ClangCXX; 113my $ClangVersion; 114 115##----------------------------------------------------------------------------## 116# GetHTMLRunDir - Construct an HTML directory name for the current sub-run. 117##----------------------------------------------------------------------------## 118 119sub 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 202sub 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 228sub 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 249my $Prefix; 250 251sub 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 264sub GetPrefix { 265 return $Prefix; 266} 267 268##----------------------------------------------------------------------------## 269# UpdateInFilePath - Update the path in the report file. 270##----------------------------------------------------------------------------## 271 272sub 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 294sub 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 330my %AlreadyScanned; 331 332sub 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 409sub 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 438sub 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 485my @filesFound; 486my $baseDir; 487sub 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 497sub 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 553print 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"> 560function 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 570function 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 582function 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 592var NumUnchecked = 0; 593 594function 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> 619ENDTEXT 620 621print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n" 622 if (defined($BuildName) && defined($BuildDate)); 623 624print OUT <<ENDTEXT; 625</table> 626ENDTEXT 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); 648print 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> 652ENDTEXT 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 682print 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> 697ENDTEXT 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 859sub 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 876sub 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 899sub RunXcodebuild { 900 my $Args = shift; 901 my $IgnoreErrors = shift; 902 my $CCAnalyzer = shift; 903 my $CXXAnalyzer = shift; 904 my $Options = shift; 905 906 if ($IgnoreErrors) { 907 AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES"); 908 } 909 910 # Detect the version of Xcode. If Xcode 4.6 or higher, use new 911 # in situ support for analyzer interposition without needed to override 912 # the compiler. 913 open(DETECT_XCODE, "xcodebuild -version |") or 914 die "error: cannot detect version of xcodebuild\n"; 915 916 my $oldBehavior = 1; 917 918 while(<DETECT_XCODE>) { 919 if (/^Xcode (.+)$/) { 920 my $ver = $1; 921 if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) { 922 if ($1 >= 4.6) { 923 $oldBehavior = 0; 924 last; 925 } 926 } 927 } 928 } 929 close(DETECT_XCODE); 930 931 if ($oldBehavior == 0) { 932 my $OutputDir = $Options->{"OUTPUT_DIR"}; 933 my $CLANG = $Options->{"CLANG"}; 934 push @$Args, 935 "RUN_CLANG_STATIC_ANALYZER=YES", 936 "CLANG_ANALYZER_OUTPUT=plist-html", 937 "CLANG_ANALYZER_EXEC=$CLANG", 938 "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir"; 939 940 return (system(@$Args) >> 8); 941 } 942 943 # Default to old behavior where we insert a bogus compiler. 944 SetEnv($Options); 945 946 # Check if using iPhone SDK 3.0 (simulator). If so the compiler being 947 # used should be gcc-4.2. 948 if (!defined $ENV{"CCC_CC"}) { 949 for (my $i = 0 ; $i < scalar(@$Args); ++$i) { 950 if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) { 951 if (@$Args[$i+1] =~ /^iphonesimulator3/) { 952 $ENV{"CCC_CC"} = "gcc-4.2"; 953 $ENV{"CCC_CXX"} = "g++-4.2"; 954 } 955 } 956 } 957 } 958 959 # Disable PCH files until clang supports them. 960 AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO"); 961 962 # When 'CC' is set, xcodebuild uses it to do all linking, even if we are 963 # linking C++ object files. Set 'LDPLUSPLUS' so that xcodebuild uses 'g++' 964 # (via c++-analyzer) when linking such files. 965 $ENV{"LDPLUSPLUS"} = $CXXAnalyzer; 966 967 return (system(@$Args) >> 8); 968} 969 970sub RunBuildCommand { 971 my $Args = shift; 972 my $IgnoreErrors = shift; 973 my $Cmd = $Args->[0]; 974 my $CCAnalyzer = shift; 975 my $CXXAnalyzer = shift; 976 my $Options = shift; 977 978 # Get only the part of the command after the last '/'. 979 if ($Cmd =~ /\/([^\/]+)$/) { 980 $Cmd = $1; 981 } 982 983 if ($Cmd eq "xcodebuild") { 984 return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $Options); 985 } 986 987 # Setup the environment. 988 SetEnv($Options); 989 990 if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 991 $Cmd =~ /(.*\/?cc[^\/]*$)/ or 992 $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or 993 $Cmd =~ /(.*\/?clang$)/ or 994 $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) { 995 996 if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) { 997 $ENV{"CCC_CC"} = $1; 998 } 999 1000 shift @$Args; 1001 unshift @$Args, $CCAnalyzer; 1002 } 1003 elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or 1004 $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or 1005 $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or 1006 $Cmd =~ /(.*\/?clang\+\+$)/ or 1007 $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) { 1008 if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) { 1009 $ENV{"CCC_CXX"} = $1; 1010 } 1011 shift @$Args; 1012 unshift @$Args, $CXXAnalyzer; 1013 } 1014 elsif ($IgnoreErrors) { 1015 if ($Cmd eq "make" or $Cmd eq "gmake") { 1016 AddIfNotPresent($Args, "CC=$CCAnalyzer"); 1017 AddIfNotPresent($Args, "CXX=$CXXAnalyzer"); 1018 AddIfNotPresent($Args,"-k"); 1019 AddIfNotPresent($Args,"-i"); 1020 } 1021 } 1022 1023 return (system(@$Args) >> 8); 1024} 1025 1026##----------------------------------------------------------------------------## 1027# DisplayHelp - Utility function to display all help options. 1028##----------------------------------------------------------------------------## 1029 1030sub DisplayHelp { 1031 1032print <<ENDTEXT; 1033USAGE: $Prog [options] <build command> [build options] 1034 1035ENDTEXT 1036 1037 if (defined $BuildName) { 1038 print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n"; 1039 } 1040 1041print <<ENDTEXT; 1042OPTIONS: 1043 1044 -analyze-headers 1045 1046 Also analyze functions in #included files. By default, such functions 1047 are skipped unless they are called by functions within the main source file. 1048 1049 -o <output location> 1050 1051 Specifies the output directory for analyzer reports. Subdirectories will be 1052 created as needed to represent separate "runs" of the analyzer. If this 1053 option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X) 1054 to store the reports. 1055 1056 -h 1057 --help 1058 1059 Display this message. 1060 1061 -k 1062 --keep-going 1063 1064 Add a "keep on going" option to the specified build command. This option 1065 currently supports make and xcodebuild. This is a convenience option; one 1066 can specify this behavior directly using build options. 1067 1068 --html-title [title] 1069 --html-title=[title] 1070 1071 Specify the title used on generated HTML pages. If not specified, a default 1072 title will be used. 1073 1074 -plist 1075 1076 By default the output of scan-build is a set of HTML files. This option 1077 outputs the results as a set of .plist files. 1078 1079 -plist-html 1080 1081 By default the output of scan-build is a set of HTML files. This option 1082 outputs the results as a set of HTML and .plist files. 1083 1084 --status-bugs 1085 1086 By default, the exit status of scan-build is the same as the executed build 1087 command. Specifying this option causes the exit status of scan-build to be 1 1088 if it found potential bugs and 0 otherwise. 1089 1090 --use-cc [compiler path] 1091 --use-cc=[compiler path] 1092 1093 scan-build analyzes a project by interposing a "fake compiler", which 1094 executes a real compiler for compilation and the static analyzer for analysis. 1095 Because of the current implementation of interposition, scan-build does not 1096 know what compiler your project normally uses. Instead, it simply overrides 1097 the CC environment variable, and guesses your default compiler. 1098 1099 In the future, this interposition mechanism to be improved, but if you need 1100 scan-build to use a specific compiler for *compilation* then you can use 1101 this option to specify a path to that compiler. 1102 1103 --use-c++ [compiler path] 1104 --use-c++=[compiler path] 1105 1106 This is the same as "-use-cc" but for C++ code. 1107 1108 -v 1109 1110 Enable verbose output from scan-build. A second and third '-v' increases 1111 verbosity. 1112 1113 -V 1114 --view 1115 1116 View analysis results in a web browser when the build completes. 1117 1118ADVANCED OPTIONS: 1119 1120 -no-failure-reports 1121 1122 Do not create a 'failures' subdirectory that includes analyzer crash reports 1123 and preprocessed source files. 1124 1125 -stats 1126 1127 Generates visitation statistics for the project being analyzed. 1128 1129 -maxloop <loop count> 1130 1131 Specifiy the number of times a block can be visited before giving up. 1132 Default is 4. Increase for more comprehensive coverage at a cost of speed. 1133 1134 -internal-stats 1135 1136 Generate internal analyzer statistics. 1137 1138 --use-analyzer [Xcode|path to clang] 1139 --use-analyzer=[Xcode|path to clang] 1140 1141 scan-build uses the 'clang' executable relative to itself for static 1142 analysis. One can override this behavior with this option by using the 1143 'clang' packaged with Xcode (on OS X) or from the PATH. 1144 1145 --keep-empty 1146 1147 Don't remove the build results directory even if no issues were reported. 1148 1149CONTROLLING CHECKERS: 1150 1151 A default group of checkers are always run unless explicitly disabled. 1152 Checkers may be enabled/disabled using the following options: 1153 1154 -enable-checker [checker name] 1155 -disable-checker [checker name] 1156 1157LOADING CHECKERS: 1158 1159 Loading external checkers using the clang plugin interface: 1160 1161 -load-plugin [plugin library] 1162ENDTEXT 1163 1164# Query clang for list of checkers that are enabled. 1165 1166# create a list to load the plugins via the 'Xclang' command line 1167# argument 1168my @PluginLoadCommandline_xclang; 1169foreach my $param ( @PluginsToLoad ) { 1170 push ( @PluginLoadCommandline_xclang, "-Xclang" ); 1171 push ( @PluginLoadCommandline_xclang, $param ); 1172} 1173my %EnabledCheckers; 1174foreach my $lang ("c", "objective-c", "objective-c++", "c++") { 1175 pipe(FROM_CHILD, TO_PARENT); 1176 my $pid = fork(); 1177 if ($pid == 0) { 1178 close FROM_CHILD; 1179 open(STDOUT,">&", \*TO_PARENT); 1180 open(STDERR,">&", \*TO_PARENT); 1181 exec $Clang, ( @PluginLoadCommandline_xclang, '--analyze', '-x', $lang, '-', '-###'); 1182 } 1183 close(TO_PARENT); 1184 while(<FROM_CHILD>) { 1185 foreach my $val (split /\s+/) { 1186 $val =~ s/\"//g; 1187 if ($val =~ /-analyzer-checker\=([^\s]+)/) { 1188 $EnabledCheckers{$1} = 1; 1189 } 1190 } 1191 } 1192 waitpid($pid,0); 1193 close(FROM_CHILD); 1194} 1195 1196# Query clang for complete list of checkers. 1197if (defined $Clang && -x $Clang) { 1198 pipe(FROM_CHILD, TO_PARENT); 1199 my $pid = fork(); 1200 if ($pid == 0) { 1201 close FROM_CHILD; 1202 open(STDOUT,">&", \*TO_PARENT); 1203 open(STDERR,">&", \*TO_PARENT); 1204 exec $Clang, ('-cc1', @PluginsToLoad , '-analyzer-checker-help'); 1205 } 1206 close(TO_PARENT); 1207 my $foundCheckers = 0; 1208 while(<FROM_CHILD>) { 1209 if (/CHECKERS:/) { 1210 $foundCheckers = 1; 1211 last; 1212 } 1213 } 1214 if (!$foundCheckers) { 1215 print " *** Could not query Clang for the list of available checkers."; 1216 } 1217 else { 1218 print("\nAVAILABLE CHECKERS:\n\n"); 1219 my $skip = 0; 1220 while(<FROM_CHILD>) { 1221 if (/experimental/) { 1222 $skip = 1; 1223 next; 1224 } 1225 if ($skip) { 1226 next if (!/^\s\s[^\s]/); 1227 $skip = 0; 1228 } 1229 s/^\s\s//; 1230 if (/^([^\s]+)/) { 1231 # Is the checker enabled? 1232 my $checker = $1; 1233 my $enabled = 0; 1234 my $aggregate = ""; 1235 foreach my $domain (split /\./, $checker) { 1236 $aggregate .= $domain; 1237 if ($EnabledCheckers{$aggregate}) { 1238 $enabled =1; 1239 last; 1240 } 1241 # append a dot, if an additional domain is added in the next iteration 1242 $aggregate .= "."; 1243 } 1244 1245 if ($enabled) { 1246 print " + "; 1247 } 1248 else { 1249 print " "; 1250 } 1251 } 1252 else { 1253 print " "; 1254 } 1255 print $_; 1256 } 1257 print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n" 1258 } 1259 waitpid($pid,0); 1260 close(FROM_CHILD); 1261} 1262 1263print <<ENDTEXT 1264 1265BUILD OPTIONS 1266 1267 You can specify any build option acceptable to the build command. 1268 1269EXAMPLE 1270 1271 scan-build -o /tmp/myhtmldir make -j4 1272 1273The above example causes analysis reports to be deposited into a subdirectory 1274of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different 1275subdirectory is created each time scan-build analyzes a project. The analyzer 1276should support most parallel builds, but not distributed builds. 1277 1278ENDTEXT 1279} 1280 1281##----------------------------------------------------------------------------## 1282# HtmlEscape - HTML entity encode characters that are special in HTML 1283##----------------------------------------------------------------------------## 1284 1285sub HtmlEscape { 1286 # copy argument to new variable so we don't clobber the original 1287 my $arg = shift || ''; 1288 my $tmp = $arg; 1289 $tmp =~ s/&/&/g; 1290 $tmp =~ s/</</g; 1291 $tmp =~ s/>/>/g; 1292 return $tmp; 1293} 1294 1295##----------------------------------------------------------------------------## 1296# ShellEscape - backslash escape characters that are special to the shell 1297##----------------------------------------------------------------------------## 1298 1299sub ShellEscape { 1300 # copy argument to new variable so we don't clobber the original 1301 my $arg = shift || ''; 1302 if ($arg =~ /["\s]/) { return "'" . $arg . "'"; } 1303 return $arg; 1304} 1305 1306##----------------------------------------------------------------------------## 1307# Process command-line arguments. 1308##----------------------------------------------------------------------------## 1309 1310my $AnalyzeHeaders = 0; 1311my $HtmlDir; # Parent directory to store HTML files. 1312my $IgnoreErrors = 0; # Ignore build errors. 1313my $ViewResults = 0; # View results when the build terminates. 1314my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found 1315my $KeepEmpty = 0; # Don't remove output directory even with 0 results. 1316my @AnalysesToRun; 1317my $StoreModel; 1318my $ConstraintsModel; 1319my $InternalStats; 1320my $OutputFormat = "html"; 1321my $AnalyzerStats = 0; 1322my $MaxLoop = 0; 1323my $RequestDisplayHelp = 0; 1324my $ForceDisplayHelp = 0; 1325my $AnalyzerDiscoveryMethod; 1326 1327if (!@ARGV) { 1328 $ForceDisplayHelp = 1 1329} 1330 1331while (@ARGV) { 1332 1333 # Scan for options we recognize. 1334 1335 my $arg = $ARGV[0]; 1336 1337 if ($arg eq "-h" or $arg eq "--help") { 1338 $RequestDisplayHelp = 1; 1339 shift @ARGV; 1340 next; 1341 } 1342 1343 if ($arg eq '-analyze-headers') { 1344 shift @ARGV; 1345 $AnalyzeHeaders = 1; 1346 next; 1347 } 1348 1349 if ($arg eq "-o") { 1350 shift @ARGV; 1351 1352 if (!@ARGV) { 1353 DieDiag("'-o' option requires a target directory name.\n"); 1354 } 1355 1356 # Construct an absolute path. Uses the current working directory 1357 # as a base if the original path was not absolute. 1358 $HtmlDir = abs_path(shift @ARGV); 1359 1360 next; 1361 } 1362 1363 if ($arg =~ /^--html-title(=(.+))?$/) { 1364 shift @ARGV; 1365 1366 if (!defined $2 || $2 eq '') { 1367 if (!@ARGV) { 1368 DieDiag("'--html-title' option requires a string.\n"); 1369 } 1370 1371 $HtmlTitle = shift @ARGV; 1372 } else { 1373 $HtmlTitle = $2; 1374 } 1375 1376 next; 1377 } 1378 1379 if ($arg eq "-k" or $arg eq "--keep-going") { 1380 shift @ARGV; 1381 $IgnoreErrors = 1; 1382 next; 1383 } 1384 1385 if ($arg =~ /^--use-cc(=(.+))?$/) { 1386 shift @ARGV; 1387 my $cc; 1388 1389 if (!defined $2 || $2 eq "") { 1390 if (!@ARGV) { 1391 DieDiag("'--use-cc' option requires a compiler executable name.\n"); 1392 } 1393 $cc = shift @ARGV; 1394 } 1395 else { 1396 $cc = $2; 1397 } 1398 1399 $ENV{"CCC_CC"} = $cc; 1400 next; 1401 } 1402 1403 if ($arg =~ /^--use-c\+\+(=(.+))?$/) { 1404 shift @ARGV; 1405 my $cxx; 1406 1407 if (!defined $2 || $2 eq "") { 1408 if (!@ARGV) { 1409 DieDiag("'--use-c++' option requires a compiler executable name.\n"); 1410 } 1411 $cxx = shift @ARGV; 1412 } 1413 else { 1414 $cxx = $2; 1415 } 1416 1417 $ENV{"CCC_CXX"} = $cxx; 1418 next; 1419 } 1420 1421 if ($arg eq "-v") { 1422 shift @ARGV; 1423 $Verbose++; 1424 next; 1425 } 1426 1427 if ($arg eq "-V" or $arg eq "--view") { 1428 shift @ARGV; 1429 $ViewResults = 1; 1430 next; 1431 } 1432 1433 if ($arg eq "--status-bugs") { 1434 shift @ARGV; 1435 $ExitStatusFoundBugs = 1; 1436 next; 1437 } 1438 1439 if ($arg eq "-store") { 1440 shift @ARGV; 1441 $StoreModel = shift @ARGV; 1442 next; 1443 } 1444 1445 if ($arg eq "-constraints") { 1446 shift @ARGV; 1447 $ConstraintsModel = shift @ARGV; 1448 next; 1449 } 1450 1451 if ($arg eq "-internal-stats") { 1452 shift @ARGV; 1453 $InternalStats = 1; 1454 next; 1455 } 1456 1457 if ($arg eq "-plist") { 1458 shift @ARGV; 1459 $OutputFormat = "plist"; 1460 next; 1461 } 1462 if ($arg eq "-plist-html") { 1463 shift @ARGV; 1464 $OutputFormat = "plist-html"; 1465 next; 1466 } 1467 1468 if ($arg eq "-no-failure-reports") { 1469 $ENV{"CCC_REPORT_FAILURES"} = 0; 1470 next; 1471 } 1472 if ($arg eq "-stats") { 1473 shift @ARGV; 1474 $AnalyzerStats = 1; 1475 next; 1476 } 1477 if ($arg eq "-maxloop") { 1478 shift @ARGV; 1479 $MaxLoop = shift @ARGV; 1480 next; 1481 } 1482 if ($arg eq "-enable-checker") { 1483 shift @ARGV; 1484 push @AnalysesToRun, "-analyzer-checker", shift @ARGV; 1485 next; 1486 } 1487 if ($arg eq "-disable-checker") { 1488 shift @ARGV; 1489 push @AnalysesToRun, "-analyzer-disable-checker", shift @ARGV; 1490 next; 1491 } 1492 if ($arg eq "-load-plugin") { 1493 shift @ARGV; 1494 push @PluginsToLoad, "-load", shift @ARGV; 1495 next; 1496 } 1497 if ($arg eq "--use-analyzer") { 1498 shift @ARGV; 1499 $AnalyzerDiscoveryMethod = shift @ARGV; 1500 next; 1501 } 1502 if ($arg =~ /^--use-analyzer=(.+)$/) { 1503 shift @ARGV; 1504 $AnalyzerDiscoveryMethod = $1; 1505 next; 1506 } 1507 if ($arg eq "--keep-empty") { 1508 shift @ARGV; 1509 $KeepEmpty = 1; 1510 next; 1511 } 1512 1513 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); 1514 1515 last; 1516} 1517 1518if (!@ARGV and !$RequestDisplayHelp) { 1519 ErrorDiag("No build command specified.\n\n"); 1520 $ForceDisplayHelp = 1; 1521} 1522 1523# Find 'clang' 1524if (!defined $AnalyzerDiscoveryMethod) { 1525 $Clang = Cwd::realpath("$RealBin/bin/clang"); 1526 if (!defined $Clang || ! -x $Clang) { 1527 $Clang = Cwd::realpath("$RealBin/clang"); 1528 } 1529 if (!defined $Clang || ! -x $Clang) { 1530 if (!$RequestDisplayHelp && !$ForceDisplayHelp) { 1531 DieDiag("error: Cannot find an executable 'clang' relative to scan-build." . 1532 " Consider using --use-analyzer to pick a version of 'clang' to use for static analysis.\n"); 1533 } 1534 } 1535} 1536else { 1537 if ($AnalyzerDiscoveryMethod =~ /^[Xx]code$/) { 1538 my $xcrun = `which xcrun`; 1539 chomp $xcrun; 1540 if ($xcrun eq "") { 1541 DieDiag("Cannot find 'xcrun' to find 'clang' for analysis.\n"); 1542 } 1543 $Clang = `$xcrun -toolchain XcodeDefault -find clang`; 1544 chomp $Clang; 1545 if ($Clang eq "") { 1546 DieDiag("No 'clang' executable found by 'xcrun'\n"); 1547 } 1548 } 1549 else { 1550 $Clang = Cwd::realpath($AnalyzerDiscoveryMethod); 1551 if (!defined $Clang or not -x $Clang) { 1552 DieDiag("Cannot find an executable clang at '$AnalyzerDiscoveryMethod'\n"); 1553 } 1554 } 1555} 1556 1557if ($ForceDisplayHelp || $RequestDisplayHelp) { 1558 DisplayHelp(); 1559 exit $ForceDisplayHelp; 1560} 1561 1562$ClangCXX = $Clang; 1563$ClangCXX =~ s/\-\d+\.\d+$//; 1564$ClangCXX .= "++"; 1565# Make sure to use "" to handle paths with spaces. 1566$ClangVersion = HtmlEscape(`"$Clang" --version`); 1567 1568# Determine where results go. 1569$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV))); 1570$HtmlTitle = "${CurrentDirSuffix} - scan-build results" 1571 unless (defined($HtmlTitle)); 1572 1573# Determine the output directory for the HTML reports. 1574my $BaseDir = $HtmlDir; 1575$HtmlDir = GetHTMLRunDir($HtmlDir); 1576 1577# Determine the location of ccc-analyzer. 1578my $AbsRealBin = Cwd::realpath($RealBin); 1579my $Cmd = "$AbsRealBin/libexec/ccc-analyzer"; 1580my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer"; 1581 1582if (!defined $Cmd || ! -x $Cmd) { 1583 $Cmd = "$AbsRealBin/ccc-analyzer"; 1584 DieDiag("Executable 'ccc-analyzer' does not exist at '$Cmd'\n") if(! -x $Cmd); 1585} 1586if (!defined $CmdCXX || ! -x $CmdCXX) { 1587 $CmdCXX = "$AbsRealBin/c++-analyzer"; 1588 DieDiag("Executable 'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -x $CmdCXX); 1589} 1590 1591Diag("Using '$Clang' for static analysis\n"); 1592 1593SetHtmlEnv(\@ARGV, $HtmlDir); 1594if ($AnalyzeHeaders) { push @AnalysesToRun,"-analyzer-opt-analyze-headers"; } 1595if ($AnalyzerStats) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } 1596if ($MaxLoop > 0) { push @AnalysesToRun, "-analyzer-max-loop $MaxLoop"; } 1597 1598# Delay setting up other environment variables in case we can do true 1599# interposition. 1600my $CCC_ANALYZER_ANALYSIS = join ' ',@AnalysesToRun; 1601my $CCC_ANALYZER_PLUGINS = join ' ',@PluginsToLoad; 1602my %Options = ( 1603 'CC' => $Cmd, 1604 'CXX' => $CmdCXX, 1605 'CLANG' => $Clang, 1606 'CLANG_CXX' => $ClangCXX, 1607 'VERBOSE' => $Verbose, 1608 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS, 1609 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS, 1610 'OUTPUT_DIR' => $HtmlDir 1611); 1612 1613if (defined $StoreModel) { 1614 $Options{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel; 1615} 1616if (defined $ConstraintsModel) { 1617 $Options{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel; 1618} 1619if (defined $InternalStats) { 1620 $Options{'CCC_ANALYZER_INTERNAL_STATS'} = 1; 1621} 1622if (defined $OutputFormat) { 1623 $Options{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat; 1624} 1625 1626# Run the build. 1627my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd, $CmdCXX, 1628 \%Options); 1629 1630if (defined $OutputFormat) { 1631 if ($OutputFormat =~ /plist/) { 1632 Diag "Analysis run complete.\n"; 1633 Diag "Analysis results (plist files) deposited in '$HtmlDir'\n"; 1634 } 1635 if ($OutputFormat =~ /html/) { 1636 # Postprocess the HTML directory. 1637 my $NumBugs = Postprocess($HtmlDir, $BaseDir, $AnalyzerStats, $KeepEmpty); 1638 1639 if ($ViewResults and -r "$HtmlDir/index.html") { 1640 Diag "Analysis run complete.\n"; 1641 Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n"; 1642 my $ScanView = Cwd::realpath("$RealBin/scan-view"); 1643 if (! -x $ScanView) { $ScanView = "scan-view"; } 1644 exec $ScanView, "$HtmlDir"; 1645 } 1646 1647 if ($ExitStatusFoundBugs) { 1648 exit 1 if ($NumBugs > 0); 1649 exit 0; 1650 } 1651 } 1652} 1653 1654exit $ExitStatus; 1655 1656