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