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