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 -plist-html - By default the output of scan-build is a set of HTML files. 966 This option outputs the results as a set of HTML 967 and .plist files. 968 969 --status-bugs - By default, the exit status of $Prog is the same as the 970 executed build command. Specifying this option causes the 971 exit status of $Prog to be 1 if it found potential bugs 972 and 0 otherwise. 973 974 --use-cc [compiler path] - $Prog attempts to guess the default compiler for 975 --use-cc=[compiler path] your C and Objective-C code. Use this option 976 to specify an alternate compiler. 977 978 --use-c++ [compiler path] - $Prog attempts to guess the default compiler for 979 --use-c++=[compiler path] your C++ and Objective-C++ code. Use this option 980 to specify an alternate compiler. 981 982 -v - Verbose output from $Prog and the analyzer. 983 A second and third '-v' increases verbosity. 984 985 -V - View analysis results in a web browser when the build 986 --view completes. 987 988 ADVANCED OPTIONS: 989 990 -constraints [model] - Specify the contraint engine used by the analyzer. 991 By default the 'range' model is used. Specifying 992 'basic' uses a simpler, less powerful constraint model 993 used by checker-0.160 and earlier. 994 995 -store [model] - Specify the store model used by the analyzer. By default, 996 the 'region' store model is used. 'region' specifies a field- 997 sensitive store model. Users can also specify 'basic', which 998 is far less precise but can more quickly analyze code. 999 'basic' was the default store model for checker-0.221 and 1000 earlier. 1001 1002 -no-failure-reports - Do not create a 'failures' subdirectory that includes 1003 analyzer crash reports and preprocessed source files. 1004 1005 -stats - Generates visitation statistics for the project being analyzed. 1006 1007 -maxloop N - specifiy the number of times a block can be visited before giving 1008 up. Default is 4. Increase for more comprehensive coverage at a 1009 cost of speed. 1010 1011 CONTROLLING CHECKERS: 1012 1013 A default group of checkers are always run unless explicitly disabled. 1014 Checkers may be enabled/disabled using the following options: 1015 1016 -enable-checker [checker name] 1017 -disable-checker [checker name] 1018 ENDTEXT 1019 1020 # Query clang for list of checkers that are enabled. 1021 my %EnabledCheckers; 1022 foreach my $lang ("c", "objective-c", "objective-c++", "c++") { 1023 pipe(FROM_CHILD, TO_PARENT); 1024 my $pid = fork(); 1025 if ($pid == 0) { 1026 close FROM_CHILD; 1027 open(STDOUT,">&", \*TO_PARENT); 1028 open(STDERR,">&", \*TO_PARENT); 1029 exec $Clang, ('--analyze', '-x', $lang, '-', '-###'); 1030 } 1031 close(TO_PARENT); 1032 while(<FROM_CHILD>) { 1033 foreach my $val (split /\s+/) { 1034 $val =~ s/\"//g; 1035 if ($val =~ /-analyzer-checker\=([^\s]+)/) { 1036 $EnabledCheckers{$1} = 1; 1037 } 1038 } 1039 } 1040 waitpid($pid,0); 1041 close(FROM_CHILD); 1042 } 1043 1044 # Query clang for complete list of checkers. 1045 pipe(FROM_CHILD, TO_PARENT); 1046 my $pid = fork(); 1047 if ($pid == 0) { 1048 close FROM_CHILD; 1049 open(STDOUT,">&", \*TO_PARENT); 1050 open(STDERR,">&", \*TO_PARENT); 1051 exec $Clang, ('-cc1', '-analyzer-checker-help'); 1052 } 1053 close(TO_PARENT); 1054 my $foundCheckers = 0; 1055 while(<FROM_CHILD>) { 1056 if (/CHECKERS:/) { 1057 $foundCheckers = 1; 1058 last; 1059 } 1060 } 1061 if (!$foundCheckers) { 1062 print " *** Could not query Clang for the list of available checkers."; 1063 } 1064 else { 1065 print("\nAVAILABLE CHECKERS:\n\n"); 1066 my $skip = 0; 1067 while(<FROM_CHILD>) { 1068 if (/experimental/) { 1069 $skip = 1; 1070 next; 1071 } 1072 if ($skip) { 1073 next if (!/^\s\s[^\s]/); 1074 $skip = 0; 1075 } 1076 s/^\s\s//; 1077 if (/^([^\s]+)/) { 1078 # Is the checker enabled? 1079 my $checker = $1; 1080 my $enabled = 0; 1081 my $aggregate = ""; 1082 foreach my $domain (split /\./, $checker) { 1083 $aggregate .= $domain; 1084 if ($EnabledCheckers{$aggregate}) { 1085 $enabled =1; 1086 last; 1087 } 1088 } 1089 1090 if ($enabled) { 1091 print " + "; 1092 } 1093 else { 1094 print " "; 1095 } 1096 } 1097 else { 1098 print " "; 1099 } 1100 print $_; 1101 } 1102 } 1103 waitpid($pid,0); 1104 close(FROM_CHILD); 1105 1106 print <<ENDTEXT 1107 1108 NOTE: "+" indicates that an analysis is enabled by default. 1109 1110 BUILD OPTIONS 1111 1112 You can specify any build option acceptable to the build command. 1113 1114 EXAMPLE 1115 1116 $Prog -o /tmp/myhtmldir make -j4 1117 1118 The above example causes analysis reports to be deposited into 1119 a subdirectory of "/tmp/myhtmldir" and to run "make" with the "-j4" option. 1120 A different subdirectory is created each time $Prog analyzes a project. 1121 The analyzer should support most parallel builds, but not distributed builds. 1122 1123 ENDTEXT 1124 } 1125 1126 ##----------------------------------------------------------------------------## 1127 # HtmlEscape - HTML entity encode characters that are special in HTML 1128 ##----------------------------------------------------------------------------## 1129 1130 sub HtmlEscape { 1131 # copy argument to new variable so we don't clobber the original 1132 my $arg = shift || ''; 1133 my $tmp = $arg; 1134 $tmp =~ s/&/&/g; 1135 $tmp =~ s/</</g; 1136 $tmp =~ s/>/>/g; 1137 return $tmp; 1138 } 1139 1140 ##----------------------------------------------------------------------------## 1141 # ShellEscape - backslash escape characters that are special to the shell 1142 ##----------------------------------------------------------------------------## 1143 1144 sub ShellEscape { 1145 # copy argument to new variable so we don't clobber the original 1146 my $arg = shift || ''; 1147 if ($arg =~ /["\s]/) { return "'" . $arg . "'"; } 1148 return $arg; 1149 } 1150 1151 ##----------------------------------------------------------------------------## 1152 # Process command-line arguments. 1153 ##----------------------------------------------------------------------------## 1154 1155 my $AnalyzeHeaders = 0; 1156 my $HtmlDir; # Parent directory to store HTML files. 1157 my $IgnoreErrors = 0; # Ignore build errors. 1158 my $ViewResults = 0; # View results when the build terminates. 1159 my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found 1160 my @AnalysesToRun; 1161 my $StoreModel; 1162 my $ConstraintsModel; 1163 my $OutputFormat = "html"; 1164 my $AnalyzerStats = 0; 1165 my $MaxLoop = 0; 1166 1167 if (!@ARGV) { 1168 DisplayHelp(); 1169 exit 1; 1170 } 1171 1172 1173 my $displayHelp = 0; 1174 1175 while (@ARGV) { 1176 1177 # Scan for options we recognize. 1178 1179 my $arg = $ARGV[0]; 1180 1181 if ($arg eq "-h" or $arg eq "--help") { 1182 $displayHelp = 1; 1183 shift @ARGV; 1184 next; 1185 } 1186 1187 if ($arg eq '-analyze-headers') { 1188 shift @ARGV; 1189 $AnalyzeHeaders = 1; 1190 next; 1191 } 1192 1193 if ($arg eq "-o") { 1194 shift @ARGV; 1195 1196 if (!@ARGV) { 1197 DieDiag("'-o' option requires a target directory name.\n"); 1198 } 1199 1200 # Construct an absolute path. Uses the current working directory 1201 # as a base if the original path was not absolute. 1202 $HtmlDir = abs_path(shift @ARGV); 1203 1204 next; 1205 } 1206 1207 if ($arg =~ /^--html-title(=(.+))?$/) { 1208 shift @ARGV; 1209 1210 if (!defined $2 || $2 eq '') { 1211 if (!@ARGV) { 1212 DieDiag("'--html-title' option requires a string.\n"); 1213 } 1214 1215 $HtmlTitle = shift @ARGV; 1216 } else { 1217 $HtmlTitle = $2; 1218 } 1219 1220 next; 1221 } 1222 1223 if ($arg eq "-k" or $arg eq "--keep-going") { 1224 shift @ARGV; 1225 $IgnoreErrors = 1; 1226 next; 1227 } 1228 1229 if ($arg =~ /^--use-cc(=(.+))?$/) { 1230 shift @ARGV; 1231 my $cc; 1232 1233 if (!defined $2 || $2 eq "") { 1234 if (!@ARGV) { 1235 DieDiag("'--use-cc' option requires a compiler executable name.\n"); 1236 } 1237 $cc = shift @ARGV; 1238 } 1239 else { 1240 $cc = $2; 1241 } 1242 1243 $ENV{"CCC_CC"} = $cc; 1244 next; 1245 } 1246 1247 if ($arg =~ /^--use-c\+\+(=(.+))?$/) { 1248 shift @ARGV; 1249 my $cxx; 1250 1251 if (!defined $2 || $2 eq "") { 1252 if (!@ARGV) { 1253 DieDiag("'--use-c++' option requires a compiler executable name.\n"); 1254 } 1255 $cxx = shift @ARGV; 1256 } 1257 else { 1258 $cxx = $2; 1259 } 1260 1261 $ENV{"CCC_CXX"} = $cxx; 1262 next; 1263 } 1264 1265 if ($arg eq "-v") { 1266 shift @ARGV; 1267 $Verbose++; 1268 next; 1269 } 1270 1271 if ($arg eq "-V" or $arg eq "--view") { 1272 shift @ARGV; 1273 $ViewResults = 1; 1274 next; 1275 } 1276 1277 if ($arg eq "--status-bugs") { 1278 shift @ARGV; 1279 $ExitStatusFoundBugs = 1; 1280 next; 1281 } 1282 1283 if ($arg eq "-store") { 1284 shift @ARGV; 1285 $StoreModel = shift @ARGV; 1286 next; 1287 } 1288 1289 if ($arg eq "-constraints") { 1290 shift @ARGV; 1291 $ConstraintsModel = shift @ARGV; 1292 next; 1293 } 1294 1295 if ($arg eq "-plist") { 1296 shift @ARGV; 1297 $OutputFormat = "plist"; 1298 next; 1299 } 1300 if ($arg eq "-plist-html") { 1301 shift @ARGV; 1302 $OutputFormat = "plist-html"; 1303 next; 1304 } 1305 1306 if ($arg eq "-no-failure-reports") { 1307 $ENV{"CCC_REPORT_FAILURES"} = 0; 1308 next; 1309 } 1310 if ($arg eq "-stats") { 1311 shift @ARGV; 1312 $AnalyzerStats = 1; 1313 next; 1314 } 1315 if ($arg eq "-maxloop") { 1316 shift @ARGV; 1317 $MaxLoop = shift @ARGV; 1318 next; 1319 } 1320 if ($arg eq "-enable-checker") { 1321 shift @ARGV; 1322 push @AnalysesToRun, "-analyzer-checker", shift @ARGV; 1323 next; 1324 } 1325 if ($arg eq "-disable-checker") { 1326 shift @ARGV; 1327 push @AnalysesToRun, "-analyzer-disable-checker", shift @ARGV; 1328 next; 1329 } 1330 1331 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); 1332 1333 last; 1334 } 1335 1336 if (!@ARGV and $displayHelp == 0) { 1337 Diag("No build command specified.\n\n"); 1338 $displayHelp = 1; 1339 } 1340 1341 if ($displayHelp) { 1342 DisplayHelp(); 1343 exit 1; 1344 } 1345 1346 # Determine where results go. 1347 $CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV))); 1348 $HtmlTitle = "${CurrentDirSuffix} - scan-build results" 1349 unless (defined($HtmlTitle)); 1350 1351 # Determine the output directory for the HTML reports. 1352 my $BaseDir = $HtmlDir; 1353 $HtmlDir = GetHTMLRunDir($HtmlDir); 1354 1355 # Determine the location of ccc-analyzer. 1356 my $AbsRealBin = Cwd::realpath($RealBin); 1357 my $Cmd = "$AbsRealBin/libexec/ccc-analyzer"; 1358 my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer"; 1359 1360 if (!defined $Cmd || ! -x $Cmd) { 1361 $Cmd = "$AbsRealBin/ccc-analyzer"; 1362 DieDiag("Executable 'ccc-analyzer' does not exist at '$Cmd'\n") if(! -x $Cmd); 1363 } 1364 if (!defined $CmdCXX || ! -x $CmdCXX) { 1365 $CmdCXX = "$AbsRealBin/c++-analyzer"; 1366 DieDiag("Executable 'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -x $CmdCXX); 1367 } 1368 1369 if (!defined $ClangSB || ! -x $ClangSB) { 1370 Diag("'clang' executable not found in '$RealBin/bin'.\n"); 1371 Diag("Using 'clang' from path: $Clang\n"); 1372 } 1373 1374 # Set the appropriate environment variables. 1375 SetHtmlEnv(\@ARGV, $HtmlDir); 1376 $ENV{'CC'} = $Cmd; 1377 $ENV{'CXX'} = $CmdCXX; 1378 $ENV{'CLANG'} = $Clang; 1379 $ENV{'CLANG_CXX'} = $ClangCXX; 1380 if ($Verbose >= 2) { 1381 $ENV{'CCC_ANALYZER_VERBOSE'} = 1; 1382 } 1383 if ($Verbose >= 3) { 1384 $ENV{'CCC_ANALYZER_LOG'} = 1; 1385 } 1386 if ($AnalyzeHeaders) { 1387 push @AnalysesToRun,"-analyzer-opt-analyze-headers"; 1388 } 1389 if ($AnalyzerStats) { 1390 push @AnalysesToRun, '-analyzer-checker', 'debug.Stats'; 1391 } 1392 if ($MaxLoop > 0) { 1393 push @AnalysesToRun, '-analyzer-max-loop ' . $MaxLoop; 1394 } 1395 1396 $ENV{'CCC_ANALYZER_ANALYSIS'} = join ' ',@AnalysesToRun; 1397 1398 if (defined $StoreModel) { 1399 $ENV{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel; 1400 } 1401 if (defined $ConstraintsModel) { 1402 $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel; 1403 } 1404 if (defined $OutputFormat) { 1405 $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat; 1406 } 1407 1408 # Run the build. 1409 my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd, $CmdCXX); 1410 1411 if (defined $OutputFormat) { 1412 if ($OutputFormat =~ /plist/) { 1413 Diag "Analysis run complete.\n"; 1414 Diag "Analysis results (plist files) deposited in '$HtmlDir'\n"; 1415 } 1416 elsif ($OutputFormat =~ /html/) { 1417 # Postprocess the HTML directory. 1418 my $NumBugs = Postprocess($HtmlDir, $BaseDir, $AnalyzerStats); 1419 1420 if ($ViewResults and -r "$HtmlDir/index.html") { 1421 Diag "Analysis run complete.\n"; 1422 Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n"; 1423 my $ScanView = Cwd::realpath("$RealBin/scan-view"); 1424 if (! -x $ScanView) { $ScanView = "scan-view"; } 1425 exec $ScanView, "$HtmlDir"; 1426 } 1427 1428 if ($ExitStatusFoundBugs) { 1429 exit 1 if ($NumBugs > 0); 1430 exit 0; 1431 } 1432 } 1433 } 1434 1435 exit $ExitStatus; 1436 1437