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