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