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