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