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