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