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