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