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