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