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