aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorKovarththanan Rajaratnam <kovarththanan.rajaratnam@gmail.com>2009-12-01 06:52:01 +0000
committerKovarththanan Rajaratnam <kovarththanan.rajaratnam@gmail.com>2009-12-01 06:52:01 +0000
commitd8fdde119630fa717457ab23b077f447bb2e9e3a (patch)
treed0219ee2c034392b15697c0b6a37ca404064dfe6 /tools
parente1fea772c4316294002b303f6d205b7fcccba9b1 (diff)
Tuck away scan-build related files into tools/scan-build
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@90215 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'tools')
-rwxr-xr-xtools/scan-build/ccc-analyzer632
-rwxr-xr-xtools/scan-build/scan-build1297
-rw-r--r--tools/scan-build/scanview.css62
-rw-r--r--tools/scan-build/sorttable.js493
4 files changed, 2484 insertions, 0 deletions
diff --git a/tools/scan-build/ccc-analyzer b/tools/scan-build/ccc-analyzer
new file mode 100755
index 0000000000..25b9800574
--- /dev/null
+++ b/tools/scan-build/ccc-analyzer
@@ -0,0 +1,632 @@
+#!/usr/bin/env perl
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+#
+# A script designed to interpose between the build system and gcc. It invokes
+# both gcc and the static analyzer.
+#
+##===----------------------------------------------------------------------===##
+
+use strict;
+use warnings;
+use Cwd qw/ getcwd abs_path /;
+use File::Temp qw/ tempfile /;
+use File::Path qw / mkpath /;
+use File::Basename;
+use Text::ParseWords;
+
+my $CC = $ENV{'CCC_CC'};
+if (!defined $CC) { $CC = "gcc"; }
+
+my $ReportFailures = $ENV{'CCC_REPORT_FAILURES'};
+if (!defined $ReportFailures) { $ReportFailures = 1; }
+
+my $CleanupFile;
+my $ResultFile;
+
+# Remove any stale files at exit.
+END {
+ if (defined $CleanupFile && -z $CleanupFile) {
+ `rm -f $CleanupFile`;
+ }
+}
+
+##----------------------------------------------------------------------------##
+# Process Clang Crashes.
+##----------------------------------------------------------------------------##
+
+sub GetPPExt {
+ my $Lang = shift;
+ if ($Lang =~ /objective-c/) { return ".mi"; }
+ return ".i";
+}
+
+# Set this to 1 if we want to include 'parser rejects' files.
+my $IncludeParserRejects = 0;
+my $ParserRejects = "Parser Rejects";
+
+my $AttributeIgnored = "Attribute Ignored";
+
+sub ProcessClangFailure {
+ my ($ClangCC, $Lang, $file, $Args, $HtmlDir, $ErrorType, $ofile) = @_;
+ my $Dir = "$HtmlDir/failures";
+ mkpath $Dir;
+
+ my $prefix = "clang_crash";
+ if ($ErrorType eq $ParserRejects) {
+ $prefix = "clang_parser_rejects";
+ }
+ elsif ($ErrorType eq $AttributeIgnored) {
+ $prefix = "clang_attribute_ignored";
+ }
+
+ # Generate the preprocessed file with Clang.
+ my ($PPH, $PPFile) = tempfile( $prefix . "_XXXXXX",
+ SUFFIX => GetPPExt($Lang),
+ DIR => $Dir);
+ system $ClangCC, @$Args, "-E", "-o", $PPFile;
+ close ($PPH);
+
+ # Create the info file.
+ open (OUT, ">", "$PPFile.info.txt") or die "Cannot open $PPFile.info.txt\n";
+ print OUT abs_path($file), "\n";
+ print OUT "$ErrorType\n";
+ print OUT "@$Args\n";
+ close OUT;
+ `uname -a >> $PPFile.info.txt 2>&1`;
+ `$CC -v >> $PPFile.info.txt 2>&1`;
+ system 'mv',$ofile,"$PPFile.stderr.txt";
+ return (basename $PPFile);
+}
+
+##----------------------------------------------------------------------------##
+# Running the analyzer.
+##----------------------------------------------------------------------------##
+
+# Determine what clang executable to use.
+my $Clang = $ENV{'CLANG'};
+if (!defined $Clang) { $Clang = 'clang'; }
+
+sub GetCCArgs {
+ my $Args = shift;
+
+ pipe (FROM_CHILD, TO_PARENT);
+ my $pid = fork();
+ if ($pid == 0) {
+ close FROM_CHILD;
+ open(STDOUT,">&", \*TO_PARENT);
+ open(STDERR,">&", \*TO_PARENT);
+ exec $Clang, "-###", "-fsyntax-only", @$Args;
+ }
+ close(TO_PARENT);
+ my $line;
+ while (<FROM_CHILD>) {
+ next if (!/clang-cc/);
+ $line = $_;
+ }
+
+ waitpid($pid,0);
+ close(FROM_CHILD);
+
+ die "could not find clang-cc line\n" if (!defined $line);
+ # Strip the newline and initial whitspace
+ chomp $line;
+ $line =~ s/^\s+//;
+
+ my @items = quotewords('\s+', 1, $line);
+ for (my $i = 0 ; $ i < scalar(@items); ++$i) {
+ $items[$i] =~ s/^\"//;
+ $items[$i] =~ s/\"$//;
+ }
+ my $cmd = shift @items;
+ die "cannot find 'clang-cc' in 'clang' command\n" if (!($cmd =~ /clang-cc/));
+ return \@items;
+}
+
+sub Analyze {
+ my ($ClangCC, $Args, $AnalyzeArgs, $Lang, $Output, $Verbose, $HtmlDir,
+ $file, $Analyses) = @_;
+
+ $Args = GetCCArgs($Args);
+
+ # Skip anything related to C++.
+ return if ($Lang =~ /c[+][+]/);
+
+ my $RunAnalyzer = 0;
+ my $Cmd;
+ my @CmdArgs;
+ my @CmdArgsSansAnalyses;
+
+ if ($Lang =~ /header/) {
+ exit 0 if (!defined ($Output));
+ $Cmd = 'cp';
+ push @CmdArgs,$file;
+ # Remove the PCH extension.
+ $Output =~ s/[.]gch$//;
+ push @CmdArgs,$Output;
+ @CmdArgsSansAnalyses = @CmdArgs;
+ }
+ else {
+ $Cmd = $ClangCC;
+ push @CmdArgs,'-DIBOutlet=__attribute__((iboutlet))';
+ push @CmdArgs,@$Args;
+ @CmdArgsSansAnalyses = @CmdArgs;
+ push @CmdArgs,'-analyze';
+ push @CmdArgs,"-analyzer-display-progress";
+ push @CmdArgs,"-analyzer-eagerly-assume";
+ push @CmdArgs,(split /\s/,$Analyses);
+
+ if (defined $ENV{"CCC_EXPERIMENTAL_CHECKS"}) {
+ push @CmdArgs,"-analyzer-experimental-internal-checks";
+ push @CmdArgs,"-analyzer-experimental-checks";
+ }
+
+ $RunAnalyzer = 1;
+ }
+
+ # Add the analysis arguments passed down from scan-build.
+ foreach my $Arg (@$AnalyzeArgs) {
+ push @CmdArgs, $Arg;
+ }
+
+ my @PrintArgs;
+ my $dir;
+
+ if ($RunAnalyzer) {
+ if (defined $ResultFile) {
+ push @CmdArgs,'-o';
+ push @CmdArgs, $ResultFile;
+ }
+ elsif (defined $HtmlDir) {
+ push @CmdArgs,'-o';
+ push @CmdArgs, $HtmlDir;
+ }
+ }
+
+ if ($Verbose) {
+ $dir = getcwd();
+ print STDERR "\n[LOCATION]: $dir\n";
+ push @PrintArgs,"'$Cmd'";
+ foreach my $arg (@CmdArgs) { push @PrintArgs,"\'$arg\'"; }
+ }
+
+ if ($Verbose == 1) {
+ # We MUST print to stderr. Some clients use the stdout output of
+ # gcc for various purposes.
+ print STDERR join(' ',@PrintArgs);
+ print STDERR "\n";
+ }
+ elsif ($Verbose == 2) {
+ print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n";
+ }
+
+ if (defined $ENV{'CCC_UBI'}) {
+ push @CmdArgs,"--analyzer-viz-egraph-ubigraph";
+ }
+
+ # Capture the STDERR of clang and send it to a temporary file.
+ # Capture the STDOUT of clang and reroute it to ccc-analyzer's STDERR.
+ # We save the output file in the 'crashes' directory if clang encounters
+ # any problems with the file.
+ pipe (FROM_CHILD, TO_PARENT);
+ my $pid = fork();
+ if ($pid == 0) {
+ close FROM_CHILD;
+ open(STDOUT,">&", \*TO_PARENT);
+ open(STDERR,">&", \*TO_PARENT);
+ exec $Cmd, @CmdArgs;
+ }
+
+ close TO_PARENT;
+ my ($ofh, $ofile) = tempfile("clang_output_XXXXXX", DIR => $HtmlDir);
+
+ while (<FROM_CHILD>) {
+ print $ofh $_;
+ print STDERR $_;
+ }
+
+ waitpid($pid,0);
+ close(FROM_CHILD);
+ my $Result = $?;
+
+ # Did the command die because of a signal?
+ if ($ReportFailures) {
+ if ($Result & 127 and $Cmd eq $ClangCC and defined $HtmlDir) {
+ ProcessClangFailure($ClangCC, $Lang, $file, \@CmdArgsSansAnalyses,
+ $HtmlDir, "Crash", $ofile);
+ }
+ elsif ($Result) {
+ if ($IncludeParserRejects && !($file =~/conftest/)) {
+ ProcessClangFailure($ClangCC, $Lang, $file, \@CmdArgsSansAnalyses,
+ $HtmlDir, $ParserRejects, $ofile);
+ }
+ }
+ else {
+ # Check if there were any unhandled attributes.
+ if (open(CHILD, $ofile)) {
+ my %attributes_not_handled;
+
+ # Don't flag warnings about the following attributes that we
+ # know are currently not supported by Clang.
+ $attributes_not_handled{"cdecl"} = 1;
+
+ my $ppfile;
+ while (<CHILD>) {
+ next if (! /warning: '([^\']+)' attribute ignored/);
+
+ # Have we already spotted this unhandled attribute?
+ next if (defined $attributes_not_handled{$1});
+ $attributes_not_handled{$1} = 1;
+
+ # Get the name of the attribute file.
+ my $dir = "$HtmlDir/failures";
+ my $afile = "$dir/attribute_ignored_$1.txt";
+
+ # Only create another preprocessed file if the attribute file
+ # doesn't exist yet.
+ next if (-e $afile);
+
+ # Add this file to the list of files that contained this attribute.
+ # Generate a preprocessed file if we haven't already.
+ if (!(defined $ppfile)) {
+ $ppfile = ProcessClangFailure($ClangCC, $Lang, $file,
+ \@CmdArgsSansAnalyses,
+ $HtmlDir, $AttributeIgnored, $ofile);
+ }
+
+ mkpath $dir;
+ open(AFILE, ">$afile");
+ print AFILE "$ppfile\n";
+ close(AFILE);
+ }
+ close CHILD;
+ }
+ }
+ }
+
+ unlink($ofile);
+}
+
+##----------------------------------------------------------------------------##
+# Lookup tables.
+##----------------------------------------------------------------------------##
+
+my %CompileOptionMap = (
+ '-nostdinc' => 0,
+ '-fblocks' => 0,
+ '-fobjc-gc-only' => 0,
+ '-fobjc-gc' => 0,
+ '-ffreestanding' => 0,
+ '-include' => 1,
+ '-idirafter' => 1,
+ '-iprefix' => 1,
+ '-iquote' => 1,
+ '-isystem' => 1,
+ '-iwithprefix' => 1,
+ '-iwithprefixbefore' => 1
+);
+
+my %LinkerOptionMap = (
+ '-framework' => 1
+);
+
+my %CompilerLinkerOptionMap = (
+ '-isysroot' => 1,
+ '-arch' => 1,
+ '-v' => 0,
+ '-fpascal-strings' => 0,
+ '-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '='
+ '-miphoneos-version-min' => 0 # This is really a 1 argument, but always has '='
+);
+
+my %IgnoredOptionMap = (
+ '-MT' => 1, # Ignore these preprocessor options.
+ '-MF' => 1,
+
+ '-fsyntax-only' => 0,
+ '-save-temps' => 0,
+ '-install_name' => 1,
+ '-exported_symbols_list' => 1,
+ '-current_version' => 1,
+ '-compatibility_version' => 1,
+ '-init' => 1,
+ '-e' => 1,
+ '-seg1addr' => 1,
+ '-bundle_loader' => 1,
+ '-multiply_defined' => 1,
+ '-sectorder' => 3,
+ '--param' => 1,
+ '-u' => 1
+);
+
+my %LangMap = (
+ 'c' => 'c',
+ 'cpp' => 'c++',
+ 'cc' => 'c++',
+ 'i' => 'c-cpp-output',
+ 'm' => 'objective-c',
+ 'mi' => 'objective-c-cpp-output'
+);
+
+my %UniqueOptions = (
+ '-isysroot' => 0
+);
+
+my %LangsAccepted = (
+ "objective-c" => 1,
+ "c" => 1
+);
+
+##----------------------------------------------------------------------------##
+# Main Logic.
+##----------------------------------------------------------------------------##
+
+my $Action = 'link';
+my @CompileOpts;
+my @LinkOpts;
+my @Files;
+my $Lang;
+my $Output;
+my %Uniqued;
+
+# Forward arguments to gcc.
+my $Status = system($CC,@ARGV);
+if ($Status) { exit($Status >> 8); }
+
+# Get the analysis options.
+my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
+if (!defined($Analyses)) { $Analyses = '-checker-cfref'; }
+
+# Get the store model.
+my $StoreModel = $ENV{'CCC_ANALYZER_STORE_MODEL'};
+if (!defined $StoreModel) { $StoreModel = "region"; }
+
+# Get the constraints engine.
+my $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'};
+if (!defined $ConstraintsModel) { $ConstraintsModel = "range"; }
+
+# Get the output format.
+my $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'};
+if (!defined $OutputFormat) { $OutputFormat = "html"; }
+
+# Determine the level of verbosity.
+my $Verbose = 0;
+if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
+if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
+
+# Determine what clang-cc executable to use.
+my $ClangCC = $ENV{'CLANG_CC'};
+if (!defined $ClangCC) { $ClangCC = 'clang-cc'; }
+
+# Get the HTML output directory.
+my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
+
+my %DisabledArchs = ('ppc' => 1, 'ppc64' => 1);
+my %ArchsSeen;
+my $HadArch = 0;
+
+# Process the arguments.
+foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
+ my $Arg = $ARGV[$i];
+ my ($ArgKey) = split /=/,$Arg,2;
+
+ # Modes ccc-analyzer supports
+ if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; }
+ elsif ($Arg eq '-c') { $Action = 'compile'; }
+ elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
+
+ # Specially handle duplicate cases of -arch
+ if ($Arg eq "-arch") {
+ my $arch = $ARGV[$i+1];
+ # We don't want to process 'ppc' because of Clang's lack of support
+ # for Altivec (also some #defines won't likely be defined correctly, etc.)
+ if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; }
+ $HadArch = 1;
+ ++$i;
+ next;
+ }
+
+ # Options with possible arguments that should pass through to compiler.
+ if (defined $CompileOptionMap{$ArgKey}) {
+ my $Cnt = $CompileOptionMap{$ArgKey};
+ push @CompileOpts,$Arg;
+ while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
+ next;
+ }
+
+ # Options with possible arguments that should pass through to linker.
+ if (defined $LinkerOptionMap{$ArgKey}) {
+ my $Cnt = $LinkerOptionMap{$ArgKey};
+ push @LinkOpts,$Arg;
+ while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
+ next;
+ }
+
+ # Options with possible arguments that should pass through to both compiler
+ # and the linker.
+ if (defined $CompilerLinkerOptionMap{$ArgKey}) {
+ my $Cnt = $CompilerLinkerOptionMap{$ArgKey};
+
+ # Check if this is an option that should have a unique value, and if so
+ # determine if the value was checked before.
+ if ($UniqueOptions{$Arg}) {
+ if (defined $Uniqued{$Arg}) {
+ $i += $Cnt;
+ next;
+ }
+ $Uniqued{$Arg} = 1;
+ }
+
+ push @CompileOpts,$Arg;
+ push @LinkOpts,$Arg;
+
+ while ($Cnt > 0) {
+ ++$i; --$Cnt;
+ push @CompileOpts, $ARGV[$i];
+ push @LinkOpts, $ARGV[$i];
+ }
+ next;
+ }
+
+ # Ignored options.
+ if (defined $IgnoredOptionMap{$ArgKey}) {
+ my $Cnt = $IgnoredOptionMap{$ArgKey};
+ while ($Cnt > 0) {
+ ++$i; --$Cnt;
+ }
+ next;
+ }
+
+ # Compile mode flags.
+ if ($Arg =~ /^-[D,I,U](.*)$/) {
+ my $Tmp = $Arg;
+ if ($1 eq '') {
+ # FIXME: Check if we are going off the end.
+ ++$i;
+ $Tmp = $Arg . $ARGV[$i];
+ }
+ push @CompileOpts,$Tmp;
+ next;
+ }
+
+ # Language.
+ if ($Arg eq '-x') {
+ $Lang = $ARGV[$i+1];
+ ++$i; next;
+ }
+
+ # Output file.
+ if ($Arg eq '-o') {
+ ++$i;
+ $Output = $ARGV[$i];
+ next;
+ }
+
+ # Get the link mode.
+ if ($Arg =~ /^-[l,L,O]/) {
+ if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
+ elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
+ else { push @LinkOpts,$Arg; }
+ next;
+ }
+
+ if ($Arg =~ /^-std=/) {
+ push @CompileOpts,$Arg;
+ next;
+ }
+
+# if ($Arg =~ /^-f/) {
+# # FIXME: Not sure if the remaining -fxxxx options have no arguments.
+# push @CompileOpts,$Arg;
+# push @LinkOpts,$Arg; # FIXME: Not sure if these are link opts.
+# }
+
+ # Get the compiler/link mode.
+ if ($Arg =~ /^-F(.+)$/) {
+ my $Tmp = $Arg;
+ if ($1 eq '') {
+ # FIXME: Check if we are going off the end.
+ ++$i;
+ $Tmp = $Arg . $ARGV[$i];
+ }
+ push @CompileOpts,$Tmp;
+ push @LinkOpts,$Tmp;
+ next;
+ }
+
+ # Input files.
+ if ($Arg eq '-filelist') {
+ # FIXME: Make sure we aren't walking off the end.
+ open(IN, $ARGV[$i+1]);
+ while (<IN>) { s/\015?\012//; push @Files,$_; }
+ close(IN);
+ ++$i;
+ next;
+ }
+
+ # Handle -Wno-. We don't care about extra warnings, but
+ # we should suppress ones that we don't want to see.
+ if ($Arg =~ /^-Wno-/) {
+ push @CompileOpts, $Arg;
+ next;
+ }
+
+ if (!($Arg =~ /^-/)) {
+ push @Files, $Arg;
+ next;
+ }
+}
+
+if ($Action eq 'compile' or $Action eq 'link') {
+ my @Archs = keys %ArchsSeen;
+ # Skip the file if we don't support the architectures specified.
+ exit 0 if ($HadArch && scalar(@Archs) == 0);
+
+ foreach my $file (@Files) {
+ # Determine the language for the file.
+ my $FileLang = $Lang;
+
+ if (!defined($FileLang)) {
+ # Infer the language from the extension.
+ if ($file =~ /[.]([^.]+)$/) {
+ $FileLang = $LangMap{$1};
+ }
+ }
+
+ next if (!defined $FileLang);
+ next if (!defined $LangsAccepted{$FileLang});
+
+ my @CmdArgs;
+ my @AnalyzeArgs;
+
+ if ($FileLang ne 'unknown') {
+ push @CmdArgs,'-x';
+ push @CmdArgs,$FileLang;
+ }
+
+ if (defined $StoreModel) {
+ push @AnalyzeArgs, "-analyzer-store=$StoreModel";
+ }
+
+ if (defined $ConstraintsModel) {
+ push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel";
+ }
+
+ if (defined $OutputFormat) {
+ push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat;
+ if ($OutputFormat =~ /plist/) {
+ # Change "Output" to be a file.
+ my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => ".plist",
+ DIR => $HtmlDir);
+ $ResultFile = $f;
+ $CleanupFile = $f;
+ }
+ }
+
+ push @CmdArgs,@CompileOpts;
+ push @CmdArgs,$file;
+
+ if (scalar @Archs) {
+ foreach my $arch (@Archs) {
+ my @NewArgs;
+ push @NewArgs, '-arch';
+ push @NewArgs, $arch;
+ push @NewArgs, @CmdArgs;
+ Analyze($ClangCC, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output,
+ $Verbose, $HtmlDir, $file, $Analyses);
+ }
+ }
+ else {
+ Analyze($ClangCC, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output,
+ $Verbose, $HtmlDir, $file, $Analyses);
+ }
+ }
+}
+
+exit($Status >> 8);
+
diff --git a/tools/scan-build/scan-build b/tools/scan-build/scan-build
new file mode 100755
index 0000000000..8d99f070ea
--- /dev/null
+++ b/tools/scan-build/scan-build
@@ -0,0 +1,1297 @@
+#!/usr/bin/env perl
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+##===----------------------------------------------------------------------===##
+#
+# A script designed to wrap a build so that all calls to gcc are intercepted
+# and piped to the static analyzer.
+#
+##===----------------------------------------------------------------------===##
+
+use strict;
+use warnings;
+use FindBin qw($RealBin);
+use Digest::MD5;
+use File::Basename;
+use Term::ANSIColor;
+use Term::ANSIColor qw(:constants);
+use Cwd qw/ getcwd abs_path /;
+use Sys::Hostname;
+
+my $Verbose = 0; # Verbose output from this script.
+my $Prog = "scan-build";
+my $BuildName;
+my $BuildDate;
+my $CXX; # Leave undefined initially.
+
+my $TERM = $ENV{'TERM'};
+my $UseColor = (defined $TERM and $TERM eq 'xterm-color' and -t STDOUT
+ and defined $ENV{'SCAN_BUILD_COLOR'});
+
+my $UserName = HtmlEscape(getpwuid($<) || 'unknown');
+my $HostName = HtmlEscape(hostname() || 'unknown');
+my $CurrentDir = HtmlEscape(getcwd());
+my $CurrentDirSuffix = basename($CurrentDir);
+
+my $CmdArgs;
+
+my $HtmlTitle;
+
+my $Date = localtime();
+
+##----------------------------------------------------------------------------##
+# Diagnostics
+##----------------------------------------------------------------------------##
+
+sub Diag {
+ if ($UseColor) {
+ print BOLD, MAGENTA "$Prog: @_";
+ print RESET;
+ }
+ else {
+ print "$Prog: @_";
+ }
+}
+
+sub DiagCrashes {
+ my $Dir = shift;
+ Diag ("The analyzer encountered problems on some source files.\n");
+ Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
+ Diag ("Please consider submitting a bug report using these files:\n");
+ Diag (" http://clang.llvm.org/StaticAnalysisUsage.html#filingbugs\n")
+}
+
+sub DieDiag {
+ if ($UseColor) {
+ print BOLD, RED "$Prog: ";
+ print RESET, RED @_;
+ print RESET;
+ }
+ else {
+ print "$Prog: ", @_;
+ }
+ exit(0);
+}
+
+##----------------------------------------------------------------------------##
+# Some initial preprocessing of Clang options.
+##----------------------------------------------------------------------------##
+
+# First, look for 'clang-cc' in libexec.
+my $ClangCCSB = Cwd::realpath("$RealBin/libexec/clang-cc");
+# Second, look for 'clang-cc' in the same directory as scan-build.
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ $ClangCCSB = Cwd::realpath("$RealBin/clang-cc");
+}
+# Third, look for 'clang-cc' in ../libexec
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ $ClangCCSB = Cwd::realpath("$RealBin/../libexec/clang-cc");
+}
+# Finally, default to looking for 'clang-cc' in the path.
+if (!defined $ClangCCSB || ! -x $ClangCCSB) {
+ $ClangCCSB = "clang-cc";
+}
+my $ClangCC = $ClangCCSB;
+
+# Now find 'clang'
+my $ClangSB = Cwd::realpath("$RealBin/bin/clang");
+if (!defined $ClangSB || ! -x $ClangSB) {
+ $ClangSB = Cwd::realpath("$RealBin/clang");
+}
+# Third, look for 'clang' in ../bin
+if (!defined $ClangSB || ! -x $ClangSB) {
+ $ClangSB = Cwd::realpath("$RealBin/../bin/clang");
+}
+# Finally, default to looking for 'clang-cc' in the path.
+if (!defined $ClangSB || ! -x $ClangSB) {
+ $ClangSB = "clang";
+}
+my $Clang = $ClangSB;
+
+
+my %AvailableAnalyses;
+
+# Query clang for analysis options.
+open(PIPE, "-|", $ClangCC, "--help") or
+ DieDiag("Cannot execute '$ClangCC'\n");
+
+my $FoundAnalysis = 0;
+
+while(<PIPE>) {
+ if ($FoundAnalysis == 0) {
+ if (/Checks and Analyses/) {
+ $FoundAnalysis = 1;
+ }
+ next;
+ }
+
+ if (/^\s\s\s\s([^\s]+)\s(.+)$/) {
+ next if ($1 =~ /-dump/ or $1 =~ /-view/
+ or $1 =~ /-warn-uninit/);
+
+ $AvailableAnalyses{$1} = $2;
+ next;
+ }
+ last;
+}
+
+close (PIPE);
+
+my %AnalysesDefaultEnabled = (
+ '-warn-dead-stores' => 1,
+ '-checker-cfref' => 1,
+ '-warn-objc-methodsigs' => 1,
+ # Do not enable the missing -dealloc check by default.
+ # '-warn-objc-missing-dealloc' => 1,
+ '-warn-objc-unused-ivars' => 1,
+ '-warn-security-syntactic' => 1
+);
+
+##----------------------------------------------------------------------------##
+# GetHTMLRunDir - Construct an HTML directory name for the current sub-run.
+##----------------------------------------------------------------------------##
+
+sub GetHTMLRunDir {
+
+ die "Not enough arguments." if (@_ == 0);
+ my $Dir = shift @_;
+
+ my $TmpMode = 0;
+ if (!defined $Dir) {
+ if (`uname` =~ /Darwin/) {
+ $Dir = $ENV{'TMPDIR'};
+ if (!defined $Dir) { $Dir = "/tmp"; }
+ }
+ else {
+ $Dir = "/tmp";
+ }
+
+ $TmpMode = 1;
+ }
+
+ # Chop off any trailing '/' characters.
+ while ($Dir =~ /\/$/) { chop $Dir; }
+
+ # Get current date and time.
+
+ my @CurrentTime = localtime();
+
+ my $year = $CurrentTime[5] + 1900;
+ my $day = $CurrentTime[3];
+ my $month = $CurrentTime[4] + 1;
+
+ my $DateString = sprintf("%d-%02d-%02d", $year, $month, $day);
+
+ # Determine the run number.
+
+ my $RunNumber;
+
+ if (-d $Dir) {
+
+ if (! -r $Dir) {
+ DieDiag("directory '$Dir' exists but is not readable.\n");
+ }
+
+ # Iterate over all files in the specified directory.
+
+ my $max = 0;
+
+ opendir(DIR, $Dir);
+ my @FILES = grep { -d "$Dir/$_" } readdir(DIR);
+ closedir(DIR);
+
+ foreach my $f (@FILES) {
+
+ # Strip the prefix '$Prog-' if we are dumping files to /tmp.
+ if ($TmpMode) {
+ next if (!($f =~ /^$Prog-(.+)/));
+ $f = $1;
+ }
+
+
+ my @x = split/-/, $f;
+ next if (scalar(@x) != 4);
+ next if ($x[0] != $year);
+ next if ($x[1] != $month);
+ next if ($x[2] != $day);
+
+ if ($x[3] > $max) {
+ $max = $x[3];
+ }
+ }
+
+ $RunNumber = $max + 1;
+ }
+ else {
+
+ if (-x $Dir) {
+ DieDiag("'$Dir' exists but is not a directory.\n");
+ }
+
+ if ($TmpMode) {
+ DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n");
+ }
+
+ # $Dir does not exist. It will be automatically created by the
+ # clang driver. Set the run number to 1.
+
+ $RunNumber = 1;
+ }
+
+ die "RunNumber must be defined!" if (!defined $RunNumber);
+
+ # Append the run number.
+ my $NewDir;
+ if ($TmpMode) {
+ $NewDir = "$Dir/$Prog-$DateString-$RunNumber";
+ }
+ else {
+ $NewDir = "$Dir/$DateString-$RunNumber";
+ }
+ system 'mkdir','-p',$NewDir;
+ return $NewDir;
+}
+
+sub SetHtmlEnv {
+
+ die "Wrong number of arguments." if (scalar(@_) != 2);
+
+ my $Args = shift;
+ my $Dir = shift;
+
+ die "No build command." if (scalar(@$Args) == 0);
+
+ my $Cmd = $$Args[0];
+
+ if ($Cmd =~ /configure/) {
+ return;
+ }
+
+ if ($Verbose) {
+ Diag("Emitting reports for this run to '$Dir'.\n");
+ }
+
+ $ENV{'CCC_ANALYZER_HTML'} = $Dir;
+}
+
+##----------------------------------------------------------------------------##
+# ComputeDigest - Compute a digest of the specified file.
+##----------------------------------------------------------------------------##
+
+sub ComputeDigest {
+ my $FName = shift;
+ DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);
+
+ # Use Digest::MD5. We don't have to be cryptographically secure. We're
+ # just looking for duplicate files that come from a non-malicious source.
+ # We use Digest::MD5 because it is a standard Perl module that should
+ # come bundled on most systems.
+ open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
+ binmode FILE;
+ my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
+ close(FILE);
+
+ # Return the digest.
+ return $Result;
+}
+
+##----------------------------------------------------------------------------##
+# UpdatePrefix - Compute the common prefix of files.
+##----------------------------------------------------------------------------##
+
+my $Prefix;
+
+sub UpdatePrefix {
+ my $x = shift;
+ my $y = basename($x);
+ $x =~ s/\Q$y\E$//;
+
+ if (!defined $Prefix) {
+ $Prefix = $x;
+ return;
+ }
+
+ chop $Prefix while (!($x =~ /^\Q$Prefix/));
+}
+
+sub GetPrefix {
+ return $Prefix;
+}
+
+##----------------------------------------------------------------------------##
+# UpdateInFilePath - Update the path in the report file.
+##----------------------------------------------------------------------------##
+
+sub UpdateInFilePath {
+ my $fname = shift;
+ my $regex = shift;
+ my $newtext = shift;
+
+ open (RIN, $fname) or die "cannot open $fname";
+ open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
+
+ while (<RIN>) {
+ s/$regex/$newtext/;
+ print ROUT $_;
+ }
+
+ close (ROUT);
+ close (RIN);
+ system("mv", "$fname.tmp", $fname);
+}
+
+##----------------------------------------------------------------------------##
+# ScanFile - Scan a report file for various identifying attributes.
+##----------------------------------------------------------------------------##
+
+# Sometimes a source file is scanned more than once, and thus produces
+# multiple error reports. We use a cache to solve this problem.
+
+my %AlreadyScanned;
+
+sub ScanFile {
+
+ my $Index = shift;
+ my $Dir = shift;
+ my $FName = shift;
+
+ # Compute a digest for the report file. Determine if we have already
+ # scanned a file that looks just like it.
+
+ my $digest = ComputeDigest("$Dir/$FName");
+
+ if (defined $AlreadyScanned{$digest}) {
+ # Redundant file. Remove it.
+ system ("rm", "-f", "$Dir/$FName");
+ return;
+ }
+
+ $AlreadyScanned{$digest} = 1;
+
+ # At this point the report file is not world readable. Make it happen.
+ system ("chmod", "644", "$Dir/$FName");
+
+ # Scan the report file for tags.
+ open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
+
+ my $BugType = "";
+ my $BugFile = "";
+ my $BugCategory;
+ my $BugPathLength = 1;
+ my $BugLine = 0;
+
+ while (<IN>) {
+ last if (/<!-- BUGMETAEND -->/);
+
+ if (/<!-- BUGTYPE (.*) -->$/) {
+ $BugType = $1;
+ }
+ elsif (/<!-- BUGFILE (.*) -->$/) {
+ $BugFile = abs_path($1);
+ UpdatePrefix($BugFile);
+ }
+ elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
+ $BugPathLength = $1;
+ }
+ elsif (/<!-- BUGLINE (.*) -->$/) {
+ $BugLine = $1;
+ }
+ elsif (/<!-- BUGCATEGORY (.*) -->$/) {
+ $BugCategory = $1;
+ }
+ }
+
+ close(IN);
+
+ if (!defined $BugCategory) {
+ $BugCategory = "Other";
+ }
+
+ push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugLine,
+ $BugPathLength ];
+}
+
+##----------------------------------------------------------------------------##
+# CopyFiles - Copy resource files to target directory.
+##----------------------------------------------------------------------------##
+
+sub CopyFiles {
+
+ my $Dir = shift;
+
+ my $JS = Cwd::realpath("$RealBin/sorttable.js");
+
+ DieDiag("Cannot find 'sorttable.js'.\n")
+ if (! -r $JS);
+
+ system ("cp", $JS, "$Dir");
+
+ DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
+ if (! -r "$Dir/sorttable.js");
+
+ my $CSS = Cwd::realpath("$RealBin/scanview.css");
+
+ DieDiag("Cannot find 'scanview.css'.\n")
+ if (! -r $CSS);
+
+ system ("cp", $CSS, "$Dir");
+
+ DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
+ if (! -r $CSS);
+}
+
+##----------------------------------------------------------------------------##
+# Postprocess - Postprocess the results of an analysis scan.
+##----------------------------------------------------------------------------##
+
+sub Postprocess {
+
+ my $Dir = shift;
+ my $BaseDir = shift;
+
+ die "No directory specified." if (!defined $Dir);
+
+ if (! -d $Dir) {
+ Diag("No bugs found.\n");
+ return 0;
+ }
+
+ opendir(DIR, $Dir);
+ my @files = grep { /^report-.*\.html$/ } readdir(DIR);
+ closedir(DIR);
+
+ if (scalar(@files) == 0 and ! -e "$Dir/failures") {
+ Diag("Removing directory '$Dir' because it contains no reports.\n");
+ system ("rm", "-fR", $Dir);
+ return 0;
+ }
+
+ # Scan each report file and build an index.
+ my @Index;
+ foreach my $file (@files) { ScanFile(\@Index, $Dir, $file); }
+
+ # Scan the failures directory and use the information in the .info files
+ # to update the common prefix directory.
+ my @failures;
+ my @attributes_ignored;
+ if (-d "$Dir/failures") {
+ opendir(DIR, "$Dir/failures");
+ @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
+ closedir(DIR);
+ opendir(DIR, "$Dir/failures");
+ @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
+ closedir(DIR);
+ foreach my $file (@failures) {
+ open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
+ my $Path = <IN>;
+ if (defined $Path) { UpdatePrefix($Path); }
+ close IN;
+ }
+ }
+
+ # Generate an index.html file.
+ my $FName = "$Dir/index.html";
+ open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
+
+ # Print out the header.
+
+print OUT <<ENDTEXT;
+<html>
+<head>
+<title>${HtmlTitle}</title>
+<link type="text/css" rel="stylesheet" href="scanview.css"/>
+<script src="sorttable.js"></script>
+<script language='javascript' type="text/javascript">
+function SetDisplay(RowClass, DisplayVal)
+{
+ var Rows = document.getElementsByTagName("tr");
+ for ( var i = 0 ; i < Rows.length; ++i ) {
+ if (Rows[i].className == RowClass) {
+ Rows[i].style.display = DisplayVal;
+ }
+ }
+}
+
+function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
+ var Inputs = d