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 726<h2>Filter Results by File</h2> 727<input 728 type="text" 729 id="file_input" 730 onkeyup="searchFiles()" 731 placeholder="Enter a path or filename" 732 title="Enter a path or filename"> 733 734<h2>Reports</h2> 735 736<table id="reports_table" class="sortable" style="table-layout:automatic"> 737<thead><tr> 738 <td>Bug Group</td> 739 <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind"> ▾</span></td> 740 <td>File</td> 741 <td>Function/Method</td> 742 <td class="Q">Line</td> 743 <td class="Q">Path Length</td> 744ENDTEXT 745 746if ($Options{ShowDescription}) { 747print OUT <<ENDTEXT; 748 <td class="Q">Description</td> 749ENDTEXT 750} 751 752print OUT <<ENDTEXT; 753 <td class="sorttable_nosort"></td> 754 <!-- REPORTBUGCOL --> 755</tr></thead> 756<tbody> 757ENDTEXT 758 759 my $prefix = GetPrefix(); 760 my $regex; 761 my $InFileRegex; 762 my $InFilePrefix = "File:</td><td>"; 763 764 if (defined $prefix) { 765 $regex = qr/^\Q$prefix\E/is; 766 $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is; 767 } 768 769 for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) { 770 my $x = "$row->[1]:$row->[2]"; 771 $x = lc $x; 772 $x =~ s/[ ,'":\/()]+/_/g; 773 774 my $ReportFile = $row->[0]; 775 776 print OUT "<tr class=\"bt_$x\">"; 777 print OUT "<td class=\"DESC\">"; 778 print OUT $row->[1]; # $BugCategory 779 print OUT "</td>"; 780 print OUT "<td class=\"DESC\">"; 781 print OUT $row->[2]; # $BugType 782 print OUT "</td>"; 783 784 # Update the file prefix. 785 my $fname = $row->[3]; 786 787 if (defined $regex) { 788 $fname =~ s/$regex//; 789 UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix) 790 } 791 792 print OUT "<td>"; 793 my @fname = split /\//,$fname; 794 if ($#fname > 0) { 795 while ($#fname >= 0) { 796 my $x = shift @fname; 797 print OUT $x; 798 if ($#fname >= 0) { 799 print OUT "/"; 800 } 801 } 802 } 803 else { 804 print OUT $fname; 805 } 806 print OUT "</td>"; 807 808 print OUT "<td class=\"DESC\">"; 809 print OUT $row->[4]; # Function 810 print OUT "</td>"; 811 812 # Print out the quantities. 813 for my $j ( 5 .. 6 ) { # Line & Path length 814 print OUT "<td class=\"Q\">$row->[$j]</td>"; 815 } 816 817 # Print the rest of the columns. 818 for (my $j = 7; $j <= $#{$row}; ++$j) { 819 print OUT "<td>$row->[$j]</td>" 820 } 821 822 # Emit the "View" link. 823 print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>"; 824 825 # Emit REPORTBUG markers. 826 print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n"; 827 828 # End the row. 829 print OUT "</tr>\n"; 830 } 831 832 print OUT "</tbody>\n</table>\n\n"; 833 } 834 835 if (scalar (@failures) || scalar(@attributes_ignored)) { 836 print OUT "<h2>Analyzer Failures</h2>\n"; 837 838 if (scalar @attributes_ignored) { 839 print OUT "The analyzer's parser ignored the following attributes:<p>\n"; 840 print OUT "<table>\n"; 841 print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; 842 foreach my $file (sort @attributes_ignored) { 843 die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/)); 844 my $attribute = $1; 845 # Open the attribute file to get the first file that failed. 846 next if (!open (ATTR, "$Dir/failures/$file")); 847 my $ppfile = <ATTR>; 848 chomp $ppfile; 849 close ATTR; 850 next if (! -e "$Dir/failures/$ppfile"); 851 # Open the info file and get the name of the source file. 852 open (INFO, "$Dir/failures/$ppfile.info.txt") or 853 die "Cannot open $Dir/failures/$ppfile.info.txt\n"; 854 my $srcfile = <INFO>; 855 chomp $srcfile; 856 close (INFO); 857 # Print the information in the table. 858 my $prefix = GetPrefix(); 859 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } 860 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"; 861 my $ppfile_clang = $ppfile; 862 $ppfile_clang =~ s/[.](.+)$/.clang.$1/; 863 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; 864 } 865 print OUT "</table>\n"; 866 } 867 868 if (scalar @failures) { 869 print OUT "<p>The analyzer had problems processing the following files:</p>\n"; 870 print OUT "<table>\n"; 871 print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n"; 872 foreach my $file (sort @failures) { 873 $file =~ /(.+).info.txt$/; 874 # Get the preprocessed file. 875 my $ppfile = $1; 876 # Open the info file and get the name of the source file. 877 open (INFO, "$Dir/failures/$file") or 878 die "Cannot open $Dir/failures/$file\n"; 879 my $srcfile = <INFO>; 880 chomp $srcfile; 881 my $problem = <INFO>; 882 chomp $problem; 883 close (INFO); 884 # Print the information in the table. 885 my $prefix = GetPrefix(); 886 if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; } 887 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"; 888 my $ppfile_clang = $ppfile; 889 $ppfile_clang =~ s/[.](.+)$/.clang.$1/; 890 print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n"; 891 } 892 print OUT "</table>\n"; 893 } 894 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"; 895 } 896 897 print OUT "</body></html>\n"; 898 close(OUT); 899 CopyFiles($Dir); 900 901 # Make sure $Dir and $BaseDir are world readable/executable. 902 chmod(0755, $Dir); 903 if (defined $BaseDir) { chmod(0755, $BaseDir); } 904 905 # Print statistics 906 print CalcStats(\@Stats) if $AnalyzerStats; 907 908 my $Num = scalar(@Index); 909 if ($Num == 1) { 910 Diag("$Num bug found.\n"); 911 } else { 912 Diag("$Num bugs found.\n"); 913 } 914 if ($Num > 0 && -r "$Dir/index.html") { 915 Diag("Run 'scan-view $Dir' to examine bug reports.\n"); 916 } 917 918 DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored); 919 920 return $Num; 921} 922 923sub Finalize { 924 my $BaseDir = shift; 925 my $ExitStatus = shift; 926 927 Diag "Analysis run complete.\n"; 928 if (defined $Options{OutputFormat}) { 929 if ($Options{OutputFormat} =~ /plist/ || 930 $Options{OutputFormat} =~ /sarif/) { 931 Diag "Analysis results (" . 932 ($Options{OutputFormat} =~ /plist/ ? "plist" : "sarif") . 933 " files) deposited in '$Options{OutputDir}'\n"; 934 } 935 if ($Options{OutputFormat} =~ /html/) { 936 # Postprocess the HTML directory. 937 my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir, 938 $Options{AnalyzerStats}, $Options{KeepEmpty}); 939 940 if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") { 941 Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n"; 942 my $ScanView = Cwd::realpath("$RealBin/scan-view"); 943 if (! -x $ScanView) { $ScanView = "scan-view"; } 944 if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); } 945 if (! -x $ScanView) { $ScanView = `which scan-view`; chomp $ScanView; } 946 exec $ScanView, "$Options{OutputDir}"; 947 } 948 949 if ($Options{ExitStatusFoundBugs}) { 950 exit 1 if ($NumBugs > 0); 951 exit $ExitStatus; 952 } 953 } 954 } 955 956 exit $ExitStatus; 957} 958 959##----------------------------------------------------------------------------## 960# RunBuildCommand - Run the build command. 961##----------------------------------------------------------------------------## 962 963sub AddIfNotPresent { 964 my $Args = shift; 965 my $Arg = shift; 966 my $found = 0; 967 968 foreach my $k (@$Args) { 969 if ($k eq $Arg) { 970 $found = 1; 971 last; 972 } 973 } 974 975 if ($found == 0) { 976 push @$Args, $Arg; 977 } 978} 979 980sub SetEnv { 981 my $EnvVars = shift @_; 982 foreach my $var ('CC', 'CXX', 'CLANG', 'CLANG_CXX', 983 'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS', 984 'CCC_ANALYZER_CONFIG') { 985 die "$var is undefined\n" if (!defined $var); 986 $ENV{$var} = $EnvVars->{$var}; 987 } 988 foreach my $var ('CCC_ANALYZER_CONSTRAINTS_MODEL', 989 'CCC_ANALYZER_INTERNAL_STATS', 990 'CCC_ANALYZER_OUTPUT_FORMAT', 991 'CCC_CC', 992 'CCC_CXX', 993 'CCC_REPORT_FAILURES', 994 'CLANG_ANALYZER_TARGET', 995 'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE') { 996 my $x = $EnvVars->{$var}; 997 if (defined $x) { $ENV{$var} = $x } 998 } 999 my $Verbose = $EnvVars->{'VERBOSE'}; 1000 if ($Verbose >= 2) { 1001 $ENV{'CCC_ANALYZER_VERBOSE'} = 1; 1002 } 1003 if ($Verbose >= 3) { 1004 $ENV{'CCC_ANALYZER_LOG'} = 1; 1005 } 1006} 1007 1008sub RunXcodebuild { 1009 my $Args = shift; 1010 my $IgnoreErrors = shift; 1011 my $CCAnalyzer = shift; 1012 my $CXXAnalyzer = shift; 1013 my $EnvVars = shift; 1014 1015 if ($IgnoreErrors) { 1016 AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES"); 1017 } 1018 1019 # Detect the version of Xcode. If Xcode 4.6 or higher, use new 1020 # in situ support for analyzer interposition without needed to override 1021 # the compiler. 1022 open(DETECT_XCODE, "-|", $Args->[0], "-version") or 1023 die "error: cannot detect version of xcodebuild\n"; 1024 1025 my $oldBehavior = 1; 1026 1027 while(<DETECT_XCODE>) { 1028 if (/^Xcode (.+)$/) { 1029 my $ver = $1; 1030 if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) { 1031 if ($1 >= 4.6) { 1032 $oldBehavior = 0; 1033 last; 1034 } 1035 } 1036 } 1037 } 1038 close(DETECT_XCODE); 1039 1040 # If --override-compiler is explicitly requested, resort to the old 1041 # behavior regardless of Xcode version. 1042 if ($Options{OverrideCompiler}) { 1043 $oldBehavior = 1; 1044 } 1045 1046 if ($oldBehavior == 0) { 1047 my $OutputDir = $EnvVars->{"OUTPUT_DIR"}; 1048 my $CLANG = $EnvVars->{"CLANG"}; 1049 my $OtherFlags = $EnvVars->{"CCC_ANALYZER_ANALYSIS"} . " " 1050 . $EnvVars->{"CCC_ANALYZER_CONFIG"}; 1051 push @$Args, 1052 "RUN_CLANG_STATIC_ANALYZER=YES", 1053 "CLANG_ANALYZER_OUTPUT=plist-html", 1054 "CLANG_ANALYZER_EXEC=$CLANG", 1055 "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir", 1056 "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags"; 1057 1058 return (system(@$Args) >> 8); 1059 } 1060 1061 # Default to old behavior where we insert a bogus compiler. 1062 SetEnv($EnvVars); 1063 1064 # Check if using iPhone SDK 3.0 (simulator). If so the compiler being 1065 # used should be gcc-4.2. 1066 if (!defined $ENV{"CCC_CC"}) { 1067 for (my $i = 0 ; $i < scalar(@$Args); ++$i) { 1068 if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) { 1069 if (@$Args[$i+1] =~ /^iphonesimulator3/) { 1070 $ENV{"CCC_CC"} = "gcc-4.2"; 1071 $ENV{"CCC_CXX"} = "g++-4.2"; 1072 } 1073 } 1074 } 1075 } 1076 1077 # Disable PCH files until clang supports them. 1078 AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO"); 1079 1080 # When 'CC' is set, xcodebuild uses it to do all linking, even if we are 1081 # linking C++ object files. Set 'LDPLUSPLUS' so that xcodebuild uses 'g++' 1082 # (via c++-analyzer) when linking such files. 1083 $ENV{"LDPLUSPLUS"} = $CXXAnalyzer; 1084 1085 return (system(@$Args) >> 8); 1086} 1087 1088sub RunBuildCommand { 1089 my $Args = shift; 1090 my $IgnoreErrors = shift; 1091 my $KeepCC = shift; 1092 my $Cmd = $Args->[0]; 1093 my $CCAnalyzer = shift; 1094 my $CXXAnalyzer = shift; 1095 my $EnvVars = shift; 1096 1097 if ($Cmd =~ /\bxcodebuild$/) { 1098 return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $EnvVars); 1099 } 1100 1101 # Setup the environment. 1102 SetEnv($EnvVars); 1103 1104 if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 1105 $Cmd =~ /(.*\/?cc[^\/]*$)/ or 1106 $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or 1107 $Cmd =~ /(.*\/?clang[^\/]*$)/ or 1108 $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) { 1109 1110 if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) { 1111 $ENV{"CCC_CC"} = $1; 1112 } 1113 1114 shift @$Args; 1115 unshift @$Args, $CCAnalyzer; 1116 } 1117 elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or 1118 $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or 1119 $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or 1120 $Cmd =~ /(.*\/?clang\+\+$)/ or 1121 $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) { 1122 if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) { 1123 $ENV{"CCC_CXX"} = $1; 1124 } 1125 shift @$Args; 1126 unshift @$Args, $CXXAnalyzer; 1127 } 1128 elsif ($Cmd eq "make" or $Cmd eq "gmake" or $Cmd eq "mingw32-make") { 1129 if (!$KeepCC) { 1130 AddIfNotPresent($Args, "CC=$CCAnalyzer"); 1131 AddIfNotPresent($Args, "CXX=$CXXAnalyzer"); 1132 } 1133 if ($IgnoreErrors) { 1134 AddIfNotPresent($Args,"-k"); 1135 AddIfNotPresent($Args,"-i"); 1136 } 1137 } 1138 1139 return (system(@$Args) >> 8); 1140} 1141 1142##----------------------------------------------------------------------------## 1143# DisplayHelp - Utility function to display all help options. 1144##----------------------------------------------------------------------------## 1145 1146sub DisplayHelp { 1147 1148 my $ArgClangNotFoundErrMsg = shift; 1149print <<ENDTEXT; 1150USAGE: $Prog [options] <build command> [build options] 1151 1152ENDTEXT 1153 1154 if (defined $BuildName) { 1155 print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n"; 1156 } 1157 1158print <<ENDTEXT; 1159OPTIONS: 1160 1161 -analyze-headers 1162 1163 Also analyze functions in #included files. By default, such functions 1164 are skipped unless they are called by functions within the main source file. 1165 1166 --force-analyze-debug-code 1167 1168 Tells analyzer to enable assertions in code even if they were disabled 1169 during compilation to enable more precise results. 1170 1171 -o <output location> 1172 1173 Specifies the output directory for analyzer reports. Subdirectories will be 1174 created as needed to represent separate "runs" of the analyzer. If this 1175 option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X) 1176 to store the reports. 1177 1178 -h 1179 --help 1180 1181 Display this message. 1182 1183 -k 1184 --keep-going 1185 1186 Add a "keep on going" option to the specified build command. This option 1187 currently supports make and xcodebuild. This is a convenience option; one 1188 can specify this behavior directly using build options. 1189 1190 --keep-cc 1191 1192 Do not override CC and CXX make variables. Useful when running make in 1193 autoconf-based (and similar) projects where configure can add extra flags 1194 to those variables. 1195 1196 --html-title [title] 1197 --html-title=[title] 1198 1199 Specify the title used on generated HTML pages. If not specified, a default 1200 title will be used. 1201 1202 --show-description 1203 1204 Display the description of defects in the list 1205 1206 -sarif 1207 1208 By default the output of scan-build is a set of HTML files. This option 1209 outputs the results in SARIF format. 1210 1211 -plist 1212 1213 By default the output of scan-build is a set of HTML files. This option 1214 outputs the results as a set of .plist files. 1215 1216 -plist-html 1217 1218 By default the output of scan-build is a set of HTML files. This option 1219 outputs the results as a set of HTML and .plist files. 1220 1221 --status-bugs 1222 1223 By default, the exit status of scan-build is the same as the executed build 1224 command. Specifying this option causes the exit status of scan-build to be 1 1225 if it found potential bugs and the exit status of the build itself otherwise. 1226 1227 --exclude <path> 1228 1229 Do not run static analyzer against files found in this 1230 directory (You can specify this option multiple times). 1231 Could be useful when project contains 3rd party libraries. 1232 1233 --use-cc [compiler path] 1234 --use-cc=[compiler path] 1235 1236 scan-build analyzes a project by interposing a "fake compiler", which 1237 executes a real compiler for compilation and the static analyzer for analysis. 1238 Because of the current implementation of interposition, scan-build does not 1239 know what compiler your project normally uses. Instead, it simply overrides 1240 the CC environment variable, and guesses your default compiler. 1241 1242 In the future, this interposition mechanism to be improved, but if you need 1243 scan-build to use a specific compiler for *compilation* then you can use 1244 this option to specify a path to that compiler. 1245 1246 If the given compiler is a cross compiler, you may also need to provide 1247 --analyzer-target option to properly analyze the source code because static 1248 analyzer runs as if the code is compiled for the host machine by default. 1249 1250 --use-c++ [compiler path] 1251 --use-c++=[compiler path] 1252 1253 This is the same as "--use-cc" but for C++ code. 1254 1255 --analyzer-target [target triple name for analysis] 1256 --analyzer-target=[target triple name for analysis] 1257 1258 This provides target triple information to clang static analyzer. 1259 It only changes the target for analysis but doesn't change the target of a 1260 real compiler given by --use-cc and --use-c++ options. 1261 1262 -v 1263 1264 Enable verbose output from scan-build. A second and third '-v' increases 1265 verbosity. 1266 1267 -V 1268 --view 1269 1270 View analysis results in a web browser when the build completes. 1271 1272 --generate-index-only <output location> 1273 1274 Do not perform the analysis, but only regenerate the index.html file 1275 from existing report.html files. Useful for making a custom Static Analyzer 1276 integration into a build system that isn't otherwise supported by scan-build. 1277 1278ADVANCED OPTIONS: 1279 1280 -no-failure-reports 1281 1282 Do not create a 'failures' subdirectory that includes analyzer crash reports 1283 and preprocessed source files. 1284 1285 -stats 1286 1287 Generates visitation statistics for the project being analyzed. 1288 1289 -maxloop <loop count> 1290 1291 Specify the number of times a block can be visited before giving up. 1292 Default is 4. Increase for more comprehensive coverage at a cost of speed. 1293 1294 -internal-stats 1295 1296 Generate internal analyzer statistics. 1297 1298 --use-analyzer [Xcode|path to clang] 1299 --use-analyzer=[Xcode|path to clang] 1300 1301 scan-build uses the 'clang' executable relative to itself for static 1302 analysis. One can override this behavior with this option by using the 1303 'clang' packaged with Xcode (on OS X) or from the PATH. 1304 1305 --keep-empty 1306 1307 Don't remove the build results directory even if no issues were reported. 1308 1309 --override-compiler 1310 Always resort to the ccc-analyzer even when better interposition methods 1311 are available. 1312 1313 -analyzer-config <options> 1314 1315 Provide options to pass through to the analyzer's -analyzer-config flag. 1316 Several options are separated with comma: 'key1=val1,key2=val2' 1317 1318 Available options: 1319 * stable-report-filename=true or false (default) 1320 Switch the page naming to: 1321 report-<filename>-<function/method name>-<id>.html 1322 instead of report-XXXXXX.html 1323 1324CONTROLLING CHECKERS: 1325 1326 A default group of checkers are always run unless explicitly disabled. 1327 Checkers may be enabled/disabled using the following options: 1328 1329 -enable-checker [checker name] 1330 -disable-checker [checker name] 1331 1332LOADING CHECKERS: 1333 1334 Loading external checkers using the clang plugin interface: 1335 1336 -load-plugin [plugin library] 1337ENDTEXT 1338 1339 if (defined $Clang && -x $Clang) { 1340 # Query clang for list of checkers that are enabled. 1341 1342 # create a list to load the plugins via the 'Xclang' command line 1343 # argument 1344 my @PluginLoadCommandline_xclang; 1345 foreach my $param ( @{$Options{PluginsToLoad}} ) { 1346 push ( @PluginLoadCommandline_xclang, "-Xclang" ); 1347 push ( @PluginLoadCommandline_xclang, "-load" ); 1348 push ( @PluginLoadCommandline_xclang, "-Xclang" ); 1349 push ( @PluginLoadCommandline_xclang, $param ); 1350 } 1351 1352 my %EnabledCheckers; 1353 foreach my $lang ("c", "objective-c", "objective-c++", "c++") { 1354 my $ExecLine = join(' ', qq/"$Clang"/, @PluginLoadCommandline_xclang, "--analyze", "-x", $lang, "-", "-###", "2>&1", "|"); 1355 open(PS, $ExecLine); 1356 while (<PS>) { 1357 foreach my $val (split /\s+/) { 1358 $val =~ s/\"//g; 1359 if ($val =~ /-analyzer-checker\=([^\s]+)/) { 1360 $EnabledCheckers{$1} = 1; 1361 } 1362 } 1363 } 1364 } 1365 1366 # Query clang for complete list of checkers. 1367 my @PluginLoadCommandline; 1368 foreach my $param ( @{$Options{PluginsToLoad}} ) { 1369 push ( @PluginLoadCommandline, "-load" ); 1370 push ( @PluginLoadCommandline, $param ); 1371 } 1372 1373 my $ExecLine = join(' ', qq/"$Clang"/, "-cc1", @PluginLoadCommandline, "-analyzer-checker-help", "2>&1", "|"); 1374 open(PS, $ExecLine); 1375 my $foundCheckers = 0; 1376 while (<PS>) { 1377 if (/CHECKERS:/) { 1378 $foundCheckers = 1; 1379 last; 1380 } 1381 } 1382 if (!$foundCheckers) { 1383 print " *** Could not query Clang for the list of available checkers."; 1384 } 1385 else { 1386 print("\nAVAILABLE CHECKERS:\n\n"); 1387 my $skip = 0; 1388 while(<PS>) { 1389 if (/experimental/) { 1390 $skip = 1; 1391 next; 1392 } 1393 if ($skip) { 1394 next if (!/^\s\s[^\s]/); 1395 $skip = 0; 1396 } 1397 s/^\s\s//; 1398 if (/^([^\s]+)/) { 1399 # Is the checker enabled? 1400 my $checker = $1; 1401 my $enabled = 0; 1402 my $aggregate = ""; 1403 foreach my $domain (split /\./, $checker) { 1404 $aggregate .= $domain; 1405 if ($EnabledCheckers{$aggregate}) { 1406 $enabled =1; 1407 last; 1408 } 1409 # append a dot, if an additional domain is added in the next iteration 1410 $aggregate .= "."; 1411 } 1412 1413 if ($enabled) { 1414 print " + "; 1415 } 1416 else { 1417 print " "; 1418 } 1419 } 1420 else { 1421 print " "; 1422 } 1423 print $_; 1424 } 1425 print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n"; 1426 } 1427 close PS; 1428 } 1429 else { 1430 print " *** Could not query Clang for the list of available checkers.\n"; 1431 if (defined $ArgClangNotFoundErrMsg) { 1432 print " *** Reason: $ArgClangNotFoundErrMsg\n"; 1433 } 1434 } 1435 1436print <<ENDTEXT 1437 1438BUILD OPTIONS 1439 1440 You can specify any build option acceptable to the build command. 1441 1442EXAMPLE 1443 1444 scan-build -o /tmp/myhtmldir make -j4 1445 1446The above example causes analysis reports to be deposited into a subdirectory 1447of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different 1448subdirectory is created each time scan-build analyzes a project. The analyzer 1449should support most parallel builds, but not distributed builds. 1450 1451ENDTEXT 1452} 1453 1454##----------------------------------------------------------------------------## 1455# HtmlEscape - HTML entity encode characters that are special in HTML 1456##----------------------------------------------------------------------------## 1457 1458sub HtmlEscape { 1459 # copy argument to new variable so we don't clobber the original 1460 my $arg = shift || ''; 1461 my $tmp = $arg; 1462 $tmp =~ s/&/&/g; 1463 $tmp =~ s/</</g; 1464 $tmp =~ s/>/>/g; 1465 return $tmp; 1466} 1467 1468##----------------------------------------------------------------------------## 1469# ShellEscape - backslash escape characters that are special to the shell 1470##----------------------------------------------------------------------------## 1471 1472sub ShellEscape { 1473 # copy argument to new variable so we don't clobber the original 1474 my $arg = shift || ''; 1475 if ($arg =~ /["\s]/) { return "'" . $arg . "'"; } 1476 return $arg; 1477} 1478 1479##----------------------------------------------------------------------------## 1480# FindXcrun - searches for the 'xcrun' executable. Returns "" if not found. 1481##----------------------------------------------------------------------------## 1482 1483sub FindXcrun { 1484 my $xcrun = `which xcrun`; 1485 chomp $xcrun; 1486 return $xcrun; 1487} 1488 1489##----------------------------------------------------------------------------## 1490# FindClang - searches for 'clang' executable. 1491##----------------------------------------------------------------------------## 1492 1493sub FindClang { 1494 if (!defined $Options{AnalyzerDiscoveryMethod}) { 1495 $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang"); 1496 if (!defined $Clang || ! -x $Clang) { 1497 $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang"); 1498 if (!defined $Clang || ! -x $Clang) { 1499 # When an Xcode toolchain is present, look for a clang in the sibling bin 1500 # of the parent of the bin directory. So if scan-build is at 1501 # $TOOLCHAIN/usr/local/bin/scan-build look for clang at 1502 # $TOOLCHAIN/usr/bin/clang. 1503 my $has_xcode_toolchain = FindXcrun() ne ""; 1504 if ($has_xcode_toolchain && -f "$RealBin/../../bin/clang") { 1505 $Clang = Cwd::realpath("$RealBin/../../bin/clang"); 1506 } 1507 } 1508 } 1509 if (!defined $Clang || ! -x $Clang) { 1510 return "error: Cannot find an executable 'clang' relative to" . 1511 " scan-build. Consider using --use-analyzer to pick a version of" . 1512 " 'clang' to use for static analysis.\n"; 1513 } 1514 } 1515 else { 1516 if ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) { 1517 my $xcrun = FindXcrun(); 1518 if ($xcrun eq "") { 1519 return "Cannot find 'xcrun' to find 'clang' for analysis.\n"; 1520 } 1521 $Clang = `$xcrun -toolchain XcodeDefault -find clang`; 1522 chomp $Clang; 1523 if ($Clang eq "") { 1524 return "No 'clang' executable found by 'xcrun'\n"; 1525 } 1526 } 1527 else { 1528 $Clang = $Options{AnalyzerDiscoveryMethod}; 1529 if (!defined $Clang or not -x $Clang) { 1530 return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n"; 1531 } 1532 } 1533 } 1534 return undef; 1535} 1536 1537##----------------------------------------------------------------------------## 1538# Process command-line arguments. 1539##----------------------------------------------------------------------------## 1540 1541my $RequestDisplayHelp = 0; 1542my $ForceDisplayHelp = 0; 1543 1544sub ProcessArgs { 1545 my $Args = shift; 1546 my $NumArgs = 0; 1547 1548 while (@$Args) { 1549 1550 $NumArgs++; 1551 1552 # Scan for options we recognize. 1553 1554 my $arg = $Args->[0]; 1555 1556 if ($arg eq "-h" or $arg eq "--help") { 1557 $RequestDisplayHelp = 1; 1558 shift @$Args; 1559 next; 1560 } 1561 1562 if ($arg eq '-analyze-headers') { 1563 shift @$Args; 1564 $Options{AnalyzeHeaders} = 1; 1565 next; 1566 } 1567 1568 if ($arg eq "-o") { 1569 if (defined($Options{OutputDir})) { 1570 DieDiag("Only one of '-o' or '--generate-index-only' can be specified.\n"); 1571 } 1572 1573 shift @$Args; 1574 1575 if (!@$Args) { 1576 DieDiag("'-o' option requires a target directory name.\n"); 1577 } 1578 1579 # Construct an absolute path. Uses the current working directory 1580 # as a base if the original path was not absolute. 1581 my $OutDir = shift @$Args; 1582 mkpath($OutDir) unless (-e $OutDir); # abs_path wants existing dir 1583 $Options{OutputDir} = abs_path($OutDir); 1584 1585 next; 1586 } 1587 1588 if ($arg eq "--generate-index-only") { 1589 if (defined($Options{OutputDir})) { 1590 DieDiag("Only one of '-o' or '--generate-index-only' can be specified.\n"); 1591 } 1592 1593 shift @$Args; 1594 1595 if (!@$Args) { 1596 DieDiag("'--generate-index-only' option requires a target directory name.\n"); 1597 } 1598 1599 # Construct an absolute path. Uses the current working directory 1600 # as a base if the original path was not absolute. 1601 my $OutDir = shift @$Args; 1602 mkpath($OutDir) unless (-e $OutDir); # abs_path wants existing dir 1603 $Options{OutputDir} = abs_path($OutDir); 1604 $Options{GenerateIndex} = 1; 1605 1606 next; 1607 } 1608 1609 if ($arg =~ /^--html-title(=(.+))?$/) { 1610 shift @$Args; 1611 1612 if (!defined $2 || $2 eq '') { 1613 if (!@$Args) { 1614 DieDiag("'--html-title' option requires a string.\n"); 1615 } 1616 1617 $Options{HtmlTitle} = shift @$Args; 1618 } else { 1619 $Options{HtmlTitle} = $2; 1620 } 1621 1622 next; 1623 } 1624 1625 if ($arg eq "-k" or $arg eq "--keep-going") { 1626 shift @$Args; 1627 $Options{IgnoreErrors} = 1; 1628 next; 1629 } 1630 1631 if ($arg eq "--keep-cc") { 1632 shift @$Args; 1633 $Options{KeepCC} = 1; 1634 next; 1635 } 1636 1637 if ($arg =~ /^--use-cc(=(.+))?$/) { 1638 shift @$Args; 1639 my $cc; 1640 1641 if (!defined $2 || $2 eq "") { 1642 if (!@$Args) { 1643 DieDiag("'--use-cc' option requires a compiler executable name.\n"); 1644 } 1645 $cc = shift @$Args; 1646 } 1647 else { 1648 $cc = $2; 1649 } 1650 1651 $Options{UseCC} = $cc; 1652 next; 1653 } 1654 1655 if ($arg =~ /^--use-c\+\+(=(.+))?$/) { 1656 shift @$Args; 1657 my $cxx; 1658 1659 if (!defined $2 || $2 eq "") { 1660 if (!@$Args) { 1661 DieDiag("'--use-c++' option requires a compiler executable name.\n"); 1662 } 1663 $cxx = shift @$Args; 1664 } 1665 else { 1666 $cxx = $2; 1667 } 1668 1669 $Options{UseCXX} = $cxx; 1670 next; 1671 } 1672 1673 if ($arg =~ /^--analyzer-target(=(.+))?$/) { 1674 shift @ARGV; 1675 my $AnalyzerTarget; 1676 1677 if (!defined $2 || $2 eq "") { 1678 if (!@ARGV) { 1679 DieDiag("'--analyzer-target' option requires a target triple name.\n"); 1680 } 1681 $AnalyzerTarget = shift @ARGV; 1682 } 1683 else { 1684 $AnalyzerTarget = $2; 1685 } 1686 1687 $Options{AnalyzerTarget} = $AnalyzerTarget; 1688 next; 1689 } 1690 1691 if ($arg eq "-v") { 1692 shift @$Args; 1693 $Options{Verbose}++; 1694 next; 1695 } 1696 1697 if ($arg eq "-V" or $arg eq "--view") { 1698 shift @$Args; 1699 $Options{ViewResults} = 1; 1700 next; 1701 } 1702 1703 if ($arg eq "--status-bugs") { 1704 shift @$Args; 1705 $Options{ExitStatusFoundBugs} = 1; 1706 next; 1707 } 1708 1709 if ($arg eq "--show-description") { 1710 shift @$Args; 1711 $Options{ShowDescription} = 1; 1712 next; 1713 } 1714 1715 if ($arg eq "-constraints") { 1716 shift @$Args; 1717 $Options{ConstraintsModel} = shift @$Args; 1718 next; 1719 } 1720 1721 if ($arg eq "-internal-stats") { 1722 shift @$Args; 1723 $Options{InternalStats} = 1; 1724 next; 1725 } 1726 1727 if ($arg eq "-sarif") { 1728 shift @$Args; 1729 $Options{OutputFormat} = "sarif"; 1730 next; 1731 } 1732 1733 if ($arg eq "-plist") { 1734 shift @$Args; 1735 $Options{OutputFormat} = "plist"; 1736 next; 1737 } 1738 1739 if ($arg eq "-plist-html") { 1740 shift @$Args; 1741 $Options{OutputFormat} = "plist-html"; 1742 next; 1743 } 1744 1745 if ($arg eq "-analyzer-config") { 1746 shift @$Args; 1747 push @{$Options{ConfigOptions}}, shift @$Args; 1748 next; 1749 } 1750 1751 if ($arg eq "-no-failure-reports") { 1752 shift @$Args; 1753 $Options{ReportFailures} = 0; 1754 next; 1755 } 1756 1757 if ($arg eq "-stats") { 1758 shift @$Args; 1759 $Options{AnalyzerStats} = 1; 1760 next; 1761 } 1762 1763 if ($arg eq "-maxloop") { 1764 shift @$Args; 1765 $Options{MaxLoop} = shift @$Args; 1766 next; 1767 } 1768 1769 if ($arg eq "-enable-checker") { 1770 shift @$Args; 1771 my $Checker = shift @$Args; 1772 # Store $NumArgs to preserve the order the checkers were enabled. 1773 $Options{EnableCheckers}{$Checker} = $NumArgs; 1774 delete $Options{DisableCheckers}{$Checker}; 1775 next; 1776 } 1777 1778 if ($arg eq "-disable-checker") { 1779 shift @$Args; 1780 my $Checker = shift @$Args; 1781 # Store $NumArgs to preserve the order the checkers are disabled/silenced. 1782 # See whether it is a core checker to disable. That means we do not want 1783 # to emit a report from that checker so we have to silence it. 1784 if (index($Checker, "core") == 0) { 1785 $Options{SilenceCheckers}{$Checker} = $NumArgs; 1786 } else { 1787 $Options{DisableCheckers}{$Checker} = $NumArgs; 1788 delete $Options{EnableCheckers}{$Checker}; 1789 } 1790 next; 1791 } 1792 1793 if ($arg eq "--exclude") { 1794 shift @$Args; 1795 my $arg = shift @$Args; 1796 # Remove the trailing slash if any 1797 $arg =~ s|/$||; 1798 push @{$Options{Excludes}}, $arg; 1799 next; 1800 } 1801 1802 if ($arg eq "-load-plugin") { 1803 shift @$Args; 1804 push @{$Options{PluginsToLoad}}, shift @$Args; 1805 next; 1806 } 1807 1808 if ($arg eq "--use-analyzer") { 1809 shift @$Args; 1810 $Options{AnalyzerDiscoveryMethod} = shift @$Args; 1811 next; 1812 } 1813 1814 if ($arg =~ /^--use-analyzer=(.+)$/) { 1815 shift @$Args; 1816 $Options{AnalyzerDiscoveryMethod} = $1; 1817 next; 1818 } 1819 1820 if ($arg eq "--keep-empty") { 1821 shift @$Args; 1822 $Options{KeepEmpty} = 1; 1823 next; 1824 } 1825 1826 if ($arg eq "--override-compiler") { 1827 shift @$Args; 1828 $Options{OverrideCompiler} = 1; 1829 next; 1830 } 1831 1832 if ($arg eq "--force-analyze-debug-code") { 1833 shift @$Args; 1834 $Options{ForceAnalyzeDebugCode} = 1; 1835 next; 1836 } 1837 1838 DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/); 1839 1840 $NumArgs--; 1841 last; 1842 } 1843 return $NumArgs; 1844} 1845 1846if (!@ARGV) { 1847 $ForceDisplayHelp = 1 1848} 1849 1850ProcessArgs(\@ARGV); 1851# All arguments are now shifted from @ARGV. The rest is a build command, if any. 1852 1853my $ClangNotFoundErrMsg = FindClang(); 1854 1855if ($ForceDisplayHelp || $RequestDisplayHelp) { 1856 DisplayHelp($ClangNotFoundErrMsg); 1857 exit $ForceDisplayHelp; 1858} 1859 1860$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV))); 1861 1862if ($Options{GenerateIndex}) { 1863 $ClangVersion = "unknown"; 1864 Finalize($Options{OutputDir}, 0); 1865} 1866 1867# Make sure to use "" to handle paths with spaces. 1868$ClangVersion = HtmlEscape(`"$Clang" --version`); 1869 1870if (!@ARGV and !$RequestDisplayHelp) { 1871 ErrorDiag("No build command specified.\n\n"); 1872 $ForceDisplayHelp = 1; 1873} 1874 1875# Determine the output directory for the HTML reports. 1876my $BaseDir = $Options{OutputDir}; 1877$Options{OutputDir} = GetHTMLRunDir($Options{OutputDir}); 1878 1879DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg); 1880 1881$ClangCXX = $Clang; 1882if ($Clang !~ /\+\+(\.exe)?$/) { 1883 # If $Clang holds the name of the clang++ executable then we leave 1884 # $ClangCXX and $Clang equal, otherwise construct the name of the clang++ 1885 # executable from the clang executable name. 1886 1887 # Determine operating system under which this copy of Perl was built. 1888 my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/); 1889 if($IsWinBuild) { 1890 $ClangCXX =~ s/.exe$/++.exe/; 1891 } 1892 else { 1893 $ClangCXX =~ s/\-\d+(\.\d+)?$//; 1894 $ClangCXX .= "++"; 1895 } 1896} 1897 1898# Determine the location of ccc-analyzer. 1899my $AbsRealBin = Cwd::realpath($RealBin); 1900my $Cmd = "$AbsRealBin/../libexec/ccc-analyzer"; 1901my $CmdCXX = "$AbsRealBin/../libexec/c++-analyzer"; 1902 1903# Portability: use less strict but portable check -e (file exists) instead of 1904# non-portable -x (file is executable). On some windows ports -x just checks 1905# file extension to determine if a file is executable (see Perl language 1906# reference, perlport) 1907if (!defined $Cmd || ! -e $Cmd) { 1908 $Cmd = "$AbsRealBin/ccc-analyzer"; 1909 DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd); 1910} 1911if (!defined $CmdCXX || ! -e $CmdCXX) { 1912 $CmdCXX = "$AbsRealBin/c++-analyzer"; 1913 DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX); 1914} 1915 1916Diag("Using '$Clang' for static analysis\n"); 1917 1918SetHtmlEnv(\@ARGV, $Options{OutputDir}); 1919 1920my @AnalysesToRun; 1921foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} } 1922 keys %{$Options{EnableCheckers}}) { 1923 # Push checkers in order they were enabled. 1924 push @AnalysesToRun, "-analyzer-checker", $_; 1925} 1926foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} } 1927 keys %{$Options{DisableCheckers}}) { 1928 # Push checkers in order they were disabled. 1929 push @AnalysesToRun, "-analyzer-disable-checker", $_; 1930} 1931if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; } 1932if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; } 1933if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; } 1934 1935# Delay setting up other environment variables in case we can do true 1936# interposition. 1937my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun; 1938my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}}; 1939my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}}; 1940 1941if (%{$Options{SilenceCheckers}}) { 1942 $CCC_ANALYZER_CONFIG = 1943 $CCC_ANALYZER_CONFIG." -analyzer-config silence-checkers=" 1944 .join(';', sort { 1945 $Options{SilenceCheckers}{$a} <=> 1946 $Options{SilenceCheckers}{$b} 1947 } keys %{$Options{SilenceCheckers}}); 1948} 1949 1950my %EnvVars = ( 1951 'CC' => $Cmd, 1952 'CXX' => $CmdCXX, 1953 'CLANG' => $Clang, 1954 'CLANG_CXX' => $ClangCXX, 1955 'VERBOSE' => $Options{Verbose}, 1956 'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS, 1957 'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS, 1958 'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG, 1959 'OUTPUT_DIR' => $Options{OutputDir}, 1960 'CCC_CC' => $Options{UseCC}, 1961 'CCC_CXX' => $Options{UseCXX}, 1962 'CCC_REPORT_FAILURES' => $Options{ReportFailures}, 1963 'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel}, 1964 'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats}, 1965 'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat}, 1966 'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget}, 1967 'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE' => $Options{ForceAnalyzeDebugCode} 1968); 1969 1970# Run the build. 1971my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Options{KeepCC}, 1972 $Cmd, $CmdCXX, \%EnvVars); 1973 1974Finalize($BaseDir, $ExitStatus); 1975