scan-build revision fa15be4cf95b7ed2d1df583497b16a6f897cf789
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 eq '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 $CmdArgs; 40 41my $HtmlTitle; 42 43my $Date = localtime(); 44 45##----------------------------------------------------------------------------## 46# Diagnostics 47##----------------------------------------------------------------------------## 48 49sub Diag { 50 if ($UseColor) { 51 print BOLD, MAGENTA "$Prog: @_"; 52 print RESET; 53 } 54 else { 55 print "$Prog: @_"; 56 } 57} 58 59sub DiagCrashes { 60 my $Dir = shift; 61 Diag ("The analyzer encountered problems on some source files.\n"); 62 Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n"); 63 Diag ("Please consider submitting a bug report using these files:\n"); 64 Diag (" http://clang.llvm.org/StaticAnalysisUsage.html#filingbugs\n") 65} 66 67sub DieDiag { 68 if ($UseColor) { 69 print BOLD, RED "$Prog: "; 70 print RESET, RED @_; 71 print RESET; 72 } 73 else { 74 print "$Prog: ", @_; 75 } 76 exit(0); 77} 78 79##----------------------------------------------------------------------------## 80# Some initial preprocessing of Clang options. 81##----------------------------------------------------------------------------## 82 83# Find 'clang' 84my $ClangSB = Cwd::realpath("$RealBin/bin/clang"); 85my $ClangCXXSB; 86if (!defined $ClangSB || ! -x $ClangSB) { 87 $ClangSB = Cwd::realpath("$RealBin/clang"); 88 if (defined $ClangSB) { $ClangCXXSB = $ClangSB . "++"; } 89} 90my $Clang = $ClangSB; 91my $ClangCXX = $ClangCXXSB; 92# Default to looking for 'clang' in the path. 93if (!defined $Clang || ! -x $Clang) { 94 $Clang = "clang"; 95 $ClangCXX = "clang++"; 96} 97 98my %AvailableAnalyses; 99 100# Query clang for analysis options. 101open(PIPE, "-|", $Clang, "-cc1", "--help") or 102 DieDiag("Cannot execute '$Clang'\n"); 103 104my $FoundAnalysis = 0; 105 106while(<PIPE>) { 107 if ($FoundAnalysis == 0) { 108 if (/Checks and Analyses/) { 109 $FoundAnalysis = 1; 110 } 111 next; 112 } 113 if (/^\s\s\s\s([^\s]+)\s(.+)$/) { 114 next if ($1 =~ /-dump/ or $1 =~ /-view/ 115 or $1 =~ /-warn-uninit/); 116 $AvailableAnalyses{$1} = $2; 117 next; 118 } 119 last; 120} 121close (PIPE); 122 123my %AnalysesDefaultEnabled = ( 124 '-analyzer-check-dead-stores' => 1, 125 '-checker-cfref' => 1, 126 '-analyzer-check-objc-methodsigs' => 1, 127 # Do not enable the missing -dealloc check by default. 128 # '-warn-objc-missing-dealloc' => 1, 129 '-analyzer-check-objc-unused-ivars' => 1, 130 '-analyzer-check-security-syntactic' => 1 131); 132 133##----------------------------------------------------------------------------## 134# GetHTMLRunDir - Construct an HTML directory name for the current sub-run. 135##----------------------------------------------------------------------------## 136 137sub GetHTMLRunDir { 138 die "Not enough arguments." if (@_ == 0); 139 my $Dir = shift @_; 140 my $TmpMode = 0; 141 if (!defined $Dir) { 142 if (`uname` =~ /Darwin/) { 143 $Dir = $ENV{'TMPDIR'}; 144 if (!defined $Dir) { $Dir = "/tmp"; } 145 } 146 else { 147 $Dir = "/tmp"; 148 } 149 $TmpMode = 1; 150 } 151 152 # Chop off any trailing '/' characters. 153 while ($Dir =~ /\/$/) { chop $Dir; } 154 155 # Get current date and time. 156 my @CurrentTime = localtime(); 157 my $year = $CurrentTime[5] + 1900; 158 my $day = $CurrentTime[3]; 159 my $month = $CurrentTime[4] + 1; 160 my $DateString = sprintf("%d-%02d-%02d", $year, $month, $day); 161 162 # Determine the run number. 163 my $RunNumber; 164 165 if (-d $Dir) { 166 if (! -r $Dir) { 167 DieDiag("directory '$Dir' exists but is not readable.\n"); 168 } 169 # Iterate over all files in the specified directory. 170 my $max = 0; 171 opendir(DIR, $Dir); 172 my @FILES = grep { -d "$Dir/$_" } readdir(DIR); 173 closedir(DIR); 174 175 foreach my $f (@FILES) { 176 # Strip the prefix '$Prog-' if we are dumping files to /tmp. 177 if ($TmpMode) { 178 next if (!($f =~ /^$Prog-(.+)/)); 179 $f = $1; 180 } 181 182 my @x = split/-/, $f; 183 next if (scalar(@x) != 4); 184 next if ($x[0] != $year); 185 next if ($x[1] != $month); 186 next if ($x[2] != $day); 187 188 if ($x[3] > $max) { 189 $max = $x[3]; 190 } 191 } 192 193 $RunNumber = $max + 1; 194 } 195 else { 196 197 if (-x $Dir) { 198 DieDiag("'$Dir' exists but is not a directory.\n"); 199 } 200 201 if ($TmpMode) { 202 DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n"); 203 } 204 205 # $Dir does not exist. It will be automatically created by the 206 # clang driver. Set the run number to 1. 207 208 $RunNumber = 1; 209 } 210 211 die "RunNumber must be defined!" if (!defined $RunNumber); 212 213 # Append the run number. 214 my $NewDir; 215 if ($TmpMode) { 216 $NewDir = "$Dir/$Prog-$DateString-$RunNumber"; 217 } 218 else { 219 $NewDir = "$Dir/$DateString-$RunNumber"; 220 } 221 system 'mkdir','-p',$NewDir; 222 return $NewDir; 223} 224 225sub SetHtmlEnv { 226 227 die "Wrong number of arguments." if (scalar(@_) != 2); 228 229 my $Args = shift; 230 my $Dir = shift; 231 232 die "No build command." if (scalar(@$Args) == 0); 233 234 my $Cmd = $$Args[0]; 235 236 if ($Cmd =~ /configure/) { 237 return; 238 } 239 240 if ($Verbose) { 241 Diag("Emitting reports for this run to '$Dir'.\n"); 242 } 243 244 $ENV{'CCC_ANALYZER_HTML'} = $Dir; 245} 246 247##----------------------------------------------------------------------------## 248# ComputeDigest - Compute a digest of the specified file. 249##----------------------------------------------------------------------------## 250 251sub ComputeDigest { 252 my $FName = shift; 253 DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName); 254 255 # Use Digest::MD5. We don't have to be cryptographically secure. We're 256 # just looking for duplicate files that come from a non-malicious source. 257 # We use Digest::MD5 because it is a standard Perl module that should 258 # come bundled on most systems. 259 open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n"); 260 binmode FILE; 261 my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest; 262 close(FILE); 263 264 # Return the digest. 265 return $Result; 266} 267 268##----------------------------------------------------------------------------## 269# UpdatePrefix - Compute the common prefix of files. 270##----------------------------------------------------------------------------## 271 272my $Prefix; 273 274sub UpdatePrefix { 275 my $x = shift; 276 my $y = basename($x); 277 $x =~ s/\Q$y\E$//; 278 279 if (!defined $Prefix) { 280 $Prefix = $x; 281 return; 282 } 283 284 chop $Prefix while (!($x =~ /^\Q$Prefix/)); 285} 286 287sub GetPrefix { 288 return $Prefix; 289} 290 291##----------------------------------------------------------------------------## 292# UpdateInFilePath - Update the path in the report file. 293##----------------------------------------------------------------------------## 294 295sub UpdateInFilePath { 296 my $fname = shift; 297 my $regex = shift; 298 my $newtext = shift; 299 300 open (RIN, $fname) or die "cannot open $fname"; 301 open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp"; 302 303 while (<RIN>) { 304 s/$regex/$newtext/; 305 print ROUT $_; 306 } 307 308 close (ROUT); 309 close (RIN); 310 system("mv", "$fname.tmp", $fname); 311} 312 313##----------------------------------------------------------------------------## 314# ScanFile - Scan a report file for various identifying attributes. 315##----------------------------------------------------------------------------## 316 317# Sometimes a source file is scanned more than once, and thus produces 318# multiple error reports. We use a cache to solve this problem. 319 320my %AlreadyScanned; 321 322sub ScanFile { 323 324 my $Index = shift; 325 my $Dir = shift; 326 my $FName = shift; 327 328 # Compute a digest for the report file. Determine if we have already 329 # scanned a file that looks just like it. 330 331 my $digest = ComputeDigest("$Dir/$FName"); 332 333 if (defined $AlreadyScanned{$digest}) { 334 # Redundant file. Remove it. 335 system ("rm", "-f", "$Dir/$FName"); 336 return; 337 } 338 339 $AlreadyScanned{$digest} = 1; 340 341 # At this point the report file is not world readable. Make it happen. 342 system ("chmod", "644", "$Dir/$FName"); 343 344 # Scan the report file for tags. 345 open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n"); 346 347 my $BugType = ""; 348 my $BugFile = ""; 349 my $BugCategory; 350 my $BugPathLength = 1; 351 my $BugLine = 0; 352 353 while (<IN>) { 354 last if (/<!-- BUGMETAEND -->/); 355 356 if (/<!-- BUGTYPE (.*) -->$/) { 357 $BugType = $1; 358 } 359 elsif (/<!-- BUGFILE (.*) -->$/) { 360 $BugFile = abs_path($1); 361 UpdatePrefix($BugFile); 362 } 363 elsif (/<!-- BUGPATHLENGTH (.*) -->$/) { 364 $BugPathLength = $1; 365 } 366 elsif (/<!-- BUGLINE (.*) -->$/) { 367 $BugLine = $1; 368 } 369 elsif (/<!-- BUGCATEGORY (.*) -->$/) { 370 $BugCategory = $1; 371 } 372 } 373 374 close(IN); 375 376 if (!defined $BugCategory) { 377 $BugCategory = "Other"; 378 } 379 380 push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugLine, 381 $BugPathLength ]; 382} 383 384##----------------------------------------------------------------------------## 385# CopyFiles - Copy resource files to target directory. 386##----------------------------------------------------------------------------## 387 388sub CopyFiles { 389 390 my $Dir = shift; 391 392 my $JS = Cwd::realpath("$RealBin/sorttable.js"); 393 394 DieDiag("Cannot find 'sorttable.js'.\n") 395 if (! -r $JS); 396 397 system ("cp", $JS, "$Dir"); 398 399 DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n") 400 if (! -r "$Dir/sorttable.js"); 401 402 my $CSS = Cwd::realpath("$RealBin/scanview.css"); 403 404 DieDiag("Cannot find 'scanview.css'.\n") 405 if (! -r $CSS); 406 407 system ("cp", $CSS, "$Dir"); 408 409 DieDiag("Could not copy 'scanview.css' to '$Dir'.\n") 410 if (! -r $CSS); 411} 412 413##----------------------------------------------------------------------------## 414# Postprocess - Postprocess the results of an analysis scan. 415##----------------------------------------------------------------------------## 416 417sub Postprocess { 418 419 my $Dir = shift; 420 my $BaseDir = shift; 421 422 die "No directory specified." if (!defined $Dir); 423 424 if (! -d $Dir) { 425 Diag("No bugs found.\n"); 426 return 0; 427 } 428 429 opendir(DIR, $Dir); 430 my @files = grep { /^report-.*\.html$/ } readdir(DIR); 431 closedir(DIR); 432 433 if (scalar(@files) == 0 and ! -e "$Dir/failures") { 434 Diag("Removing directory '$Dir' because it contains no reports.\n"); 435 system ("rm", "-fR", $Dir); 436 return 0; 437 } 438 439 # Scan each report file and build an index. 440 my @Index; 441 foreach my $file (@files) { ScanFile(\@Index, $Dir, $file); } 442 443 # Scan the failures directory and use the information in the .info files 444 # to update the common prefix directory. 445 my @failures; 446 my @attributes_ignored; 447 if (-d "$Dir/failures") { 448 opendir(DIR, "$Dir/failures"); 449 @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR); 450 closedir(DIR); 451 opendir(DIR, "$Dir/failures"); 452 @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR); 453 closedir(DIR); 454 foreach my $file (@failures) { 455 open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n"); 456 my $Path = <IN>; 457 if (defined $Path) { UpdatePrefix($Path); } 458 close IN; 459 } 460 } 461 462 # Generate an index.html file. 463 my $FName = "$Dir/index.html"; 464 open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n"); 465 466 # Print out the header. 467 468print OUT <<ENDTEXT; 469<html> 470<head> 471<title>${HtmlTitle}</title> 472<link type="text/css" rel="stylesheet" href="scanview.css"/> 473<script src="sorttable.js"></script> 474<script language='javascript' type="text/javascript"> 475function SetDisplay(RowClass, DisplayVal) 476{ 477 var Rows = document.getElementsByTagName("tr"); 478 for ( var i = 0 ; i < Rows.length; ++i ) { 479 if (Rows[i].className == RowClass) { 480 Rows[i].style.display = DisplayVal; 481 } 482 } 483} 484 485function CopyCheckedStateToCheckButtons(SummaryCheckButton) { 486 var Inputs = document.getElementsByTagName("input"); 487 for ( var i = 0 ; i < Inputs.length; ++i ) { 488 if (Inputs[i].type == "checkbox") { 489 if(Inputs[i] != SummaryCheckButton) { 490 Inputs[i].checked = SummaryCheckButton.checked; 491 Inputs[i].onclick(); 492 } 493 } 494 } 495} 496 497function returnObjById( id ) { 498 if (document.getElementById) 499 var returnVar = document.getElementById(id); 500 else if (document.all) 501 var returnVar = document.all[id]; 502 else if (document.layers) 503 var returnVar = document.layers[id]; 504 return returnVar; 505} 506 507var NumUnchecked = 0; 508 509function ToggleDisplay(CheckButton, ClassName) { 510 if (CheckButton.checked) { 511 SetDisplay(ClassName, ""); 512 if (--NumUnchecked == 0) { 513 returnObjById("AllBugsCheck").checked = true; 514 } 515 } 516 else { 517 SetDisplay(ClassName, "none"); 518 NumUnchecked++; 519 returnObjById("AllBugsCheck").checked = false; 520 } 521} 522</script> 523<!-- SUMMARYENDHEAD --> 524</head> 525<body> 526<h1>${HtmlTitle}</h1> 527 528<table> 529<tr><th>User:</th><td>${UserName}\@${HostName}</td></tr> 530<tr><th>Working Directory:</th><td>${CurrentDir}</td></tr> 531<tr><th>Command Line:</th><td>${CmdArgs}</td></tr> 532<tr><th>Date:</th><td>${Date}</td></tr> 533ENDTEXT 534 535print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n" 536 if (defined($BuildName) && defined($BuildDate)); 537 538print OUT <<ENDTEXT; 539</table> 540ENDTEXT 541 542 if (scalar(@files)) { 543 # Print out the summary table. 544 my %Totals; 545 546 for my $row ( @Index ) { 547 my $bug_type = ($row->[2]); 548 my $bug_category = ($row->[1]); 549 my $key = "$bug_category:$bug_type"; 550 551 if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; } 552 else { $Totals{$key}->[0]++; } 553 } 554 555 print OUT "<h2>Bug Summary</h2>"; 556 557 if (defined $BuildName) { 558 print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n" 559 } 560 561 my $TotalBugs = scalar(@Index); 562print OUT <<ENDTEXT; 563<table> 564<thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead> 565<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> 566ENDTEXT 567 568 my $last_category; 569 570 for my $key ( 571 sort { 572 my $x = $Totals{$a}; 573 my $y = $Totals{$b}; 574 my $res = $x->[1] cmp $y->[1]; 575 $res = $x->[2] cmp $y->[2] if ($res == 0); 576 $res 577 } keys %Totals ) 578 { 579 my $val = $Totals{$key}; 580 my $category = $val->[1]; 581 if (!defined $last_category or $last_category ne $category) { 582 $last_category = $category; 583 print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n"; 584 } 585 my $x = lc $key; 586 $x =~ s/[ ,'":\/()]+/_/g; 587 print OUT "<tr><td class=\"SUMM_DESC\">"; 588 print OUT $val->[2]; 589 print OUT "</td><td class=\"Q\">"; 590 print OUT $val->[0]; 591 print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n"; 592 } 593 594 # Print out the table of errors. 595 596print OUT <<ENDTEXT; 597</table> 598<h2>Reports</h2> 599 600<table class="sortable" style="table-layout:automatic"> 601<thead><tr> 602 <td>Bug Group</td> 603 <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind"> ▾</span></td> 604 <td>File</td> 605 <td class="Q">Line</td> 606 <td class="Q">Path Length</td> 607 <td class="sorttable_nosort"></td> 608 <!-- REPORTBUGCOL --> 609</tr></thead> 610<tbody> 611ENDTEXT 612 613 my $prefix = GetPrefix(); 614 my $regex; 615 my $InFileRegex; 616 my $InFilePrefix = "File:</td><td>"; 617 618 if (defined $prefix) { 619 $regex = qr/^\Q$prefix\E/is; 620 $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is; 621 } 622 623 for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) { 624 my $x = "$row->[1]:$row->[2]"; 625 $x = lc $x; 626 $x =~ s/[ ,'":\/()]+/_/g; 627 628 my $ReportFile = $row->[0]; 629 630 print OUT "<tr class=\"bt_$x\">"; 631 print OUT "<td class=\"DESC\">"; 632 print OUT $row->[1]; 633 print OUT "</td>"; 634 print OUT "<td class=\"DESC\">"; 635 print OUT $row->[2]; 636 print OUT "</td>"; 637 638 # Update the file prefix. 639 my $fname = $row->[3]; 640 641 if (defined $regex) { 642 $fname =~ s/$regex//; 643 UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix) 644 } 645 646 print OUT "<td>"; 647 my @fname = split /\//,$fname; 648 if ($#fname > 0) { 649 while ($#fname >= 0) { 650 my $x = shift @fname; 651 print OUT $x; 652 if ($#fname >= 0) { 653 print OUT "<span class=\"W\"> </span>/"; 654 } 655 } 656 } 657 else { 658 print OUT $fname; 659 } 660 print OUT "</td>"; 661 662 # Print out the quantities. 663 for my $j ( 4 .. 5 ) { 664 print OUT "<td class=\"Q\">$row->[$j]</td>"; 665 } 666 667 # Print the rest of the columns. 668 for (my $j = 6; $j <= $#{$row}; ++$j) { 669 print OUT "<td>$row->[$j]</td>" 670 } 671 672 # Emit the "View" link. 673 print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>"; 674 675 # Emit REPORTBUG markers. 676 print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n"; 677 678 # End the row. 679 print OUT "</tr>\n"; 680 } 681 682 print OUT "</tbody>\n</table>\n\n"; 683 } 684 685 if (scalar (@failures) || scalar(@attributes_ignored)) { 686 print OUT "<h2>Analyzer Failures</h2>\n"; 687 688 if (scalar @attributes_ignored) { 689 print OUT "The analyzer's parser ignored the following attributes:<p>\n"; 690 print OUT "<table>\n"; 691 print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; 692 foreach my $file (sort @attributes_ignored) { 693 die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/)); 694 my $attribute = $1; 695 # Open the attribute file to get the first file that failed. 696 next if (!open (ATTR, "$Dir/failures/$file")); 697 my $ppfile = <ATTR>; 698 chomp $ppfile; 699 close ATTR; 700 next if (! -e "$Dir/failures/$ppfile"); 701 # Open the info file and get the name of the source file. 702 open (INFO, "$Dir/failures/$ppfile.info.txt") or 703 die "Cannot open $Dir/failures/$ppfile.info.txt\n"; 704 my $srcfile = <INFO>; 705 chomp $srcfile; 706 close (INFO); 707 # Print the information in the table. 708 my $prefix = GetPrefix(); 709 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } 710 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"; 711 my $ppfile_clang = $ppfile; 712 $ppfile_clang =~ s/[.](.+)$/.clang.$1/; 713 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; 714 } 715 print OUT "</table>\n"; 716 } 717 718 if (scalar @failures) { 719 print OUT "<p>The analyzer had problems processing the following files:</p>\n"; 720 print OUT "<table>\n"; 721 print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; 722 foreach my $file (sort @failures) { 723 $file =~ /(.+).info.txt$/; 724 # Get the preprocessed file. 725 my $ppfile = $1; 726 # Open the info file and get the name of the source file. 727 open (INFO, "$Dir/failures/$file") or 728 die "Cannot open $Dir/failures/$file\n"; 729 my $srcfile = <INFO>; 730 chomp $srcfile; 731 my $problem = <INFO>; 732 chomp $problem; 733 close (INFO); 734 # Print the information in the table. 735 my $prefix = GetPrefix(); 736 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } 737 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"; 738 my $ppfile_clang = $ppfile; 739 $ppfile_clang =~ s/[.](.+)$/.clang.$1/; 740 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; 741 } 742 print OUT "</table>\n"; 743 } 744 print OUT "<p>Please consider submitting preprocessed files as <a href=\"http://clang.llvm.org/StaticAnalysisUsage.html#filingbugs\">bug reports</a>. <!-- REPORTCRASHES --> </p>\n"; 745 } 746 747 print OUT "</body></html>\n"; 748 close(OUT); 749 CopyFiles($Dir); 750 751 # Make sure $Dir and $BaseDir are world readable/executable. 752 system("chmod", "755", $Dir); 753 if (defined $BaseDir) { system("chmod", "755", $BaseDir); } 754 755 my $Num = scalar(@Index); 756 Diag("$Num bugs found.\n"); 757 if ($Num > 0 && -r "$Dir/index.html") { 758 Diag("Run 'scan-view $Dir' to examine bug reports.\n"); 759 } 760 761 DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored); 762 763 return $Num; 764} 765 766##----------------------------------------------------------------------------## 767# RunBuildCommand - Run the build command. 768##----------------------------------------------------------------------------## 769 770sub AddIfNotPresent { 771 my $Args = shift; 772 my $Arg = shift; 773 my $found = 0; 774 775 foreach my $k (@$Args) { 776 if ($k eq $Arg) { 777 $found = 1; 778 last; 779 } 780 } 781 782 if ($found == 0) { 783 push @$Args, $Arg; 784 } 785} 786 787sub RunBuildCommand { 788 789 my $Args = shift; 790 my $IgnoreErrors = shift; 791 my $Cmd = $Args->[0]; 792 my $CCAnalyzer = shift; 793 794 # Get only the part of the command after the last '/'. 795 if ($Cmd =~ /\/([^\/]+)$/) { 796 $Cmd = $1; 797 } 798 799 if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 800 $Cmd =~ /(.*\/?cc[^\/]*$)/ or 801 $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or 802 $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) { 803 804 if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) { 805 $ENV{"CCC_CC"} = $1; 806 } 807 808 shift @$Args; 809 unshift @$Args, $CCAnalyzer; 810 } 811 elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or 812 $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or 813 $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or 814 $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) { 815 if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) { 816 $ENV{"CCC_CXX"} = $1; 817 } 818 shift @$Args; 819 unshift @$Args, $CCAnalyzer; 820 } 821 elsif ($IgnoreErrors) { 822 if ($Cmd eq "make" or $Cmd eq "gmake") { 823 AddIfNotPresent($Args, "CC=$CCAnalyzer"); 824 AddIfNotPresent($Args, "CXX=$CCAnalyzer"); 825 AddIfNotPresent($Args,"-k"); 826 AddIfNotPresent($Args,"-i"); 827 } 828 elsif ($Cmd eq "xcodebuild") { 829 AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES"); 830 } 831 } 832 833 if ($Cmd eq "xcodebuild") { 834 # Check if using iPhone SDK 3.0 (simulator). If so the compiler being 835 # used should be gcc-4.2. 836 if (!defined $ENV{"CCC_CC"}) { 837 for (my $i = 0 ; $i < scalar(@$Args); ++$i) { 838 if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) { 839 if (@$Args[$i+1] =~ /^iphonesimulator3/) { 840 $ENV{"CCC_CC"} = "gcc-4.2"; 841 $ENV{"CCC_CXX"} = "g++-4.2"; 842 } 843 } 844 } 845 } 846 847 # Disable distributed builds for xcodebuild. 848 AddIfNotPresent($Args,"-nodistribute"); 849 850 # Disable PCH files until clang supports them. 851 AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO"); 852 853 # When 'CC' is set, xcodebuild uses it to do all linking, even if we are 854 # linking C++ object files. Set 'LDPLUSPLUS' so that xcodebuild uses 'g++' 855 # when linking such files. 856 if (!defined $ENV{'CCC_CXX'}) { 857 $ENV{'CCC_CXX'} = 'g++'; 858 } 859 $ENV{'LDPLUSPLUS'} = $ENV{'CCC_CXX'}; 860 } 861 862 return (system(@$Args) >> 8); 863} 864 865##----------------------------------------------------------------------------## 866# DisplayHelp - Utility function to display all help options. 867##----------------------------------------------------------------------------## 868 869sub DisplayHelp { 870 871print <<ENDTEXT; 872USAGE: $Prog [options] <build command> [build options] 873 874ENDTEXT 875 876 if (defined $BuildName) { 877 print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n"; 878 } 879 880print <<ENDTEXT; 881OPTIONS: 882 883 -analyze-headers - Also analyze functions in #included files. 884 885 --experimental-checks - Enable experimental checks that are currently in heavy testing 886 887 -o - Target directory for HTML report files. Subdirectories 888 will be created as needed to represent separate "runs" of 889 the analyzer. If this option is not specified, a directory 890 is created in /tmp (TMPDIR on Mac OS X) to store the reports. 891 892 -h - Display this message. 893 --help 894 895 -k - Add a "keep on going" option to the specified build command. 896 --keep-going This option currently supports make and xcodebuild. 897 This is a convenience option; one can specify this 898 behavior directly using build options. 899 900 --html-title [title] - Specify the title used on generated HTML pages. 901 --html-title=[title] If not specified, a default title will be used. 902 903 -plist - By default the output of scan-build is a set of HTML files. 904 This option outputs the results as a set of .plist files. 905 906 --status-bugs - By default, the exit status of $Prog is the same as the 907 executed build command. Specifying this option causes the 908 exit status of $Prog to be 1 if it found potential bugs 909 and 0 otherwise. 910 911 --use-cc [compiler path] - By default, $Prog uses 'gcc' to compile and link 912 --use-cc=[compiler path] your C and Objective-C code. Use this option 913 to specify an alternate compiler. 914 915 --use-c++ [compiler path] - By default, $Prog uses 'g++' to compile and link 916 --use-c++=[compiler path] your C++ and Objective-C++ code. Use this option 917 to specify an alternate compiler. 918 919 -v - Verbose output from $Prog and the analyzer. 920 A second and third '-v' increases verbosity. 921 922 -V - View analysis results in a web browser when the build 923 --view completes. 924 925ADVANCED OPTIONS: 926 927 -constraints [model] - Specify the contraint engine used by the analyzer. 928 By default the 'range' model is used. Specifying 929 'basic' uses a simpler, less powerful constraint model 930 used by checker-0.160 and earlier. 931 932 -store [model] - Specify the store model used by the analyzer. By default, 933 the 'region' store model is used. 'region' specifies a field- 934 sensitive store model. Users can also specify 'basic', which 935 is far less precise but can more quickly analyze code. 936 'basic' was the default store model for checker-0.221 and 937 earlier. 938 939 -no-failure-reports - Do not create a 'failures' subdirectory that includes 940 analyzer crash reports and preprocessed source files. 941 942AVAILABLE ANALYSES (multiple analyses may be specified): 943 944ENDTEXT 945 946 foreach my $Analysis (sort keys %AvailableAnalyses) { 947 if (defined $AnalysesDefaultEnabled{$Analysis}) { 948 print " (+)"; 949 } 950 else { 951 print " "; 952 } 953 954 print " $Analysis $AvailableAnalyses{$Analysis}\n"; 955 } 956 957print <<ENDTEXT 958 959 NOTE: "(+)" indicates that an analysis is enabled by default unless one 960 or more analysis options are specified 961 962BUILD OPTIONS 963 964 You can specify any build option acceptable to the build command. 965 966EXAMPLE 967 968 $Prog -o /tmp/myhtmldir make -j4 969 970 The above example causes analysis reports to be deposited into 971 a subdirectory of "/tmp/myhtmldir" and to run "make" with the "-j4" option. 972 A different subdirectory is created each time $Prog analyzes a project. 973 The analyzer should support most parallel builds, but not distributed builds. 974 975ENDTEXT 976} 977 978##----------------------------------------------------------------------------## 979# HtmlEscape - HTML entity encode characters that are special in HTML 980##----------------------------------------------------------------------------## 981 982sub HtmlEscape { 983 # copy argument to new variable so we don't clobber the original 984 my $arg = shift || ''; 985 my $tmp = $arg; 986 $tmp =~ s/&/&/g; 987 $tmp =~ s/</</g; 988 $tmp =~ s/>/>/g; 989 return $tmp; 990} 991 992##----------------------------------------------------------------------------## 993# ShellEscape - backslash escape characters that are special to the shell 994##----------------------------------------------------------------------------## 995 996sub ShellEscape { 997 # copy argument to new variable so we don't clobber the original 998 my $arg = shift || ''; 999 if ($arg =~ /["\s]/) { return "'" . $arg . "'"; } 1000 return $arg; 1001} 1002 1003##----------------------------------------------------------------------------## 1004# Process command-line arguments. 1005##----------------------------------------------------------------------------## 1006 1007my $AnalyzeHeaders = 0; 1008my $HtmlDir; # Parent directory to store HTML files. 1009my $IgnoreErrors = 0; # Ignore build errors. 1010my $ViewResults = 0; # View results when the build terminates. 1011my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found 1012my @AnalysesToRun; 1013my $StoreModel; 1014my $ConstraintsModel; 1015my $OutputFormat = "html"; 1016 1017if (!@ARGV) { 1018 DisplayHelp(); 1019 exit 1; 1020} 1021 1022while (@ARGV) { 1023 1024 # Scan for options we recognize. 1025 1026 my $arg = $ARGV[0]; 1027 1028 if ($arg eq "-h" or $arg eq "--help") { 1029 DisplayHelp(); 1030 exit 0; 1031 } 1032 1033 if ($arg eq '-analyze-headers') { 1034 shift @ARGV; 1035 $AnalyzeHeaders = 1; 1036 next; 1037 } 1038 1039 if (defined $AvailableAnalyses{$arg}) { 1040 shift @ARGV; 1041 push @AnalysesToRun, $arg; 1042 next; 1043 } 1044 1045 if ($arg eq "-o") { 1046 shift @ARGV; 1047 1048 if (!@ARGV) { 1049 DieDiag("'-o' option requires a target directory name.\n"); 1050 } 1051 1052 # Construct an absolute path. Uses the current working directory 1053 # as a base if the original path was not absolute. 1054 $HtmlDir = abs_path(shift @ARGV); 1055 1056 next; 1057 } 1058 1059 if ($arg =~ /^--html-title(=(.+))?$/) { 1060 shift @ARGV; 1061 1062 if (!defined $2 || $2 eq '') { 1063 if (!@ARGV) { 1064 DieDiag("'--html-title' option requires a string.\n"); 1065 } 1066 1067 $HtmlTitle = shift @ARGV; 1068 } else { 1069 $HtmlTitle = $2; 1070 } 1071 1072 next; 1073 } 1074 1075 if ($arg eq "-k" or $arg eq "--keep-going") { 1076 shift @ARGV; 1077 $IgnoreErrors = 1; 1078 next; 1079 } 1080 1081 if ($arg eq "--experimental-checks") { 1082 shift @ARGV; 1083 $ENV{"CCC_EXPERIMENTAL_CHECKS"} = 1; 1084 next; 1085 } 1086 1087 if ($arg =~ /^--use-cc(=(.+))?$/) { 1088 shift @ARGV; 1089 my $cc; 1090 1091 if (!defined $2 || $2 eq "") { 1092 if (!@ARGV) { 1093 DieDiag("'--use-cc' option requires a compiler executable name.\n"); 1094 } 1095 $cc = shift @ARGV; 1096 } 1097 else { 1098 $cc = $2; 1099 } 1100 1101 $ENV{"CCC_CC"} = $cc; 1102 next; 1103 } 1104 1105 if ($arg =~ /^--use-c\+\+(=(.+))?$/) { 1106 shift @ARGV; 1107 my $cxx; 1108 1109 if (!defined $2 || $2 eq "") { 1110 if (!@ARGV) { 1111 DieDiag("'--use-c++' option requires a compiler executable name.\n"); 1112 } 1113 $cxx = shift @ARGV; 1114 } 1115 else { 1116 $cxx = $2; 1117 } 1118 1119 $ENV{"CCC_CXX"} = $cxx; 1120 next; 1121 } 1122 1123 if ($arg eq "-v") { 1124 shift @ARGV; 1125 $Verbose++; 1126 next; 1127 } 1128 1129 if ($arg eq "-V" or $arg eq "--view") { 1130 shift @ARGV; 1131 $ViewResults = 1; 1132 next; 1133 } 1134 1135 if ($arg eq "--status-bugs") { 1136 shift @ARGV; 1137 $ExitStatusFoundBugs = 1; 1138 next; 1139 } 1140 1141 if ($arg eq "-store") { 1142 shift @ARGV; 1143 $StoreModel = shift @ARGV; 1144 next; 1145 } 1146 1147 if ($arg eq "-constraints") { 1148 shift @ARGV; 1149 $ConstraintsModel = shift @ARGV; 1150 next; 1151 } 1152 1153 if ($arg eq "-plist") { 1154 shift @ARGV; 1155 $OutputFormat = "plist"; 1156 next; 1157 } 1158 if ($arg eq "-plist-html") { 1159 shift @ARGV; 1160 $OutputFormat = "plist-html"; 1161 next; 1162 } 1163 1164 if ($arg eq "-no-failure-reports") { 1165 $ENV{"CCC_REPORT_FAILURES"} = 0; 1166 next; 1167 } 1168 1169 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); 1170 1171 last; 1172} 1173 1174if (!@ARGV) { 1175 Diag("No build command specified.\n\n"); 1176 DisplayHelp(); 1177 exit 1; 1178} 1179 1180$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV))); 1181$HtmlTitle = "${CurrentDirSuffix} - scan-build results" 1182 unless (defined($HtmlTitle)); 1183 1184# Determine the output directory for the HTML reports. 1185my $BaseDir = $HtmlDir; 1186$HtmlDir = GetHTMLRunDir($HtmlDir); 1187 1188# Set the appropriate environment variables. 1189SetHtmlEnv(\@ARGV, $HtmlDir); 1190 1191my $AbsRealBin = Cwd::realpath($RealBin); 1192my $Cmd = "$AbsRealBin/libexec/ccc-analyzer"; 1193my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer"; 1194 1195if (!defined $Cmd || ! -x $Cmd) { 1196 $Cmd = "$AbsRealBin/ccc-analyzer"; 1197 DieDiag("Executable 'ccc-analyzer' does not exist at '$Cmd'\n") if(! -x $Cmd); 1198} 1199if (!defined $CmdCXX || ! -x $CmdCXX) { 1200 $CmdCXX = "$AbsRealBin/c++-analyzer"; 1201 DieDiag("Executable 'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -x $CmdCXX); 1202} 1203 1204if (!defined $ClangSB || ! -x $ClangSB) { 1205 Diag("'clang' executable not found in '$RealBin/bin'.\n"); 1206 Diag("Using 'clang' from path.\n"); 1207} 1208 1209$ENV{'CC'} = $Cmd; 1210$ENV{'CXX'} = $CmdCXX; 1211$ENV{'CLANG'} = $Clang; 1212$ENV{'CLANG_CXX'} = $ClangCXX; 1213 1214if ($Verbose >= 2) { 1215 $ENV{'CCC_ANALYZER_VERBOSE'} = 1; 1216} 1217 1218if ($Verbose >= 3) { 1219 $ENV{'CCC_ANALYZER_LOG'} = 1; 1220} 1221 1222if (scalar(@AnalysesToRun) == 0) { 1223 foreach my $key (keys %AnalysesDefaultEnabled) { 1224 push @AnalysesToRun,$key; 1225 } 1226} 1227 1228if ($AnalyzeHeaders) { 1229 push @AnalysesToRun,"-analyzer-opt-analyze-headers"; 1230} 1231 1232$ENV{'CCC_ANALYZER_ANALYSIS'} = join ' ',@AnalysesToRun; 1233 1234if (defined $StoreModel) { 1235 $ENV{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel; 1236} 1237 1238if (defined $ConstraintsModel) { 1239 $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel; 1240} 1241 1242if (defined $OutputFormat) { 1243 $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat; 1244} 1245 1246 1247# Run the build. 1248my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd); 1249 1250if (defined $OutputFormat) { 1251 if ($OutputFormat =~ /plist/) { 1252 Diag "Analysis run complete.\n"; 1253 Diag "Analysis results (plist files) deposited in '$HtmlDir'\n"; 1254 } 1255 elsif ($OutputFormat =~ /html/) { 1256 # Postprocess the HTML directory. 1257 my $NumBugs = Postprocess($HtmlDir, $BaseDir); 1258 1259 if ($ViewResults and -r "$HtmlDir/index.html") { 1260 Diag "Analysis run complete.\n"; 1261 Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n"; 1262 my $ScanView = Cwd::realpath("$RealBin/scan-view"); 1263 if (! -x $ScanView) { $ScanView = "scan-view"; } 1264 exec $ScanView, "$HtmlDir"; 1265 } 1266 1267 if ($ExitStatusFoundBugs) { 1268 exit 1 if ($NumBugs > 0); 1269 exit 0; 1270 } 1271 } 1272} 1273 1274exit $ExitStatus; 1275 1276