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