1#!/usr/bin/env perl 2# 3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4# See https://llvm.org/LICENSE.txt for license information. 5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6# 7##===----------------------------------------------------------------------===## 8# 9# A script designed to wrap a build so that all calls to gcc are intercepted 10# and piped to the static analyzer. 11# 12##===----------------------------------------------------------------------===## 13 14use strict; 15use warnings; 16use FindBin qw($RealBin); 17use Digest::MD5; 18use File::Basename; 19use File::Find; 20use File::Copy qw(copy); 21use File::Path qw( rmtree mkpath ); 22use Term::ANSIColor; 23use Term::ANSIColor qw(:constants); 24use Cwd qw/ getcwd abs_path /; 25use Sys::Hostname; 26use Hash::Util qw(lock_keys); 27 28my $Prog = "scan-build"; 29my $BuildName; 30my $BuildDate; 31 32my $TERM = $ENV{'TERM'}; 33my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT 34 and defined $ENV{'SCAN_BUILD_COLOR'}); 35 36# Portability: getpwuid is not implemented for Win32 (see Perl language 37# reference, perlport), use getlogin instead. 38my $UserName = HtmlEscape(getlogin() || getpwuid($<) || 'unknown'); 39my $HostName = HtmlEscape(hostname() || 'unknown'); 40my $CurrentDir = HtmlEscape(getcwd()); 41 42my $CmdArgs; 43 44my $Date = localtime(); 45 46# Command-line/config arguments. 47my %Options = ( 48 Verbose => 0, # Verbose output from this script. 49 AnalyzeHeaders => 0, 50 OutputDir => undef, # Parent directory to store HTML files. 51 HtmlTitle => basename($CurrentDir)." - scan-build results", 52 IgnoreErrors => 0, # Ignore build errors. 53 KeepCC => 0, # Do not override CC and CXX make variables 54 ViewResults => 0, # View results when the build terminates. 55 ExitStatusFoundBugs => 0, # Exit status reflects whether bugs were found 56 ShowDescription => 0, # Display the description of the defect in the list 57 KeepEmpty => 0, # Don't remove output directory even with 0 results. 58 EnableCheckers => {}, 59 DisableCheckers => {}, 60 SilenceCheckers => {}, 61 Excludes => [], 62 UseCC => undef, # C compiler to use for compilation. 63 UseCXX => undef, # C++ compiler to use for compilation. 64 AnalyzerTarget => undef, 65 StoreModel => undef, 66 ConstraintsModel => undef, 67 InternalStats => undef, 68 OutputFormat => "html", 69 ConfigOptions => [], # Options to pass through to the analyzer's -analyzer-config flag. 70 ReportFailures => undef, 71 AnalyzerStats => 0, 72 MaxLoop => 0, 73 PluginsToLoad => [], 74 AnalyzerDiscoveryMethod => undef, 75 OverrideCompiler => 0, # The flag corresponding to the --override-compiler command line option. 76 ForceAnalyzeDebugCode => 0, 77 GenerateIndex => 0 # Skip the analysis, only generate index.html. 78); 79lock_keys(%Options); 80 81##----------------------------------------------------------------------------## 82# Diagnostics 83##----------------------------------------------------------------------------## 84 85sub Diag { 86 if ($UseColor) { 87 print BOLD, MAGENTA "$Prog: @_"; 88 print RESET; 89 } 90 else { 91 print "$Prog: @_"; 92 } 93} 94 95sub ErrorDiag { 96 if ($UseColor) { 97 print STDERR BOLD, RED "$Prog: "; 98 print STDERR RESET, RED @_; 99 print STDERR RESET; 100 } else { 101 print STDERR "$Prog: @_"; 102 } 103} 104 105sub DiagCrashes { 106 my $Dir = shift; 107 Diag ("The analyzer encountered problems on some source files.\n"); 108 Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n"); 109 Diag ("Please consider submitting a bug report using these files:\n"); 110 Diag (" http://clang-analyzer.llvm.org/filing_bugs.html\n") 111} 112 113sub DieDiag { 114 if ($UseColor) { 115 print STDERR BOLD, RED "$Prog: "; 116 print STDERR RESET, RED @_; 117 print STDERR RESET; 118 } 119 else { 120 print STDERR "$Prog: ", @_; 121 } 122 exit 1; 123} 124 125##----------------------------------------------------------------------------## 126# Print default checker names 127##----------------------------------------------------------------------------## 128 129if (grep /^--help-checkers$/, @ARGV) { 130 my @options = qx($0 -h); 131 foreach (@options) { 132 next unless /^ \+/; 133 s/^\s*//; 134 my ($sign, $name, @text) = split ' ', $_; 135 print $name, $/ if $sign eq '+'; 136 } 137 exit 0; 138} 139 140##----------------------------------------------------------------------------## 141# Declaration of Clang options. Populated later. 142##----------------------------------------------------------------------------## 143 144my $Clang; 145my $ClangSB; 146my $ClangCXX; 147my $ClangVersion; 148 149##----------------------------------------------------------------------------## 150# GetHTMLRunDir - Construct an HTML directory name for the current sub-run. 151##----------------------------------------------------------------------------## 152 153sub GetHTMLRunDir { 154 die "Not enough arguments." if (@_ == 0); 155 my $Dir = shift @_; 156 my $TmpMode = 0; 157 if (!defined $Dir) { 158 $Dir = $ENV{'TMPDIR'} || $ENV{'TEMP'} || $ENV{'TMP'} || "/tmp"; 159 $TmpMode = 1; 160 } 161 162 # Chop off any trailing '/' characters. 163 while ($Dir =~ /\/$/) { chop $Dir; } 164 165 # Get current date and time. 166 my @CurrentTime = localtime(); 167 my $year = $CurrentTime[5] + 1900; 168 my $day = $CurrentTime[3]; 169 my $month = $CurrentTime[4] + 1; 170 my $hour = $CurrentTime[2]; 171 my $min = $CurrentTime[1]; 172 my $sec = $CurrentTime[0]; 173 174 my $TimeString = sprintf("%02d%02d%02d", $hour, $min, $sec); 175 my $DateString = sprintf("%d-%02d-%02d-%s-$$", 176 $year, $month, $day, $TimeString); 177 178 # Determine the run number. 179 my $RunNumber; 180 181 if (-d $Dir) { 182 if (! -r $Dir) { 183 DieDiag("directory '$Dir' exists but is not readable.\n"); 184 } 185 # Iterate over all files in the specified directory. 186 my $max = 0; 187 opendir(DIR, $Dir); 188 my @FILES = grep { -d "$Dir/$_" } readdir(DIR); 189 closedir(DIR); 190 191 foreach my $f (@FILES) { 192 # Strip the prefix '$Prog-' if we are dumping files to /tmp. 193 if ($TmpMode) { 194 next if (!($f =~ /^$Prog-(.+)/)); 195 $f = $1; 196 } 197 198 my @x = split/-/, $f; 199 next if (scalar(@x) != 4); 200 next if ($x[0] != $year); 201 next if ($x[1] != $month); 202 next if ($x[2] != $day); 203 next if ($x[3] != $TimeString); 204 next if ($x[4] != $$); 205 206 if ($x[5] > $max) { 207 $max = $x[5]; 208 } 209 } 210 211 $RunNumber = $max + 1; 212 } 213 else { 214 215 if (-x $Dir) { 216 DieDiag("'$Dir' exists but is not a directory.\n"); 217 } 218 219 if ($TmpMode) { 220 DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n"); 221 } 222 223 # $Dir does not exist. It will be automatically created by the 224 # clang driver. Set the run number to 1. 225 226 $RunNumber = 1; 227 } 228 229 die "RunNumber must be defined!" if (!defined $RunNumber); 230 231 # Append the run number. 232 my $NewDir; 233 if ($TmpMode) { 234 $NewDir = "$Dir/$Prog-$DateString-$RunNumber"; 235 } 236 else { 237 $NewDir = "$Dir/$DateString-$RunNumber"; 238 } 239 240 # Make sure that the directory does not exist in order to avoid hijack. 241 if (-e $NewDir) { 242 DieDiag("The directory '$NewDir' already exists.\n"); 243 } 244 245 mkpath($NewDir); 246 return $NewDir; 247} 248 249sub SetHtmlEnv { 250 251 die "Wrong number of arguments." if (scalar(@_) != 2); 252 253 my $Args = shift; 254 my $Dir = shift; 255 256 die "No build command." if (scalar(@$Args) == 0); 257 258 my $Cmd = $$Args[0]; 259 260 if ($Cmd =~ /configure/ || $Cmd =~ /autogen/) { 261 return; 262 } 263 264 if ($Options{Verbose}) { 265 Diag("Emitting reports for this run to '$Dir'.\n"); 266 } 267 268 $ENV{'CCC_ANALYZER_HTML'} = $Dir; 269} 270 271##----------------------------------------------------------------------------## 272# ComputeDigest - Compute a digest of the specified file. 273##----------------------------------------------------------------------------## 274 275sub ComputeDigest { 276 my $FName = shift; 277 DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName); 278 279 # Use Digest::MD5. We don't have to be cryptographically secure. We're 280 # just looking for duplicate files that come from a non-malicious source. 281 # We use Digest::MD5 because it is a standard Perl module that should 282 # come bundled on most systems. 283 open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n"); 284 binmode FILE; 285 my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest; 286 close(FILE); 287 288 # Return the digest. 289 return $Result; 290} 291 292##----------------------------------------------------------------------------## 293# UpdatePrefix - Compute the common prefix of files. 294##----------------------------------------------------------------------------## 295 296my $Prefix; 297 298sub UpdatePrefix { 299 my $x = shift; 300 my $y = basename($x); 301 $x =~ s/\Q$y\E$//; 302 303 if (!defined $Prefix) { 304 $Prefix = $x; 305 return; 306 } 307 308 chop $Prefix while (!($x =~ /^\Q$Prefix/)); 309} 310 311sub GetPrefix { 312 return $Prefix; 313} 314 315##----------------------------------------------------------------------------## 316# UpdateInFilePath - Update the path in the report file. 317##----------------------------------------------------------------------------## 318 319sub UpdateInFilePath { 320 my $fname = shift; 321 my $regex = shift; 322 my $newtext = shift; 323 324 open (RIN, $fname) or die "cannot open $fname"; 325 open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp"; 326 327 while (<RIN>) { 328 s/$regex/$newtext/; 329 print ROUT $_; 330 } 331 332 close (ROUT); 333 close (RIN); 334 rename("$fname.tmp", $fname) 335} 336 337##----------------------------------------------------------------------------## 338# AddStatLine - Decode and insert a statistics line into the database. 339##----------------------------------------------------------------------------## 340 341sub AddStatLine { 342 my $Line = shift; 343 my $Stats = shift; 344 my $File = shift; 345 346 print $Line . "\n"; 347 348 my $Regex = qr/(.*?)\ ->\ Total\ CFGBlocks:\ (\d+)\ \|\ Unreachable 349 \ CFGBlocks:\ (\d+)\ \|\ Exhausted\ Block:\ (yes|no)\ \|\ Empty\ WorkList: 350 \ (yes|no)/x; 351 352 if ($Line !~ $Regex) { 353 return; 354 } 355 356 # Create a hash of the interesting fields 357 my $Row = { 358 Filename => $File, 359 Function => $1, 360 Total => $2, 361 Unreachable => $3, 362 Aborted => $4, 363 Empty => $5 364 }; 365 366 # Add them to the stats array 367 push @$Stats, $Row; 368} 369 370##----------------------------------------------------------------------------## 371# ScanFile - Scan a report file for various identifying attributes. 372##----------------------------------------------------------------------------## 373 374# Sometimes a source file is scanned more than once, and thus produces 375# multiple error reports. We use a cache to solve this problem. 376 377my %AlreadyScanned; 378 379sub ScanFile { 380 381 my $Index = shift; 382 my $Dir = shift; 383 my $FName = shift; 384 my $Stats = shift; 385 386 # Compute a digest for the report file. Determine if we have already 387 # scanned a file that looks just like it. 388 389 my $digest = ComputeDigest("$Dir/$FName"); 390 391 if (defined $AlreadyScanned{$digest}) { 392 # Redundant file. Remove it. 393 unlink("$Dir/$FName"); 394 return; 395 } 396 397 $AlreadyScanned{$digest} = 1; 398 399 # At this point the report file is not world readable. Make it happen. 400 chmod(0644, "$Dir/$FName"); 401 402 # Scan the report file for tags. 403 open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n"); 404 405 my $BugType = ""; 406 my $BugFile = ""; 407 my $BugFunction = ""; 408 my $BugCategory = ""; 409 my $BugDescription = ""; 410 my $BugPathLength = 1; 411 my $BugLine = 0; 412 413 while (<IN>) { 414 last if (/<!-- BUGMETAEND -->/); 415 416 if (/<!-- BUGTYPE (.*) -->$/) { 417 $BugType = $1; 418 } 419 elsif (/<!-- BUGFILE (.*) -->$/) { 420 $BugFile = abs_path($1); 421 if (!defined $BugFile) { 422 # The file no longer exists: use the original path. 423 $BugFile = $1; 424 } 425 426 # Get just the path 427 my $p = dirname($BugFile); 428 # Check if the path is found in the list of exclude 429 if (grep { $p =~ m/$_/ } @{$Options{Excludes}}) { 430 if ($Options{Verbose}) { 431 Diag("File '$BugFile' deleted: part of an ignored directory.\n"); 432 } 433 434 # File in an ignored directory. Remove it 435 unlink("$Dir/$FName"); 436 return; 437 } 438 439 UpdatePrefix($BugFile); 440 } 441 elsif (/<!-- BUGPATHLENGTH (.*) -->$/) { 442 $BugPathLength = $1; 443 } 444 elsif (/<!-- BUGLINE (.*) -->$/) { 445 $BugLine = $1; 446 } 447 elsif (/<!-- BUGCATEGORY (.*) -->$/) { 448 $BugCategory = $1; 449 } 450 elsif (/<!-- BUGDESC (.*) -->$/) { 451 $BugDescription = $1; 452 } 453 elsif (/<!-- FUNCTIONNAME (.*) -->$/) { 454 $BugFunction = $1; 455 } 456 457 } 458 459 460 close(IN); 461 462 if (!defined $BugCategory) { 463 $BugCategory = "Other"; 464 } 465 466 # Don't add internal statistics to the bug reports 467 if ($BugCategory =~ /statistics/i) { 468 AddStatLine($BugDescription, $Stats, $BugFile); 469 return; 470 } 471 472 push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugFunction, $BugLine, 473 $BugPathLength ]; 474 475 if ($Options{ShowDescription}) { 476 push @{ $Index->[-1] }, $BugDescription 477 } 478} 479 480##----------------------------------------------------------------------------## 481# CopyFiles - Copy resource files to target directory. 482##----------------------------------------------------------------------------## 483 484sub CopyFiles { 485 486 my $Dir = shift; 487 488 my $JS = Cwd::realpath("$RealBin/../share/scan-build/sorttable.js"); 489 490 DieDiag("Cannot find 'sorttable.js'.\n") 491 if (! -r $JS); 492 493 copy($JS, "$Dir"); 494 495 DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n") 496 if (! -r "$Dir/sorttable.js"); 497 498 my $CSS = Cwd::realpath("$RealBin/../share/scan-build/scanview.css"); 499 500 DieDiag("Cannot find 'scanview.css'.\n") 501 if (! -r $CSS); 502 503 copy($CSS, "$Dir"); 504 505 DieDiag("Could not copy 'scanview.css' to '$Dir'.\n") 506 if (! -r $CSS); 507} 508 509##----------------------------------------------------------------------------## 510# CalcStats - Calculates visitation statistics and returns the string. 511##----------------------------------------------------------------------------## 512 513sub CalcStats { 514 my $Stats = shift; 515 516 my $TotalBlocks = 0; 517 my $UnreachedBlocks = 0; 518 my $TotalFunctions = scalar(@$Stats); 519 my $BlockAborted = 0; 520 my $WorkListAborted = 0; 521 my $Aborted = 0; 522 523 # Calculate the unique files 524 my $FilesHash = {}; 525 526 foreach my $Row (@$Stats) { 527 $FilesHash->{$Row->{Filename}} = 1; 528 $TotalBlocks += $Row->{Total}; 529 $UnreachedBlocks += $Row->{Unreachable}; 530 $BlockAborted++ if $Row->{Aborted} eq 'yes'; 531 $WorkListAborted++ if $Row->{Empty} eq 'no'; 532 $Aborted++ if $Row->{Aborted} eq 'yes' || $Row->{Empty} eq 'no'; 533 } 534 535 my $TotalFiles = scalar(keys(%$FilesHash)); 536 537 # Calculations 538 my $PercentAborted = sprintf("%.2f", $Aborted / $TotalFunctions * 100); 539 my $PercentBlockAborted = sprintf("%.2f", $BlockAborted / $TotalFunctions 540 * 100); 541 my $PercentWorkListAborted = sprintf("%.2f", $WorkListAborted / 542 $TotalFunctions * 100); 543 my $PercentBlocksUnreached = sprintf("%.2f", $UnreachedBlocks / $TotalBlocks 544 * 100); 545 546 my $StatsString = "Analyzed $TotalBlocks blocks in $TotalFunctions functions" 547 . " in $TotalFiles files\n" 548 . "$Aborted functions aborted early ($PercentAborted%)\n" 549 . "$BlockAborted had aborted blocks ($PercentBlockAborted%)\n" 550 . "$WorkListAborted had unfinished worklists ($PercentWorkListAborted%)\n" 551 . "$UnreachedBlocks blocks were never reached ($PercentBlocksUnreached%)\n"; 552 553 return $StatsString; 554} 555 556##----------------------------------------------------------------------------## 557# Postprocess - Postprocess the results of an analysis scan. 558##----------------------------------------------------------------------------## 559 560my @filesFound; 561my $baseDir; 562sub FileWanted { 563 my $baseDirRegEx = quotemeta $baseDir; 564 my $file = $File::Find::name; 565 566 # The name of the file is generated by clang binary (HTMLDiagnostics.cpp) 567 if ($file =~ /report-.*\.html$/) { 568 my $relative_file = $file; 569 $relative_file =~ s/$baseDirRegEx//g; 570 push @filesFound, $relative_file; 571 } 572} 573 574sub Postprocess { 575 576 my $Dir = shift; 577 my $BaseDir = shift; 578 my $AnalyzerStats = shift; 579 my $KeepEmpty = shift; 580 581 die "No directory specified." if (!defined $Dir); 582 583 if (! -d $Dir) { 584 Diag("No bugs found.\n"); 585 return 0; 586 } 587 588 $baseDir = $Dir . "/"; 589 find({ wanted => \&FileWanted, follow => 0}, $Dir); 590 591 if (scalar(@filesFound) == 0 and ! -e "$Dir/failures") { 592 if (! $KeepEmpty) { 593 Diag("Removing directory '$Dir' because it contains no reports.\n"); 594 rmtree($Dir) or die "Cannot rmtree '$Dir' : $!"; 595 } 596 Diag("No bugs found.\n"); 597 return 0; 598 } 599 600 # Scan each report file, in alphabetical order, and build an index. 601 my @Index; 602 my @Stats; 603 604 @filesFound = sort @filesFound; 605 foreach my $file (@filesFound) { ScanFile(\@Index, $Dir, $file, \@Stats); } 606 607 # Scan the failures directory and use the information in the .info files 608 # to update the common prefix directory. 609 my @failures; 610 my @attributes_ignored; 611 if (-d "$Dir/failures") { 612 opendir(DIR, "$Dir/failures"); 613 @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR); 614 closedir(DIR); 615 opendir(DIR, "$Dir/failures"); 616 @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR); 617 closedir(DIR); 618 foreach my $file (@failures) { 619 open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n"); 620 my $Path = <IN>; 621 if (defined $Path) { UpdatePrefix($Path); } 622 close IN; 623 } 624 } 625 626 # Generate an index.html file. 627 my $FName = "$Dir/index.html"; 628 open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n"); 629 630 # Print out the header. 631 632print OUT <<ENDTEXT; 633<html> 634<head> 635<title>${Options{HtmlTitle}}</title> 636<link type="text/css" rel="stylesheet" href="scanview.css"/> 637<script src="sorttable.js"></script> 638<script language='javascript' type="text/javascript"> 639function SetDisplay(RowClass, DisplayVal) 640{ 641 var Rows = document.getElementsByTagName("tr"); 642 for ( var i = 0 ; i < Rows.length; ++i ) { 643 if (Rows[i].className == RowClass) { 644 Rows[i].style.display = DisplayVal; 645 } 646 } 647} 648 649function CopyCheckedStateToCheckButtons(SummaryCheckButton) { 650 var Inputs = document.getElementsByTagName("input"); 651 for ( var i = 0 ; i < Inputs.length; ++i ) { 652 if (Inputs[i].type == "checkbox") { 653 if(Inputs[i] != SummaryCheckButton) { 654 Inputs[i].checked = SummaryCheckButton.checked; 655 Inputs[i].onclick(); 656 } 657 } 658 } 659} 660 661function returnObjById( id ) { 662 if (document.getElementById) 663 var returnVar = document.getElementById(id); 664 else if (document.all) 665 var returnVar = document.all[id]; 666 else if (document.layers) 667 var returnVar = document.layers[id]; 668 return returnVar; 669} 670 671var NumUnchecked = 0; 672 673function ToggleDisplay(CheckButton, ClassName) { 674 if (CheckButton.checked) { 675 SetDisplay(ClassName, ""); 676 if (--NumUnchecked == 0) { 677 returnObjById("AllBugsCheck").checked = true; 678 } 679 } 680 else { 681 SetDisplay(ClassName, "none"); 682 NumUnchecked++; 683 returnObjById("AllBugsCheck").checked = false; 684 } 685} 686</script> 687<!-- SUMMARYENDHEAD --> 688</head> 689<body> 690<h1>${Options{HtmlTitle}}</h1> 691 692<table> 693<tr><th>User:</th><td>${UserName}\@${HostName}</td></tr> 694<tr><th>Working Directory:</th><td>${CurrentDir}</td></tr> 695<tr><th>Command Line:</th><td>${CmdArgs}</td></tr> 696<tr><th>Clang Version:</th><td>${ClangVersion}</td></tr> 697<tr><th>Date:</th><td>${Date}</td></tr> 698ENDTEXT 699 700print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n" 701 if (defined($BuildName) && defined($BuildDate)); 702 703print OUT <<ENDTEXT; 704</table> 705ENDTEXT 706 707 if (scalar(@filesFound)) { 708 # Print out the summary table. 709 my %Totals; 710 711 for my $row ( @Index ) { 712 my $bug_type = ($row->[2]); 713 my $bug_category = ($row->[1]); 714 my $key = "$bug_category:$bug_type"; 715 716 if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; } 717 else { $Totals{$key}->[0]++; } 718 } 719 720 print OUT "<h2>Bug Summary</h2>"; 721 722 if (defined $BuildName) { 723 print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n" 724 } 725 726 my $TotalBugs = scalar(@Index); 727print OUT <<ENDTEXT; 728<table> 729<thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead> 730<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> 731ENDTEXT 732 733 my $last_category; 734 735 for my $key ( 736 sort { 737 my $x = $Totals{$a}; 738 my $y = $Totals{$b}; 739 my $res = $x->[1] cmp $y->[1]; 740 $res = $x->[2] cmp $y->[2] if ($res == 0); 741 $res 742 } keys %Totals ) 743 { 744 my $val = $Totals{$key}; 745 my $category = $val->[1]; 746 if (!defined $last_category or $last_category ne $category) { 747 $last_category = $category; 748 print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n"; 749 } 750 my $x = lc $key; 751 $x =~ s/[ ,'":\/()]+/_/g; 752 print OUT "<tr><td class=\"SUMM_DESC\">"; 753 print OUT $val->[2]; 754 print OUT "</td><td class=\"Q\">"; 755 print OUT $val->[0]; 756 print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n"; 757 } 758 759 # Print out the table of errors. 760 761print OUT <<ENDTEXT; 762</table> 763<h2>Reports</h2> 764 765<table class="sortable" style="table-layout:automatic"> 766<thead><tr> 767 <td>Bug Group</td> 768 <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind"> ▾</span></td> 769 <td>File</td> 770 <td>Function/Method</td> 771 <td class="Q">Line</td> 772 <td class="Q">Path Length</td> 773ENDTEXT 774 775if ($Options{ShowDescription}) { 776print OUT <<ENDTEXT; 777 <td class="Q">Description</td> 778ENDTEXT 779} 780 781print OUT <<ENDTEXT; 782 <td class="sorttable_nosort"></td> 783 <!-- REPORTBUGCOL --> 784</tr></thead> 785<tbody> 786ENDTEXT 787 788 my $prefix = GetPrefix(); 789 my $regex; 790 my $InFileRegex; 791 my $InFilePrefix = "File:</td><td>"; 792 793 if (defined $prefix) { 794 $regex = qr/^\Q$prefix\E/is; 795 $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is; 796 } 797 798 for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) { 799 my $x = "$row->[1]:$row->[2]"; 800 $x = lc $x; 801 $x =~ s/[ ,'":\/()]+/_/g; 802 803 my $ReportFile = $row->[0]; 804 805 print OUT "<tr class=\"bt_$x\">"; 806 print OUT "<td class=\"DESC\">"; 807 print OUT $row->[1]; # $BugCategory 808 print OUT "</td>"; 809 print OUT "<td class=\"DESC\">"; 810 print OUT $row->[2]; # $BugType 811 print OUT "</td>"; 812 813 # Update the file prefix. 814 my $fname = $row->[3]; 815 816 if (defined $regex) { 817 $fname =~ s/$regex//; 818 UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix) 819 } 820 821 print OUT "<td>"; 822 my @fname = split /\//,$fname; 823 if ($#fname > 0) { 824 while ($#fname >= 0) { 825 my $x = shift @fname; 826 print OUT $x; 827 if ($#fname >= 0) { 828 print OUT "/"; 829 } 830 } 831 } 832 else { 833 print OUT $fname; 834 } 835 print OUT "</td>"; 836 837 print OUT "<td class=\"DESC\">"; 838 print OUT $row->[4]; # Function 839 print OUT "</td>"; 840 841 # Print out the quantities. 842 for my $j ( 5 .. 6 ) { # Line & Path length 843 print OUT "<td class=\"Q\">$row->[$j]</td>"; 844 } 845 846 # Print the rest of the columns. 847 for (my $j = 7; $j <= $#{$row}; ++$j) { 848 print OUT "<td>$row->[$j]</td>" 849 } 850 851 # Emit the "View" link. 852 print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>"; 853 854 # Emit REPORTBUG markers. 855 print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n"; 856 857 # End the row. 858 print OUT "</tr>\n"; 859 } 860 861 print OUT "</tbody>\n</table>\n\n"; 862 } 863 864 if (scalar (@failures) || scalar(@attributes_ignored)) { 865 print OUT "<h2>Analyzer Failures</h2>\n"; 866 867 if (scalar @attributes_ignored) { 868 print OUT "The analyzer's parser ignored the following attributes:<p>\n"; 869 print OUT "<table>\n"; 870 print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; 871 foreach my $file (sort @attributes_ignored) { 872 die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/)); 873 my $attribute = $1; 874 # Open the attribute file to get the first file that failed. 875 next if (!open (ATTR, "$Dir/failures/$file")); 876 my $ppfile = <ATTR>; 877 chomp $ppfile; 878 close ATTR; 879 next if (! -e "$Dir/failures/$ppfile"); 880 # Open the info file and get the name of the source file. 881 open (INFO, "$Dir/failures/$ppfile.info.txt") or 882 die "Cannot open $Dir/failures/$ppfile.info.txt\n"; 883 my $srcfile = <INFO>; 884 chomp $srcfile; 885 close (INFO); 886 # Print the information in the table. 887 my $prefix = GetPrefix(); 888 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } 889 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"; 890 my $ppfile_clang = $ppfile; 891 $ppfile_clang =~ s/[.](.+)$/.clang.$1/; 892 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; 893 } 894 print OUT "</table>\n"; 895 } 896 897 if (scalar @failures) { 898 print OUT "<p>The analyzer had problems processing the following files:</p>\n"; 899 print OUT "<table>\n"; 900 print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; 901 foreach my $file (sort @failures) { 902 $file =~ /(.+).info.txt$/; 903 # Get the preprocessed file. 904 my $ppfile = $1; 905 # Open the info file and get the name of the source file. 906 open (INFO, "$Dir/failures/$file") or 907 die "Cannot open $Dir/failures/$file\n"; 908 my $srcfile = <INFO>; 909 chomp $srcfile; 910 my $problem = <INFO>; 911 chomp $problem; 912 close (INFO); 913 # Print the information in the table. 914 my $prefix = GetPrefix(); 915 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } 916 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"; 917 my $ppfile_clang = $ppfile; 918 $ppfile_clang =~ s/[.](.+)$/.clang.$1/; 919 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; 920 } 921 print OUT "</table>\n"; 922 } 923 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"; 924 } 925 926 print OUT "</body></html>\n"; 927 close(OUT); 928 CopyFiles($Dir); 929 930 # Make sure $Dir and $BaseDir are world readable/executable. 931 chmod(0755, $Dir); 932 if (defined $BaseDir) { chmod(0755, $BaseDir); } 933 934 # Print statistics 935 print CalcStats(\@Stats) if $AnalyzerStats; 936 937 my $Num = scalar(@Index); 938 if ($Num == 1) { 939 Diag("$Num bug found.\n"); 940 } else { 941 Diag("$Num bugs found.\n"); 942 } 943 if ($Num > 0 && -r "$Dir/index.html") { 944 Diag("Run 'scan-view $Dir' to examine bug reports.\n"); 945 } 946 947 DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored); 948 949 return $Num; 950} 951 952sub Finalize { 953 my $BaseDir = shift; 954 my $ExitStatus = shift; 955 956 Diag "Analysis run complete.\n"; 957 if (defined $Options{OutputFormat}) { 958 if ($Options{OutputFormat} =~ /plist/ || 959 $Options{OutputFormat} =~ /sarif/) { 960 Diag "Analysis results (" . 961 ($Options{OutputFormat} =~ /plist/ ? "plist" : "sarif") . 962 " files) deposited in '$Options{OutputDir}'\n"; 963 } 964 if ($Options{OutputFormat} =~ /html/) { 965 # Postprocess the HTML directory. 966 my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir, 967 $Options{AnalyzerStats}, $Options{KeepEmpty}); 968 969 if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") { 970 Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n"; 971 my $ScanView = Cwd::realpath("$RealBin/scan-view"); 972 if (! -x $ScanView) { $ScanView = "scan-view"; } 973 if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); } 974 if (! -x $ScanView) { $ScanView = `which scan-view`; chomp $ScanView; } 975 exec $ScanView, "$Options{OutputDir}"; 976 } 977 978 if ($Options{ExitStatusFoundBugs}) { 979 exit 1 if ($NumBugs > 0); 980 exit $ExitStatus; 981 } 982 } 983 } 984 985 exit $ExitStatus; 986} 987 988##----------------------------------------------------------------------------## 989# RunBuildCommand - Run the build command. 990##----------------------------------------------------------------------------## 991 992sub AddIfNotPresent { 993 my $Args = shift; 994 my $Arg = shift; 995 my $found = 0; 996 997 foreach my $k (@$Args) { 998 if ($k eq $Arg) { 999 $found = 1; 1000 last; 1001 } 1002 } 1003 1004 if ($found == 0) { 1005 push @$Args, $Arg; 1006 } 1007} 1008 1009sub SetEnv { 1010 my $EnvVars = shift @_; 1011 foreach my $var ('CC', 'CXX', 'CLANG', 'CLANG_CXX', 1012 'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS', 1013 'CCC_ANALYZER_CONFIG') { 1014 die "$var is undefined\n" if (!defined $var); 1015 $ENV{$var} = $EnvVars->{$var}; 1016 } 1017 foreach my $var ('CCC_ANALYZER_STORE_MODEL', 1018 'CCC_ANALYZER_CONSTRAINTS_MODEL', 1019 'CCC_ANALYZER_INTERNAL_STATS', 1020 'CCC_ANALYZER_OUTPUT_FORMAT', 1021 'CCC_CC', 1022 'CCC_CXX', 1023 'CCC_REPORT_FAILURES', 1024 'CLANG_ANALYZER_TARGET', 1025 'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE') { 1026 my $x = $EnvVars->{$var}; 1027 if (defined $x) { $ENV{$var} = $x } 1028 } 1029 my $Verbose = $EnvVars->{'VERBOSE'}; 1030 if ($Verbose >= 2) { 1031 $ENV{'CCC_ANALYZER_VERBOSE'} = 1; 1032 } 1033 if ($Verbose >= 3) { 1034 $ENV{'CCC_ANALYZER_LOG'} = 1; 1035 } 1036} 1037 1038sub RunXcodebuild { 1039 my $Args = shift; 1040 my $IgnoreErrors = shift; 1041 my $CCAnalyzer = shift; 1042 my $CXXAnalyzer = shift; 1043 my $EnvVars = shift; 1044 1045 if ($IgnoreErrors) { 1046 AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES"); 1047 } 1048 1049 # Detect the version of Xcode. If Xcode 4.6 or higher, use new 1050 # in situ support for analyzer interposition without needed to override 1051 # the compiler. 1052 open(DETECT_XCODE, "-|", $Args->[0], "-version") or 1053 die "error: cannot detect version of xcodebuild\n"; 1054 1055 my $oldBehavior = 1; 1056 1057 while(<DETECT_XCODE>) { 1058 if (/^Xcode (.+)$/) { 1059 my $ver = $1; 1060 if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) { 1061 if ($1 >= 4.6) { 1062 $oldBehavior = 0; 1063 last; 1064 } 1065 } 1066 } 1067 } 1068 close(DETECT_XCODE); 1069 1070 # If --override-compiler is explicitly requested, resort to the old 1071 # behavior regardless of Xcode version. 1072 if ($Options{OverrideCompiler}) { 1073 $oldBehavior = 1; 1074 } 1075 1076 if ($oldBehavior == 0) { 1077 my $OutputDir = $EnvVars->{"OUTPUT_DIR"}; 1078 my $CLANG = $EnvVars->{"CLANG"}; 1079 my $OtherFlags = $EnvVars->{"CCC_ANALYZER_ANALYSIS"}; 1080 push @$Args, 1081 "RUN_CLANG_STATIC_ANALYZER=YES", 1082 "CLANG_ANALYZER_OUTPUT=plist-html", 1083 "CLANG_ANALYZER_EXEC=$CLANG", 1084 "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir", 1085 "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags"; 1086 1087 return (system(@$Args) >> 8); 1088 } 1089 1090 # Default to old behavior where we insert a bogus compiler. 1091 SetEnv($EnvVars); 1092 1093 # Check if using iPhone SDK 3.0 (simulator). If so the compiler being 1094 # used should be gcc-4.2. 1095 if (!defined $ENV{"CCC_CC"}) { 1096 for (my $i = 0 ; $i < scalar(@$Args); ++$i) { 1097 if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) { 1098 if (@$Args[$i+1] =~ /^iphonesimulator3/) { 1099 $ENV{"CCC_CC"} = "gcc-4.2"; 1100 $ENV{"CCC_CXX"} = "g++-4.2"; 1101 } 1102 } 1103 } 1104 } 1105 1106 # Disable PCH files until clang supports them. 1107 AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO"); 1108 1109 # When 'CC' is set, xcodebuild uses it to do all linking, even if we are 1110 # linking C++ object files. Set 'LDPLUSPLUS' so that xcodebuild uses 'g++' 1111 # (via c++-analyzer) when linking such files. 1112 $ENV{"LDPLUSPLUS"} = $CXXAnalyzer; 1113 1114 return (system(@$Args) >> 8); 1115} 1116 1117sub RunBuildCommand { 1118 my $Args = shift; 1119 my $IgnoreErrors = shift; 1120 my $KeepCC = shift; 1121 my $Cmd = $Args->[0]; 1122 my $CCAnalyzer = shift; 1123 my $CXXAnalyzer = shift; 1124 my $EnvVars = shift; 1125 1126 if ($Cmd =~ /\bxcodebuild$/) { 1127 return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $EnvVars); 1128 } 1129 1130 # Setup the environment. 1131 SetEnv($EnvVars); 1132 1133 if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 1134 $Cmd =~ /(.*\/?cc[^\/]*$)/ or 1135 $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or 1136 $Cmd =~ /(.*\/?clang[^\/]*$)/ or 1137 $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) { 1138 1139 if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) { 1140 $ENV{"CCC_CC"} = $1; 1141 } 1142 1143 shift @$Args; 1144 unshift @$Args, $CCAnalyzer; 1145 } 1146 elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or 1147 $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or 1148 $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or 1149 $Cmd =~ /(.*\/?clang\+\+$)/ or 1150 $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) { 1151 if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) { 1152 $ENV{"CCC_CXX"} = $1; 1153 } 1154 shift @$Args; 1155 unshift @$Args, $CXXAnalyzer; 1156 } 1157 elsif ($Cmd eq "make" or $Cmd eq "gmake" or $Cmd eq "mingw32-make") { 1158 if (!$KeepCC) { 1159 AddIfNotPresent($Args, "CC=$CCAnalyzer"); 1160 AddIfNotPresent($Args, "CXX=$CXXAnalyzer"); 1161 } 1162 if ($IgnoreErrors) { 1163 AddIfNotPresent($Args,"-k"); 1164 AddIfNotPresent($Args,"-i"); 1165 } 1166 } 1167 1168 return (system(@$Args) >> 8); 1169} 1170 1171##----------------------------------------------------------------------------## 1172# DisplayHelp - Utility function to display all help options. 1173##----------------------------------------------------------------------------## 1174 1175sub DisplayHelp { 1176 1177 my $ArgClangNotFoundErrMsg = shift; 1178print <<ENDTEXT; 1179USAGE: $Prog [options] <build command> [build options] 1180 1181ENDTEXT 1182 1183 if (defined $BuildName) { 1184 print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n"; 1185 } 1186 1187print <<ENDTEXT; 1188OPTIONS: 1189 1190 -analyze-headers 1191 1192 Also analyze functions in #included files. By default, such functions 1193 are skipped unless they are called by functions within the main source file. 1194 1195 --force-analyze-debug-code 1196 1197 Tells analyzer to enable assertions in code even if they were disabled 1198 during compilation to enable more precise results. 1199 1200 -o <output location> 1201 1202 Specifies the output directory for analyzer reports. Subdirectories will be 1203 created as needed to represent separate "runs" of the analyzer. If this 1204 option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X) 1205 to store the reports. 1206 1207 -h 1208 --help 1209 1210 Display this message. 1211 1212 -k 1213 --keep-going 1214 1215 Add a "keep on going" option to the specified build command. This option 1216 currently supports make and xcodebuild. This is a convenience option; one 1217 can specify this behavior directly using build options. 1218 1219 --keep-cc 1220 1221 Do not override CC and CXX make variables. Useful when running make in 1222 autoconf-based (and similar) projects where configure can add extra flags 1223 to those variables. 1224 1225 --html-title [title] 1226 --html-title=[title] 1227 1228 Specify the title used on generated HTML pages. If not specified, a default 1229 title will be used. 1230 1231 --show-description 1232 1233 Display the description of defects in the list 1234 1235 -sarif 1236 1237 By default the output of scan-build is a set of HTML files. This option 1238 outputs the results in SARIF format. 1239 1240 -plist 1241 1242 By default the output of scan-build is a set of HTML files. This option 1243 outputs the results as a set of .plist files. 1244 1245 -plist-html 1246 1247 By default the output of scan-build is a set of HTML files. This option 1248 outputs the results as a set of HTML and .plist files. 1249 1250 --status-bugs 1251 1252 By default, the exit status of scan-build is the same as the executed build 1253 command. Specifying this option causes the exit status of scan-build to be 1 1254 if it found potential bugs and the exit status of the build itself otherwise. 1255 1256 --exclude <path> 1257 1258 Do not run static analyzer against files found in this 1259 directory (You can specify this option multiple times). 1260 Could be useful when project contains 3rd party libraries. 1261 1262 --use-cc [compiler path] 1263 --use-cc=[compiler path] 1264 1265 scan-build analyzes a project by interposing a "fake compiler", which 1266 executes a real compiler for compilation and the static analyzer for analysis. 1267 Because of the current implementation of interposition, scan-build does not 1268 know what compiler your project normally uses. Instead, it simply overrides 1269 the CC environment variable, and guesses your default compiler. 1270 1271 In the future, this interposition mechanism to be improved, but if you need 1272 scan-build to use a specific compiler for *compilation* then you can use 1273 this option to specify a path to that compiler. 1274 1275 If the given compiler is a cross compiler, you may also need to provide 1276 --analyzer-target option to properly analyze the source code because static 1277 analyzer runs as if the code is compiled for the host machine by default. 1278 1279 --use-c++ [compiler path] 1280 --use-c++=[compiler path] 1281 1282 This is the same as "--use-cc" but for C++ code. 1283 1284 --analyzer-target [target triple name for analysis] 1285 --analyzer-target=[target triple name for analysis] 1286 1287 This provides target triple information to clang static analyzer. 1288 It only changes the target for analysis but doesn't change the target of a 1289 real compiler given by --use-cc and --use-c++ options. 1290 1291 -v 1292 1293 Enable verbose output from scan-build. A second and third '-v' increases 1294 verbosity. 1295 1296 -V 1297 --view 1298 1299 View analysis results in a web browser when the build completes. 1300 1301 --generate-index-only <output location> 1302 1303 Do not perform the analysis, but only regenerate the index.html file 1304 from existing report.html files. Useful for making a custom Static Analyzer 1305 integration into a build system that isn't otherwise supported by scan-build. 1306 1307ADVANCED OPTIONS: 1308 1309 -no-failure-reports 1310 1311 Do not create a 'failures' subdirectory that includes analyzer crash reports 1312 and preprocessed source files. 1313 1314 -stats 1315 1316 Generates visitation statistics for the project being analyzed. 1317 1318 -maxloop <loop count> 1319 1320 Specify the number of times a block can be visited before giving up. 1321 Default is 4. Increase for more comprehensive coverage at a cost of speed. 1322 1323 -internal-stats 1324 1325 Generate internal analyzer statistics. 1326 1327 --use-analyzer [Xcode|path to clang] 1328 --use-analyzer=[Xcode|path to clang] 1329 1330 scan-build uses the 'clang' executable relative to itself for static 1331 analysis. One can override this behavior with this option by using the 1332 'clang' packaged with Xcode (on OS X) or from the PATH. 1333 1334 --keep-empty 1335 1336 Don't remove the build results directory even if no issues were reported. 1337 1338 --override-compiler 1339 Always resort to the ccc-analyzer even when better interposition methods 1340 are available. 1341 1342 -analyzer-config <options> 1343 1344 Provide options to pass through to the analyzer's -analyzer-config flag. 1345 Several options are separated with comma: 'key1=val1,key2=val2' 1346 1347 Available options: 1348 * stable-report-filename=true or false (default) 1349 Switch the page naming to: 1350 report-<filename>-<function/method name>-<id>.html 1351 instead of report-XXXXXX.html 1352 1353CONTROLLING CHECKERS: 1354 1355 A default group of checkers are always run unless explicitly disabled. 1356 Checkers may be enabled/disabled using the following options: 1357 1358 -enable-checker [checker name] 1359 -disable-checker [checker name] 1360 1361LOADING CHECKERS: 1362 1363 Loading external checkers using the clang plugin interface: 1364 1365 -load-plugin [plugin library] 1366ENDTEXT 1367 1368 if (defined $Clang && -x $Clang) { 1369 # Query clang for list of checkers that are enabled. 1370 1371 # create a list to load the plugins via the 'Xclang' command line 1372 # argument 1373 my @PluginLoadCommandline_xclang; 1374 foreach my $param ( @{$Options{PluginsToLoad}} ) { 1375 push ( @PluginLoadCommandline_xclang, "-Xclang" ); 1376 push ( @PluginLoadCommandline_xclang, "-load" ); 1377 push ( @PluginLoadCommandline_xclang, "-Xclang" ); 1378 push ( @PluginLoadCommandline_xclang, $param ); 1379 } 1380 1381 my %EnabledCheckers; 1382 foreach my $lang ("c", "objective-c", "objective-c++", "c++") { 1383 my $ExecLine = join(' ', qq/"$Clang"/, @PluginLoadCommandline_xclang, "--analyze", "-x", $lang, "-", "-###", "2>&1", "|"); 1384 open(PS, $ExecLine); 1385 while (<PS>) { 1386 foreach my $val (split /\s+/) { 1387 $val =~ s/\"//g; 1388 if ($val =~ /-analyzer-checker\=([^\s]+)/) { 1389 $EnabledCheckers{$1} = 1; 1390 } 1391 } 1392 } 1393 } 1394 1395 # Query clang for complete list of checkers. 1396 my @PluginLoadCommandline; 1397 foreach my $param ( @{$Options{PluginsToLoad}} ) { 1398 push ( @PluginLoadCommandline, "-load" ); 1399 push ( @PluginLoadCommandline, $param ); 1400 } 1401 1402 my $ExecLine = join(' ', qq/"$Clang"/, "-cc1", @PluginLoadCommandline, "-analyzer-checker-help", "2>&1", "|"); 1403 open(PS, $ExecLine); 1404 my $foundCheckers = 0; 1405 while (<PS>) { 1406 if (/CHECKERS:/) { 1407 $foundCheckers = 1; 1408 last; 1409 } 1410 } 1411 if (!$foundCheckers) { 1412 print " *** Could not query Clang for the list of available checkers."; 1413 } 1414 else { 1415 print("\nAVAILABLE CHECKERS:\n\n"); 1416 my $skip = 0; 1417 while(<PS>) { 1418 if (/experimental/) { 1419 $skip = 1; 1420 next; 1421 } 1422 if ($skip) { 1423 next if (!/^\s\s[^\s]/); 1424 $skip = 0; 1425 } 1426 s/^\s\s//; 1427 if (/^([^\s]+)/) { 1428 # Is the checker enabled? 1429 my $checker = $1; 1430 my $enabled = 0; 1431 my $aggregate = ""; 1432 foreach my $domain (split /\./, $checker) { 1433 $aggregate .= $domain; 1434 if ($EnabledCheckers{$aggregate}) { 1435 $enabled =1; 1436 last; 1437 } 1438 # append a dot, if an additional domain is added in the next iteration 1439 $aggregate .= "."; 1440 } 1441 1442 if ($enabled) { 1443 print " + "; 1444 } 1445 else { 1446 print " "; 1447 } 1448 } 1449 else { 1450 print " "; 1451 } 1452 print $_; 1453 } 1454 print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n"; 1455 } 1456 close PS; 1457 } 1458 else { 1459 print " *** Could not query Clang for the list of available checkers.\n"; 1460 if (defined $ArgClangNotFoundErrMsg) { 1461 print " *** Reason: $ArgClangNotFoundErrMsg\n"; 1462 } 1463 } 1464 1465print <<ENDTEXT 1466 1467BUILD OPTIONS 1468 1469 You can specify any build option acceptable to the build command. 1470 1471EXAMPLE 1472 1473 scan-build -o /tmp/myhtmldir make -j4 1474 1475The above example causes analysis reports to be deposited into a subdirectory 1476of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different 1477subdirectory is created each time scan-build analyzes a project. The analyzer 1478should support most parallel builds, but not distributed builds. 1479 1480ENDTEXT 1481} 1482 1483##----------------------------------------------------------------------------## 1484# HtmlEscape - HTML entity encode characters that are special in HTML 1485##----------------------------------------------------------------------------## 1486 1487sub HtmlEscape { 1488 # copy argument to new variable so we don't clobber the original 1489 my $arg = shift || ''; 1490 my $tmp = $arg; 1491 $tmp =~ s/&/&/g; 1492 $tmp =~ s/</</g; 1493 $tmp =~ s/>/>/g; 1494 return $tmp; 1495} 1496 1497##----------------------------------------------------------------------------## 1498# ShellEscape - backslash escape characters that are special to the shell 1499##----------------------------------------------------------------------------## 1500 1501sub ShellEscape { 1502 # copy argument to new variable so we don't clobber the original 1503 my $arg = shift || ''; 1504 if ($arg =~ /["\s]/) { return "'" . $arg . "'"; } 1505 return $arg; 1506} 1507 1508##----------------------------------------------------------------------------## 1509# FindXcrun - searches for the 'xcrun' executable. Returns "" if not found. 1510##----------------------------------------------------------------------------## 1511 1512sub FindXcrun { 1513 my $xcrun = `which xcrun`; 1514 chomp $xcrun; 1515 return $xcrun; 1516} 1517 1518##----------------------------------------------------------------------------## 1519# FindClang - searches for 'clang' executable. 1520##----------------------------------------------------------------------------## 1521 1522sub FindClang { 1523 if (!defined $Options{AnalyzerDiscoveryMethod}) { 1524 $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang"); 1525 if (!defined $Clang || ! -x $Clang) { 1526 $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang"); 1527 if (!defined $Clang || ! -x $Clang) { 1528 # When an Xcode toolchain is present, look for a clang in the sibling bin 1529 # of the parent of the bin directory. So if scan-build is at 1530 # $TOOLCHAIN/usr/local/bin/scan-build look for clang at 1531 # $TOOLCHAIN/usr/bin/clang. 1532 my $has_xcode_toolchain = FindXcrun() ne ""; 1533 if ($has_xcode_toolchain && -f "$RealBin/../../bin/clang") { 1534 $Clang = Cwd::realpath("$RealBin/../../bin/clang"); 1535 } 1536 } 1537 } 1538 if (!defined $Clang || ! -x $Clang) { 1539 return "error: Cannot find an executable 'clang' relative to" . 1540 " scan-build. Consider using --use-analyzer to pick a version of" . 1541 " 'clang' to use for static analysis.\n"; 1542 } 1543 } 1544 else { 1545 if ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) { 1546 my $xcrun = FindXcrun(); 1547 if ($xcrun eq "") { 1548 return "Cannot find 'xcrun' to find 'clang' for analysis.\n"; 1549 } 1550 $Clang = `$xcrun -toolchain XcodeDefault -find clang`; 1551 chomp $Clang; 1552 if ($Clang eq "") { 1553 return "No 'clang' executable found by 'xcrun'\n"; 1554 } 1555 } 1556 else { 1557 $Clang = $Options{AnalyzerDiscoveryMethod}; 1558 if (!defined $Clang or not -x $Clang) { 1559 return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n"; 1560 } 1561 } 1562 } 1563 return undef; 1564} 1565 1566##----------------------------------------------------------------------------## 1567# Process command-line arguments. 1568##----------------------------------------------------------------------------## 1569 1570my $RequestDisplayHelp = 0; 1571my $ForceDisplayHelp = 0; 1572 1573sub ProcessArgs { 1574 my $Args = shift; 1575 my $NumArgs = 0; 1576 1577 while (@$Args) { 1578 1579 $NumArgs++; 1580 1581 # Scan for options we recognize. 1582 1583 my $arg = $Args->[0]; 1584 1585 if ($arg eq "-h" or $arg eq "--help") { 1586 $RequestDisplayHelp = 1; 1587 shift @$Args; 1588 next; 1589 } 1590 1591 if ($arg eq '-analyze-headers') { 1592 shift @$Args; 1593 $Options{AnalyzeHeaders} = 1; 1594 next; 1595 } 1596 1597 if ($arg eq "-o") { 1598 if (defined($Options{OutputDir})) { 1599 DieDiag("Only one of '-o' or '--generate-index-only' can be specified.\n"); 1600 } 1601 1602 shift @$Args; 1603 1604 if (!@$Args) { 1605 DieDiag("'-o' option requires a target directory name.\n"); 1606 } 1607 1608 # Construct an absolute path. Uses the current working directory 1609 # as a base if the original path was not absolute. 1610 my $OutDir = shift @$Args; 1611 mkpath($OutDir) unless (-e $OutDir); # abs_path wants existing dir 1612 $Options{OutputDir} = abs_path($OutDir); 1613 1614 next; 1615 } 1616 1617 if ($arg eq "--generate-index-only") { 1618 if (defined($Options{OutputDir})) { 1619 DieDiag("Only one of '-o' or '--generate-index-only' can be specified.\n"); 1620 } 1621 1622 shift @$Args; 1623 1624 if (!@$Args) { 1625 DieDiag("'--generate-index-only' option requires a target directory name.\n"); 1626 } 1627 1628 # Construct an absolute path. Uses the current working directory 1629 # as a base if the original path was not absolute. 1630 my $OutDir = shift @$Args; 1631 mkpath($OutDir) unless (-e $OutDir); # abs_path wants existing dir 1632 $Options{OutputDir} = abs_path($OutDir); 1633 $Options{GenerateIndex} = 1; 1634 1635 next; 1636 } 1637 1638 if ($arg =~ /^--html-title(=(.+))?$/) { 1639 shift @$Args; 1640 1641 if (!defined $2 || $2 eq '') { 1642 if (!@$Args) { 1643 DieDiag("'--html-title' option requires a string.\n"); 1644 } 1645 1646 $Options{HtmlTitle} = shift @$Args; 1647 } else { 1648 $Options{HtmlTitle} = $2; 1649 } 1650 1651 next; 1652 } 1653 1654 if ($arg eq "-k" or $arg eq "--keep-going") { 1655 shift @$Args; 1656 $Options{IgnoreErrors} = 1; 1657 next; 1658 } 1659 1660 if ($arg eq "--keep-cc") { 1661 shift @$Args; 1662 $Options{KeepCC} = 1; 1663 next; 1664 } 1665 1666 if ($arg =~ /^--use-cc(=(.+))?$/) { 1667 shift @$Args; 1668 my $cc; 1669 1670 if (!defined $2 || $2 eq "") { 1671 if (!@$Args) { 1672 DieDiag("'--use-cc' option requires a compiler executable name.\n"); 1673 } 1674 $cc = shift @$Args; 1675 } 1676 else { 1677 $cc = $2; 1678 } 1679 1680 $Options{UseCC} = $cc; 1681 next; 1682 } 1683 1684 if ($arg =~ /^--use-c\+\+(=(.+))?$/) { 1685 shift @$Args; 1686 my $cxx; 1687 1688 if (!defined $2 || $2 eq "") { 1689 if (!@$Args) { 1690 DieDiag("'--use-c++' option requires a compiler executable name.\n"); 1691 } 1692 $cxx = shift @$Args; 1693 } 1694 else { 1695 $cxx = $2; 1696 } 1697 1698 $Options{UseCXX} = $cxx; 1699 next; 1700 } 1701 1702 if ($arg =~ /^--analyzer-target(=(.+))?$/) { 1703 shift @ARGV; 1704 my $AnalyzerTarget; 1705 1706 if (!defined $2 || $2 eq "") { 1707 if (!@ARGV) { 1708 DieDiag("'--analyzer-target' option requires a target triple name.\n"); 1709 } 1710 $AnalyzerTarget = shift @ARGV; 1711 } 1712 else { 1713 $AnalyzerTarget = $2; 1714 } 1715 1716 $Options{AnalyzerTarget} = $AnalyzerTarget; 1717 next; 1718 } 1719 1720 if ($arg eq "-v") { 1721 shift @$Args; 1722 $Options{Verbose}++; 1723 next; 1724 } 1725 1726 if ($arg eq "-V" or $arg eq "--view") { 1727 shift @$Args; 1728 $Options{ViewResults} = 1; 1729 next; 1730 } 1731 1732 if ($arg eq "--status-bugs") { 1733 shift @$Args; 1734 $Options{ExitStatusFoundBugs} = 1; 1735 next; 1736 } 1737 1738 if ($arg eq "--show-description") { 1739 shift @$Args; 1740 $Options{ShowDescription} = 1; 1741 next; 1742 } 1743 1744 if ($arg eq "-store") { 1745 shift @$Args; 1746 $Options{StoreModel} = shift @$Args; 1747 next; 1748 } 1749 1750 if ($arg eq "-constraints") { 1751 shift @$Args; 1752 $Options{ConstraintsModel} = shift @$Args; 1753 next; 1754 } 1755 1756 if ($arg eq "-internal-stats") { 1757 shift @$Args; 1758 $Options{InternalStats} = 1; 1759 next; 1760 } 1761 1762 if ($arg eq "-sarif") { 1763 shift @$Args; 1764 $Options{OutputFormat} = "sarif"; 1765 next; 1766 } 1767 1768 if ($arg eq "-plist") { 1769 shift @$Args; 1770 $Options{OutputFormat} = "plist"; 1771 next; 1772 } 1773 1774 if ($arg eq "-plist-html") { 1775 shift @$Args; 1776 $Options{OutputFormat} = "plist-html"; 1777 next; 1778 } 1779 1780 if ($arg eq "-analyzer-config") { 1781 shift @$Args; 1782 push @{$Options{ConfigOptions}}, shift @$Args; 1783 next; 1784 } 1785 1786 if ($arg eq "-no-failure-reports") { 1787 shift @$Args; 1788 $Options{ReportFailures} = 0; 1789 next; 1790 } 1791 1792 if ($arg eq "-stats") { 1793 shift @$Args; 1794 $Options{AnalyzerStats} = 1; 1795 next; 1796 } 1797 1798 if ($arg eq "-maxloop") { 1799 shift @$Args; 1800 $Options{MaxLoop} = shift @$Args; 1801 next; 1802 } 1803 1804 if ($arg eq "-enable-checker") { 1805 shift @$Args; 1806 my $Checker = shift @$Args; 1807 # Store $NumArgs to preserve the order the checkers were enabled. 1808 $Options{EnableCheckers}{$Checker} = $NumArgs; 1809 delete $Options{DisableCheckers}{$Checker}; 1810 next; 1811 } 1812 1813 if ($arg eq "-disable-checker") { 1814 shift @$Args; 1815 my $Checker = shift @$Args; 1816 # Store $NumArgs to preserve the order the checkers are disabled/silenced. 1817 # See whether it is a core checker to disable. That means we do not want 1818 # to emit a report from that checker so we have to silence it. 1819 if (index($Checker, "core") == 0) { 1820 $Options{SilenceCheckers}{$Checker} = $NumArgs; 1821 } else { 1822 $Options{DisableCheckers}{$Checker} = $NumArgs; 1823 delete $Options{EnableCheckers}{$Checker}; 1824 } 1825 next; 1826 } 1827 1828 if ($arg eq "--exclude") { 1829 shift @$Args; 1830 my $arg = shift @$Args; 1831 # Remove the trailing slash if any 1832 $arg =~ s|/$||; 1833 push @{$Options{Excludes}}, $arg; 1834 next; 1835 } 1836 1837 if ($arg eq "-load-plugin") { 1838 shift @$Args; 1839 push @{$Options{PluginsToLoad}}, shift @$Args; 1840 next; 1841 } 1842 1843 if ($arg eq "--use-analyzer") { 1844 shift @$Args; 1845 $Options{AnalyzerDiscoveryMethod} = shift @$Args; 1846 next; 1847 } 1848 1849 if ($arg =~ /^--use-analyzer=(.+)$/) { 1850 shift @$Args; 1851 $Options{AnalyzerDiscoveryMethod} = $1; 1852 next; 1853 } 1854 1855 if ($arg eq "--keep-empty") { 1856 shift @$Args; 1857 $Options{KeepEmpty} = 1; 1858 next; 1859 } 1860 1861 if ($arg eq "--override-compiler") { 1862 shift @$Args; 1863 $Options{OverrideCompiler} = 1; 1864 next; 1865 } 1866 1867 if ($arg eq "--force-analyze-debug-code") { 1868 shift @$Args; 1869 $Options{ForceAnalyzeDebugCode} = 1; 1870 next; 1871 } 1872 1873 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); 1874 1875 $NumArgs--; 1876 last; 1877 } 1878 return $NumArgs; 1879} 1880 1881if (!@ARGV) { 1882 $ForceDisplayHelp = 1 1883} 1884 1885ProcessArgs(\@ARGV); 1886# All arguments are now shifted from @ARGV. The rest is a build command, if any. 1887 1888my $ClangNotFoundErrMsg = FindClang(); 1889 1890if ($ForceDisplayHelp || $RequestDisplayHelp) { 1891 DisplayHelp($ClangNotFoundErrMsg); 1892 exit $ForceDisplayHelp; 1893} 1894 1895$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV))); 1896 1897if ($Options{GenerateIndex}) { 1898 $ClangVersion = "unknown"; 1899 Finalize($Options{OutputDir}, 0); 1900} 1901 1902# Make sure to use "" to handle paths with spaces. 1903$ClangVersion = HtmlEscape(`"$Clang" --version`); 1904 1905if (!@ARGV and !$RequestDisplayHelp) { 1906 ErrorDiag("No build command specified.\n\n"); 1907 $ForceDisplayHelp = 1; 1908} 1909 1910# Determine the output directory for the HTML reports. 1911my $BaseDir = $Options{OutputDir}; 1912$Options{OutputDir} = GetHTMLRunDir($Options{OutputDir}); 1913 1914DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg); 1915 1916$ClangCXX = $Clang; 1917if ($Clang !~ /\+\+(\.exe)?$/) { 1918 # If $Clang holds the name of the clang++ executable then we leave 1919 # $ClangCXX and $Clang equal, otherwise construct the name of the clang++ 1920 # executable from the clang executable name. 1921 1922 # Determine operating system under which this copy of Perl was built. 1923 my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/); 1924 if($IsWinBuild) { 1925 $ClangCXX =~ s/.exe$/++.exe/; 1926 } 1927 else { 1928 $ClangCXX =~ s/\-\d+(\.\d+)?$//; 1929 $ClangCXX .= "++"; 1930 } 1931} 1932 1933# Determine the location of ccc-analyzer. 1934my $AbsRealBin = Cwd::realpath($RealBin); 1935my $Cmd = "$AbsRealBin/../libexec/ccc-analyzer"; 1936my $CmdCXX = "$AbsRealBin/../libexec/c++-analyzer"; 1937 1938# Portability: use less strict but portable check -e (file exists) instead of 1939# non-portable -x (file is executable). On some windows ports -x just checks 1940# file extension to determine if a file is executable (see Perl language 1941# reference, perlport) 1942if (!defined $Cmd || ! -e $Cmd) { 1943 $Cmd = "$AbsRealBin/ccc-analyzer"; 1944 DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd); 1945} 1946if (!defined $CmdCXX || ! -e $CmdCXX) { 1947 $CmdCXX = "$AbsRealBin/c++-analyzer"; 1948 DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX); 1949} 1950 1951Diag("Using '$Clang' for static analysis\n"); 1952 1953SetHtmlEnv(\@ARGV, $Options{OutputDir}); 1954 1955my @AnalysesToRun; 1956foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} } 1957 keys %{$Options{EnableCheckers}}) { 1958 # Push checkers in order they were enabled. 1959 push @AnalysesToRun, "-analyzer-checker", $_; 1960} 1961foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} } 1962 keys %{$Options{DisableCheckers}}) { 1963 # Push checkers in order they were disabled. 1964 push @AnalysesToRun, "-analyzer-disable-checker", $_; 1965} 1966if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; } 1967if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } 1968if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; } 1969 1970# Delay setting up other environment variables in case we can do true 1971# interposition. 1972my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun; 1973my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}}; 1974my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}}; 1975 1976if (%{$Options{SilenceCheckers}}) { 1977 $CCC_ANALYZER_CONFIG = 1978 $CCC_ANALYZER_CONFIG." -analyzer-config silence-checkers=" 1979 .join(';', sort { 1980 $Options{SilenceCheckers}{$a} <=> 1981 $Options{SilenceCheckers}{$b} 1982 } keys %{$Options{SilenceCheckers}}); 1983} 1984 1985my %EnvVars = ( 1986 'CC' => $Cmd, 1987 'CXX' => $CmdCXX, 1988 'CLANG' => $Clang, 1989 'CLANG_CXX' => $ClangCXX, 1990 'VERBOSE' => $Options{Verbose}, 1991 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS, 1992 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS, 1993 'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG, 1994 'OUTPUT_DIR' => $Options{OutputDir}, 1995 'CCC_CC' => $Options{UseCC}, 1996 'CCC_CXX' => $Options{UseCXX}, 1997 'CCC_REPORT_FAILURES' => $Options{ReportFailures}, 1998 'CCC_ANALYZER_STORE_MODEL' => $Options{StoreModel}, 1999 'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel}, 2000 'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats}, 2001 'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat}, 2002 'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget}, 2003 'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE' => $Options{ForceAnalyzeDebugCode} 2004); 2005 2006# Run the build. 2007my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Options{KeepCC}, 2008 $Cmd, $CmdCXX, \%EnvVars); 2009 2010Finalize($BaseDir, $ExitStatus); 2011