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