1e5dd7070Spatrick#!/usr/bin/env perl 2e5dd7070Spatrick# 3e5dd7070Spatrick# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4e5dd7070Spatrick# See https://llvm.org/LICENSE.txt for license information. 5e5dd7070Spatrick# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6e5dd7070Spatrick# 7e5dd7070Spatrick##===----------------------------------------------------------------------===## 8e5dd7070Spatrick# 9e5dd7070Spatrick# A script designed to interpose between the build system and gcc. It invokes 10e5dd7070Spatrick# both gcc and the static analyzer. 11e5dd7070Spatrick# 12e5dd7070Spatrick##===----------------------------------------------------------------------===## 13e5dd7070Spatrick 14e5dd7070Spatrickuse strict; 15e5dd7070Spatrickuse warnings; 16e5dd7070Spatrickuse FindBin; 17e5dd7070Spatrickuse Cwd qw/ getcwd abs_path /; 18e5dd7070Spatrickuse File::Temp qw/ tempfile /; 19e5dd7070Spatrickuse File::Path qw / mkpath /; 20e5dd7070Spatrickuse File::Basename; 21e5dd7070Spatrickuse Text::ParseWords; 22e5dd7070Spatrick 23e5dd7070Spatrick##===----------------------------------------------------------------------===## 24e5dd7070Spatrick# List form 'system' with STDOUT and STDERR captured. 25e5dd7070Spatrick##===----------------------------------------------------------------------===## 26e5dd7070Spatrick 27e5dd7070Spatricksub silent_system { 28e5dd7070Spatrick my $HtmlDir = shift; 29e5dd7070Spatrick my $Command = shift; 30e5dd7070Spatrick 31e5dd7070Spatrick # Save STDOUT and STDERR and redirect to a temporary file. 32e5dd7070Spatrick open OLDOUT, ">&", \*STDOUT; 33e5dd7070Spatrick open OLDERR, ">&", \*STDERR; 34e5dd7070Spatrick my ($TmpFH, $TmpFile) = tempfile("temp_buf_XXXXXX", 35e5dd7070Spatrick DIR => $HtmlDir, 36e5dd7070Spatrick UNLINK => 1); 37e5dd7070Spatrick open(STDOUT, ">$TmpFile"); 38e5dd7070Spatrick open(STDERR, ">&", \*STDOUT); 39e5dd7070Spatrick 40e5dd7070Spatrick # Invoke 'system', STDOUT and STDERR are output to a temporary file. 41e5dd7070Spatrick system $Command, @_; 42e5dd7070Spatrick 43e5dd7070Spatrick # Restore STDOUT and STDERR. 44e5dd7070Spatrick open STDOUT, ">&", \*OLDOUT; 45e5dd7070Spatrick open STDERR, ">&", \*OLDERR; 46e5dd7070Spatrick 47e5dd7070Spatrick return $TmpFH; 48e5dd7070Spatrick} 49e5dd7070Spatrick 50e5dd7070Spatrick##===----------------------------------------------------------------------===## 51e5dd7070Spatrick# Compiler command setup. 52e5dd7070Spatrick##===----------------------------------------------------------------------===## 53e5dd7070Spatrick 54e5dd7070Spatrick# Search in the PATH if the compiler exists 55e5dd7070Spatricksub SearchInPath { 56e5dd7070Spatrick my $file = shift; 57e5dd7070Spatrick foreach my $dir (split (':', $ENV{PATH})) { 58e5dd7070Spatrick if (-x "$dir/$file") { 59e5dd7070Spatrick return 1; 60e5dd7070Spatrick } 61e5dd7070Spatrick } 62e5dd7070Spatrick return 0; 63e5dd7070Spatrick} 64e5dd7070Spatrick 65e5dd7070Spatrickmy $Compiler; 66e5dd7070Spatrickmy $Clang; 67e5dd7070Spatrickmy $DefaultCCompiler; 68e5dd7070Spatrickmy $DefaultCXXCompiler; 69e5dd7070Spatrickmy $IsCXX; 70e5dd7070Spatrickmy $AnalyzerTarget; 71e5dd7070Spatrick 72e5dd7070Spatrick# If on OSX, use xcrun to determine the SDK root. 73e5dd7070Spatrickmy $UseXCRUN = 0; 74e5dd7070Spatrick 75*12c85518Srobertif (`uname -s` =~ m/Darwin/) { 76e5dd7070Spatrick $DefaultCCompiler = 'clang'; 77e5dd7070Spatrick $DefaultCXXCompiler = 'clang++'; 78e5dd7070Spatrick # Older versions of OSX do not have xcrun to 79e5dd7070Spatrick # query the SDK location. 80e5dd7070Spatrick if (-x "/usr/bin/xcrun") { 81e5dd7070Spatrick $UseXCRUN = 1; 82e5dd7070Spatrick } 83*12c85518Srobert} elsif (`uname -s` =~ m/(FreeBSD|OpenBSD)/) { 84a9ac8606Spatrick $DefaultCCompiler = 'cc'; 85a9ac8606Spatrick $DefaultCXXCompiler = 'c++'; 86e5dd7070Spatrick} else { 87e5dd7070Spatrick $DefaultCCompiler = 'gcc'; 88e5dd7070Spatrick $DefaultCXXCompiler = 'g++'; 89e5dd7070Spatrick} 90e5dd7070Spatrick 91e5dd7070Spatrickif ($FindBin::Script =~ /c\+\+-analyzer/) { 92e5dd7070Spatrick $Compiler = $ENV{'CCC_CXX'}; 93e5dd7070Spatrick if (!defined $Compiler || (! -x $Compiler && ! SearchInPath($Compiler))) { $Compiler = $DefaultCXXCompiler; } 94e5dd7070Spatrick 95e5dd7070Spatrick $Clang = $ENV{'CLANG_CXX'}; 96e5dd7070Spatrick if (!defined $Clang || ! -x $Clang) { $Clang = 'clang++'; } 97e5dd7070Spatrick 98e5dd7070Spatrick $IsCXX = 1 99e5dd7070Spatrick} 100e5dd7070Spatrickelse { 101e5dd7070Spatrick $Compiler = $ENV{'CCC_CC'}; 102e5dd7070Spatrick if (!defined $Compiler || (! -x $Compiler && ! SearchInPath($Compiler))) { $Compiler = $DefaultCCompiler; } 103e5dd7070Spatrick 104e5dd7070Spatrick $Clang = $ENV{'CLANG'}; 105e5dd7070Spatrick if (!defined $Clang || ! -x $Clang) { $Clang = 'clang'; } 106e5dd7070Spatrick 107e5dd7070Spatrick $IsCXX = 0 108e5dd7070Spatrick} 109e5dd7070Spatrick 110e5dd7070Spatrick$AnalyzerTarget = $ENV{'CLANG_ANALYZER_TARGET'}; 111e5dd7070Spatrick 112e5dd7070Spatrick##===----------------------------------------------------------------------===## 113e5dd7070Spatrick# Cleanup. 114e5dd7070Spatrick##===----------------------------------------------------------------------===## 115e5dd7070Spatrick 116e5dd7070Spatrickmy $ReportFailures = $ENV{'CCC_REPORT_FAILURES'}; 117e5dd7070Spatrickif (!defined $ReportFailures) { $ReportFailures = 1; } 118e5dd7070Spatrick 119e5dd7070Spatrickmy $CleanupFile; 120e5dd7070Spatrickmy $ResultFile; 121e5dd7070Spatrick 122e5dd7070Spatrick# Remove any stale files at exit. 123e5dd7070SpatrickEND { 124e5dd7070Spatrick if (defined $ResultFile && -z $ResultFile) { 125e5dd7070Spatrick unlink($ResultFile); 126e5dd7070Spatrick } 127e5dd7070Spatrick if (defined $CleanupFile) { 128e5dd7070Spatrick unlink($CleanupFile); 129e5dd7070Spatrick } 130e5dd7070Spatrick} 131e5dd7070Spatrick 132e5dd7070Spatrick##----------------------------------------------------------------------------## 133e5dd7070Spatrick# Process Clang Crashes. 134e5dd7070Spatrick##----------------------------------------------------------------------------## 135e5dd7070Spatrick 136e5dd7070Spatricksub GetPPExt { 137e5dd7070Spatrick my $Lang = shift; 138e5dd7070Spatrick if ($Lang =~ /objective-c\+\+/) { return ".mii" }; 139e5dd7070Spatrick if ($Lang =~ /objective-c/) { return ".mi"; } 140e5dd7070Spatrick if ($Lang =~ /c\+\+/) { return ".ii"; } 141e5dd7070Spatrick return ".i"; 142e5dd7070Spatrick} 143e5dd7070Spatrick 144e5dd7070Spatrick# Set this to 1 if we want to include 'parser rejects' files. 145e5dd7070Spatrickmy $IncludeParserRejects = 0; 146e5dd7070Spatrickmy $ParserRejects = "Parser Rejects"; 147e5dd7070Spatrickmy $AttributeIgnored = "Attribute Ignored"; 148e5dd7070Spatrickmy $OtherError = "Other Error"; 149e5dd7070Spatrick 150e5dd7070Spatricksub ProcessClangFailure { 151e5dd7070Spatrick my ($Clang, $Lang, $file, $Args, $HtmlDir, $ErrorType, $ofile) = @_; 152e5dd7070Spatrick my $Dir = "$HtmlDir/failures"; 153e5dd7070Spatrick mkpath $Dir; 154e5dd7070Spatrick 155e5dd7070Spatrick my $prefix = "clang_crash"; 156e5dd7070Spatrick if ($ErrorType eq $ParserRejects) { 157e5dd7070Spatrick $prefix = "clang_parser_rejects"; 158e5dd7070Spatrick } 159e5dd7070Spatrick elsif ($ErrorType eq $AttributeIgnored) { 160e5dd7070Spatrick $prefix = "clang_attribute_ignored"; 161e5dd7070Spatrick } 162e5dd7070Spatrick elsif ($ErrorType eq $OtherError) { 163e5dd7070Spatrick $prefix = "clang_other_error"; 164e5dd7070Spatrick } 165e5dd7070Spatrick 166e5dd7070Spatrick # Generate the preprocessed file with Clang. 167e5dd7070Spatrick my ($PPH, $PPFile) = tempfile( $prefix . "_XXXXXX", 168e5dd7070Spatrick SUFFIX => GetPPExt($Lang), 169e5dd7070Spatrick DIR => $Dir); 170e5dd7070Spatrick close ($PPH); 171e5dd7070Spatrick system $Clang, @$Args, "-E", "-o", $PPFile; 172e5dd7070Spatrick 173e5dd7070Spatrick # Create the info file. 174e5dd7070Spatrick open (OUT, ">", "$PPFile.info.txt") or die "Cannot open $PPFile.info.txt\n"; 175e5dd7070Spatrick print OUT abs_path($file), "\n"; 176e5dd7070Spatrick print OUT "$ErrorType\n"; 177e5dd7070Spatrick print OUT "@$Args\n"; 178e5dd7070Spatrick close OUT; 179e5dd7070Spatrick `uname -a >> $PPFile.info.txt 2>&1`; 180e5dd7070Spatrick `"$Compiler" -v >> $PPFile.info.txt 2>&1`; 181e5dd7070Spatrick rename($ofile, "$PPFile.stderr.txt"); 182e5dd7070Spatrick return (basename $PPFile); 183e5dd7070Spatrick} 184e5dd7070Spatrick 185e5dd7070Spatrick##----------------------------------------------------------------------------## 186e5dd7070Spatrick# Running the analyzer. 187e5dd7070Spatrick##----------------------------------------------------------------------------## 188e5dd7070Spatrick 189e5dd7070Spatricksub GetCCArgs { 190e5dd7070Spatrick my $HtmlDir = shift; 191e5dd7070Spatrick my $mode = shift; 192e5dd7070Spatrick my $Args = shift; 193e5dd7070Spatrick my $line; 194e5dd7070Spatrick my $OutputStream = silent_system($HtmlDir, $Clang, "-###", $mode, @$Args); 195e5dd7070Spatrick while (<$OutputStream>) { 196e5dd7070Spatrick next if (!/\s"?-cc1"?\s/); 197e5dd7070Spatrick $line = $_; 198e5dd7070Spatrick } 199e5dd7070Spatrick die "could not find clang line\n" if (!defined $line); 200e5dd7070Spatrick # Strip leading and trailing whitespace characters. 201e5dd7070Spatrick $line =~ s/^\s+|\s+$//g; 202e5dd7070Spatrick my @items = quotewords('\s+', 0, $line); 203e5dd7070Spatrick my $cmd = shift @items; 204e5dd7070Spatrick die "cannot find 'clang' in 'clang' command\n" if (!($cmd =~ /clang/)); 205e5dd7070Spatrick return \@items; 206e5dd7070Spatrick} 207e5dd7070Spatrick 208e5dd7070Spatricksub Analyze { 209e5dd7070Spatrick my ($Clang, $OriginalArgs, $AnalyzeArgs, $Lang, $Output, $Verbose, $HtmlDir, 210e5dd7070Spatrick $file) = @_; 211e5dd7070Spatrick 212e5dd7070Spatrick my @Args = @$OriginalArgs; 213e5dd7070Spatrick my $Cmd; 214e5dd7070Spatrick my @CmdArgs; 215e5dd7070Spatrick my @CmdArgsSansAnalyses; 216e5dd7070Spatrick 217e5dd7070Spatrick if ($Lang =~ /header/) { 218e5dd7070Spatrick exit 0 if (!defined ($Output)); 219e5dd7070Spatrick $Cmd = 'cp'; 220e5dd7070Spatrick push @CmdArgs, $file; 221e5dd7070Spatrick # Remove the PCH extension. 222e5dd7070Spatrick $Output =~ s/[.]gch$//; 223e5dd7070Spatrick push @CmdArgs, $Output; 224e5dd7070Spatrick @CmdArgsSansAnalyses = @CmdArgs; 225e5dd7070Spatrick } 226e5dd7070Spatrick else { 227e5dd7070Spatrick $Cmd = $Clang; 228e5dd7070Spatrick 229e5dd7070Spatrick # Create arguments for doing regular parsing. 230e5dd7070Spatrick my $SyntaxArgs = GetCCArgs($HtmlDir, "-fsyntax-only", \@Args); 231e5dd7070Spatrick @CmdArgsSansAnalyses = @$SyntaxArgs; 232e5dd7070Spatrick 233e5dd7070Spatrick # Create arguments for doing static analysis. 234e5dd7070Spatrick if (defined $ResultFile) { 235e5dd7070Spatrick push @Args, '-o', $ResultFile; 236e5dd7070Spatrick } 237e5dd7070Spatrick elsif (defined $HtmlDir) { 238e5dd7070Spatrick push @Args, '-o', $HtmlDir; 239e5dd7070Spatrick } 240e5dd7070Spatrick if ($Verbose) { 241e5dd7070Spatrick push @Args, "-Xclang", "-analyzer-display-progress"; 242e5dd7070Spatrick } 243e5dd7070Spatrick 244e5dd7070Spatrick foreach my $arg (@$AnalyzeArgs) { 245e5dd7070Spatrick push @Args, "-Xclang", $arg; 246e5dd7070Spatrick } 247e5dd7070Spatrick 248e5dd7070Spatrick if (defined $AnalyzerTarget) { 249e5dd7070Spatrick push @Args, "-target", $AnalyzerTarget; 250e5dd7070Spatrick } 251e5dd7070Spatrick 252e5dd7070Spatrick my $AnalysisArgs = GetCCArgs($HtmlDir, "--analyze", \@Args); 253e5dd7070Spatrick @CmdArgs = @$AnalysisArgs; 254e5dd7070Spatrick } 255e5dd7070Spatrick 256e5dd7070Spatrick my @PrintArgs; 257e5dd7070Spatrick my $dir; 258e5dd7070Spatrick 259e5dd7070Spatrick if ($Verbose) { 260e5dd7070Spatrick $dir = getcwd(); 261e5dd7070Spatrick print STDERR "\n[LOCATION]: $dir\n"; 262e5dd7070Spatrick push @PrintArgs,"'$Cmd'"; 263e5dd7070Spatrick foreach my $arg (@CmdArgs) { 264e5dd7070Spatrick push @PrintArgs,"\'$arg\'"; 265e5dd7070Spatrick } 266e5dd7070Spatrick } 267e5dd7070Spatrick if ($Verbose == 1) { 268e5dd7070Spatrick # We MUST print to stderr. Some clients use the stdout output of 269e5dd7070Spatrick # gcc for various purposes. 270e5dd7070Spatrick print STDERR join(' ', @PrintArgs); 271e5dd7070Spatrick print STDERR "\n"; 272e5dd7070Spatrick } 273e5dd7070Spatrick elsif ($Verbose == 2) { 274e5dd7070Spatrick print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n"; 275e5dd7070Spatrick } 276e5dd7070Spatrick 277e5dd7070Spatrick # Save STDOUT and STDERR of clang to a temporary file and reroute 278e5dd7070Spatrick # all clang output to ccc-analyzer's STDERR. 279e5dd7070Spatrick # We save the output file in the 'crashes' directory if clang encounters 280e5dd7070Spatrick # any problems with the file. 281e5dd7070Spatrick my ($ofh, $ofile) = tempfile("clang_output_XXXXXX", DIR => $HtmlDir); 282e5dd7070Spatrick 283e5dd7070Spatrick my $OutputStream = silent_system($HtmlDir, $Cmd, @CmdArgs); 284e5dd7070Spatrick while ( <$OutputStream> ) { 285e5dd7070Spatrick print $ofh $_; 286e5dd7070Spatrick print STDERR $_; 287e5dd7070Spatrick } 288e5dd7070Spatrick my $Result = $?; 289e5dd7070Spatrick close $ofh; 290e5dd7070Spatrick 291e5dd7070Spatrick # Did the command die because of a signal? 292e5dd7070Spatrick if ($ReportFailures) { 293e5dd7070Spatrick if ($Result & 127 and $Cmd eq $Clang and defined $HtmlDir) { 294e5dd7070Spatrick ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses, 295e5dd7070Spatrick $HtmlDir, "Crash", $ofile); 296e5dd7070Spatrick } 297e5dd7070Spatrick elsif ($Result) { 298e5dd7070Spatrick if ($IncludeParserRejects && !($file =~/conftest/)) { 299e5dd7070Spatrick ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses, 300e5dd7070Spatrick $HtmlDir, $ParserRejects, $ofile); 301e5dd7070Spatrick } else { 302e5dd7070Spatrick ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses, 303e5dd7070Spatrick $HtmlDir, $OtherError, $ofile); 304e5dd7070Spatrick } 305e5dd7070Spatrick } 306e5dd7070Spatrick else { 307e5dd7070Spatrick # Check if there were any unhandled attributes. 308e5dd7070Spatrick if (open(CHILD, $ofile)) { 309e5dd7070Spatrick my %attributes_not_handled; 310e5dd7070Spatrick 311e5dd7070Spatrick # Don't flag warnings about the following attributes that we 312e5dd7070Spatrick # know are currently not supported by Clang. 313e5dd7070Spatrick $attributes_not_handled{"cdecl"} = 1; 314e5dd7070Spatrick 315e5dd7070Spatrick my $ppfile; 316e5dd7070Spatrick while (<CHILD>) { 317e5dd7070Spatrick next if (! /warning: '([^\']+)' attribute ignored/); 318e5dd7070Spatrick 319e5dd7070Spatrick # Have we already spotted this unhandled attribute? 320e5dd7070Spatrick next if (defined $attributes_not_handled{$1}); 321e5dd7070Spatrick $attributes_not_handled{$1} = 1; 322e5dd7070Spatrick 323e5dd7070Spatrick # Get the name of the attribute file. 324e5dd7070Spatrick my $dir = "$HtmlDir/failures"; 325e5dd7070Spatrick my $afile = "$dir/attribute_ignored_$1.txt"; 326e5dd7070Spatrick 327e5dd7070Spatrick # Only create another preprocessed file if the attribute file 328e5dd7070Spatrick # doesn't exist yet. 329e5dd7070Spatrick next if (-e $afile); 330e5dd7070Spatrick 331e5dd7070Spatrick # Add this file to the list of files that contained this attribute. 332e5dd7070Spatrick # Generate a preprocessed file if we haven't already. 333e5dd7070Spatrick if (!(defined $ppfile)) { 334e5dd7070Spatrick $ppfile = ProcessClangFailure($Clang, $Lang, $file, 335e5dd7070Spatrick \@CmdArgsSansAnalyses, 336e5dd7070Spatrick $HtmlDir, $AttributeIgnored, $ofile); 337e5dd7070Spatrick } 338e5dd7070Spatrick 339e5dd7070Spatrick mkpath $dir; 340e5dd7070Spatrick open(AFILE, ">$afile"); 341e5dd7070Spatrick print AFILE "$ppfile\n"; 342e5dd7070Spatrick close(AFILE); 343e5dd7070Spatrick } 344e5dd7070Spatrick close CHILD; 345e5dd7070Spatrick } 346e5dd7070Spatrick } 347e5dd7070Spatrick } 348e5dd7070Spatrick 349e5dd7070Spatrick unlink($ofile); 350e5dd7070Spatrick} 351e5dd7070Spatrick 352e5dd7070Spatrick##----------------------------------------------------------------------------## 353e5dd7070Spatrick# Lookup tables. 354e5dd7070Spatrick##----------------------------------------------------------------------------## 355e5dd7070Spatrick 356e5dd7070Spatrickmy %CompileOptionMap = ( 357e5dd7070Spatrick '-nostdinc' => 0, 358e5dd7070Spatrick '-include' => 1, 359e5dd7070Spatrick '-idirafter' => 1, 360e5dd7070Spatrick '-imacros' => 1, 361e5dd7070Spatrick '-iprefix' => 1, 362e5dd7070Spatrick '-iquote' => 1, 363e5dd7070Spatrick '-iwithprefix' => 1, 364e5dd7070Spatrick '-iwithprefixbefore' => 1 365e5dd7070Spatrick); 366e5dd7070Spatrick 367e5dd7070Spatrickmy %LinkerOptionMap = ( 368e5dd7070Spatrick '-framework' => 1, 369e5dd7070Spatrick '-fobjc-link-runtime' => 0 370e5dd7070Spatrick); 371e5dd7070Spatrick 372e5dd7070Spatrickmy %CompilerLinkerOptionMap = ( 373e5dd7070Spatrick '-Wwrite-strings' => 0, 374e5dd7070Spatrick '-ftrapv-handler' => 1, # specifically call out separated -f flag 375e5dd7070Spatrick '-mios-simulator-version-min' => 0, # This really has 1 argument, but always has '=' 376e5dd7070Spatrick '-isysroot' => 1, 377e5dd7070Spatrick '-arch' => 1, 378e5dd7070Spatrick '-m32' => 0, 379e5dd7070Spatrick '-m64' => 0, 380e5dd7070Spatrick '-stdlib' => 0, # This is really a 1 argument, but always has '=' 381e5dd7070Spatrick '--sysroot' => 1, 382e5dd7070Spatrick '-target' => 1, 383e5dd7070Spatrick '-v' => 0, 384e5dd7070Spatrick '-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '=' 385e5dd7070Spatrick '-miphoneos-version-min' => 0, # This is really a 1 argument, but always has '=' 386e5dd7070Spatrick '--target' => 0 387e5dd7070Spatrick); 388e5dd7070Spatrick 389e5dd7070Spatrickmy %IgnoredOptionMap = ( 390e5dd7070Spatrick '-MT' => 1, # Ignore these preprocessor options. 391e5dd7070Spatrick '-MF' => 1, 392e5dd7070Spatrick 393e5dd7070Spatrick '-fsyntax-only' => 0, 394e5dd7070Spatrick '-save-temps' => 0, 395e5dd7070Spatrick '-install_name' => 1, 396e5dd7070Spatrick '-exported_symbols_list' => 1, 397e5dd7070Spatrick '-current_version' => 1, 398e5dd7070Spatrick '-compatibility_version' => 1, 399e5dd7070Spatrick '-init' => 1, 400e5dd7070Spatrick '-e' => 1, 401e5dd7070Spatrick '-seg1addr' => 1, 402e5dd7070Spatrick '-bundle_loader' => 1, 403e5dd7070Spatrick '-multiply_defined' => 1, 404e5dd7070Spatrick '-sectorder' => 3, 405e5dd7070Spatrick '--param' => 1, 406e5dd7070Spatrick '-u' => 1, 407e5dd7070Spatrick '--serialize-diagnostics' => 1 408e5dd7070Spatrick); 409e5dd7070Spatrick 410e5dd7070Spatrickmy %LangMap = ( 411e5dd7070Spatrick 'c' => $IsCXX ? 'c++' : 'c', 412e5dd7070Spatrick 'cp' => 'c++', 413e5dd7070Spatrick 'cpp' => 'c++', 414e5dd7070Spatrick 'cxx' => 'c++', 415e5dd7070Spatrick 'txx' => 'c++', 416e5dd7070Spatrick 'cc' => 'c++', 417e5dd7070Spatrick 'C' => 'c++', 418e5dd7070Spatrick 'ii' => 'c++-cpp-output', 419e5dd7070Spatrick 'i' => $IsCXX ? 'c++-cpp-output' : 'cpp-output', 420e5dd7070Spatrick 'm' => 'objective-c', 421e5dd7070Spatrick 'mi' => 'objective-c-cpp-output', 422e5dd7070Spatrick 'mm' => 'objective-c++', 423e5dd7070Spatrick 'mii' => 'objective-c++-cpp-output', 424e5dd7070Spatrick); 425e5dd7070Spatrick 426e5dd7070Spatrickmy %UniqueOptions = ( 427e5dd7070Spatrick '-isysroot' => 0 428e5dd7070Spatrick); 429e5dd7070Spatrick 430e5dd7070Spatrick##----------------------------------------------------------------------------## 431e5dd7070Spatrick# Languages accepted. 432e5dd7070Spatrick##----------------------------------------------------------------------------## 433e5dd7070Spatrick 434e5dd7070Spatrickmy %LangsAccepted = ( 435e5dd7070Spatrick "objective-c" => 1, 436e5dd7070Spatrick "c" => 1, 437e5dd7070Spatrick "c++" => 1, 438e5dd7070Spatrick "objective-c++" => 1, 439e5dd7070Spatrick "cpp-output" => 1, 440e5dd7070Spatrick "objective-c-cpp-output" => 1, 441e5dd7070Spatrick "c++-cpp-output" => 1 442e5dd7070Spatrick); 443e5dd7070Spatrick 444e5dd7070Spatrick##----------------------------------------------------------------------------## 445e5dd7070Spatrick# Main Logic. 446e5dd7070Spatrick##----------------------------------------------------------------------------## 447e5dd7070Spatrick 448e5dd7070Spatrickmy $Action = 'link'; 449e5dd7070Spatrickmy @CompileOpts; 450e5dd7070Spatrickmy @LinkOpts; 451e5dd7070Spatrickmy @Files; 452e5dd7070Spatrickmy $Lang; 453e5dd7070Spatrickmy $Output; 454e5dd7070Spatrickmy %Uniqued; 455e5dd7070Spatrick 456e5dd7070Spatrick# Forward arguments to gcc. 457e5dd7070Spatrickmy $Status = system($Compiler,@ARGV); 458e5dd7070Spatrickif (defined $ENV{'CCC_ANALYZER_LOG'}) { 459e5dd7070Spatrick print STDERR "$Compiler @ARGV\n"; 460e5dd7070Spatrick} 461e5dd7070Spatrickif ($Status) { exit($Status >> 8); } 462e5dd7070Spatrick 463e5dd7070Spatrick# Get the analysis options. 464e5dd7070Spatrickmy $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'}; 465e5dd7070Spatrick 466e5dd7070Spatrick# Get the plugins to load. 467e5dd7070Spatrickmy $Plugins = $ENV{'CCC_ANALYZER_PLUGINS'}; 468e5dd7070Spatrick 469e5dd7070Spatrick# Get the constraints engine. 470e5dd7070Spatrickmy $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'}; 471e5dd7070Spatrick 472e5dd7070Spatrick#Get the internal stats setting. 473e5dd7070Spatrickmy $InternalStats = $ENV{'CCC_ANALYZER_INTERNAL_STATS'}; 474e5dd7070Spatrick 475e5dd7070Spatrick# Get the output format. 476e5dd7070Spatrickmy $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'}; 477e5dd7070Spatrickif (!defined $OutputFormat) { $OutputFormat = "html"; } 478e5dd7070Spatrick 479e5dd7070Spatrick# Get the config options. 480e5dd7070Spatrickmy $ConfigOptions = $ENV{'CCC_ANALYZER_CONFIG'}; 481e5dd7070Spatrick 482e5dd7070Spatrick# Determine the level of verbosity. 483e5dd7070Spatrickmy $Verbose = 0; 484e5dd7070Spatrickif (defined $ENV{'CCC_ANALYZER_VERBOSE'}) { $Verbose = 1; } 485e5dd7070Spatrickif (defined $ENV{'CCC_ANALYZER_LOG'}) { $Verbose = 2; } 486e5dd7070Spatrick 487e5dd7070Spatrick# Get the HTML output directory. 488e5dd7070Spatrickmy $HtmlDir = $ENV{'CCC_ANALYZER_HTML'}; 489e5dd7070Spatrick 490e5dd7070Spatrick# Get force-analyze-debug-code option. 491e5dd7070Spatrickmy $ForceAnalyzeDebugCode = $ENV{'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE'}; 492e5dd7070Spatrick 493e5dd7070Spatrickmy %DisabledArchs = ('ppc' => 1, 'ppc64' => 1); 494e5dd7070Spatrickmy %ArchsSeen; 495e5dd7070Spatrickmy $HadArch = 0; 496e5dd7070Spatrickmy $HasSDK = 0; 497e5dd7070Spatrick 498e5dd7070Spatrick# Process the arguments. 499e5dd7070Spatrickforeach (my $i = 0; $i < scalar(@ARGV); ++$i) { 500e5dd7070Spatrick my $Arg = $ARGV[$i]; 501e5dd7070Spatrick my @ArgParts = split /=/,$Arg,2; 502e5dd7070Spatrick my $ArgKey = $ArgParts[0]; 503e5dd7070Spatrick 504e5dd7070Spatrick # Be friendly to "" in the argument list. 505e5dd7070Spatrick if (!defined($ArgKey)) { 506e5dd7070Spatrick next; 507e5dd7070Spatrick } 508e5dd7070Spatrick 509e5dd7070Spatrick # Modes ccc-analyzer supports 510e5dd7070Spatrick if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; } 511e5dd7070Spatrick elsif ($Arg eq '-c') { $Action = 'compile'; } 512e5dd7070Spatrick elsif ($Arg =~ /^-print-prog-name/) { exit 0; } 513e5dd7070Spatrick 514e5dd7070Spatrick # Specially handle duplicate cases of -arch 515e5dd7070Spatrick if ($Arg eq "-arch") { 516e5dd7070Spatrick my $arch = $ARGV[$i+1]; 517e5dd7070Spatrick # We don't want to process 'ppc' because of Clang's lack of support 518e5dd7070Spatrick # for Altivec (also some #defines won't likely be defined correctly, etc.) 519e5dd7070Spatrick if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; } 520e5dd7070Spatrick $HadArch = 1; 521e5dd7070Spatrick ++$i; 522e5dd7070Spatrick next; 523e5dd7070Spatrick } 524e5dd7070Spatrick 525e5dd7070Spatrick # On OSX/iOS, record if an SDK path was specified. This 526e5dd7070Spatrick # is innocuous for other platforms, so the check just happens. 527e5dd7070Spatrick if ($Arg =~ /^-isysroot/) { 528e5dd7070Spatrick $HasSDK = 1; 529e5dd7070Spatrick } 530e5dd7070Spatrick 531e5dd7070Spatrick # Options with possible arguments that should pass through to compiler. 532e5dd7070Spatrick if (defined $CompileOptionMap{$ArgKey}) { 533e5dd7070Spatrick my $Cnt = $CompileOptionMap{$ArgKey}; 534e5dd7070Spatrick push @CompileOpts,$Arg; 535e5dd7070Spatrick while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; } 536e5dd7070Spatrick next; 537e5dd7070Spatrick } 538e5dd7070Spatrick # Handle the case where there isn't a space after -iquote 539e5dd7070Spatrick if ($Arg =~ /^-iquote.*/) { 540e5dd7070Spatrick push @CompileOpts,$Arg; 541e5dd7070Spatrick next; 542e5dd7070Spatrick } 543e5dd7070Spatrick 544e5dd7070Spatrick # Options with possible arguments that should pass through to linker. 545e5dd7070Spatrick if (defined $LinkerOptionMap{$ArgKey}) { 546e5dd7070Spatrick my $Cnt = $LinkerOptionMap{$ArgKey}; 547e5dd7070Spatrick push @LinkOpts,$Arg; 548e5dd7070Spatrick while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; } 549e5dd7070Spatrick next; 550e5dd7070Spatrick } 551e5dd7070Spatrick 552e5dd7070Spatrick # Options with possible arguments that should pass through to both compiler 553e5dd7070Spatrick # and the linker. 554e5dd7070Spatrick if (defined $CompilerLinkerOptionMap{$ArgKey}) { 555e5dd7070Spatrick my $Cnt = $CompilerLinkerOptionMap{$ArgKey}; 556e5dd7070Spatrick 557e5dd7070Spatrick # Check if this is an option that should have a unique value, and if so 558e5dd7070Spatrick # determine if the value was checked before. 559e5dd7070Spatrick if ($UniqueOptions{$Arg}) { 560e5dd7070Spatrick if (defined $Uniqued{$Arg}) { 561e5dd7070Spatrick $i += $Cnt; 562e5dd7070Spatrick next; 563e5dd7070Spatrick } 564e5dd7070Spatrick $Uniqued{$Arg} = 1; 565e5dd7070Spatrick } 566e5dd7070Spatrick 567e5dd7070Spatrick push @CompileOpts,$Arg; 568e5dd7070Spatrick push @LinkOpts,$Arg; 569e5dd7070Spatrick 570e5dd7070Spatrick if (scalar @ArgParts == 1) { 571e5dd7070Spatrick while ($Cnt > 0) { 572e5dd7070Spatrick ++$i; --$Cnt; 573e5dd7070Spatrick push @CompileOpts, $ARGV[$i]; 574e5dd7070Spatrick push @LinkOpts, $ARGV[$i]; 575e5dd7070Spatrick } 576e5dd7070Spatrick } 577e5dd7070Spatrick next; 578e5dd7070Spatrick } 579e5dd7070Spatrick 580e5dd7070Spatrick # Ignored options. 581e5dd7070Spatrick if (defined $IgnoredOptionMap{$ArgKey}) { 582e5dd7070Spatrick my $Cnt = $IgnoredOptionMap{$ArgKey}; 583e5dd7070Spatrick while ($Cnt > 0) { 584e5dd7070Spatrick ++$i; --$Cnt; 585e5dd7070Spatrick } 586e5dd7070Spatrick next; 587e5dd7070Spatrick } 588e5dd7070Spatrick 589e5dd7070Spatrick # Compile mode flags. 590e5dd7070Spatrick if ($Arg =~ /^-(?:[DIU]|isystem)(.*)$/) { 591e5dd7070Spatrick my $Tmp = $Arg; 592e5dd7070Spatrick if ($1 eq '') { 593e5dd7070Spatrick # FIXME: Check if we are going off the end. 594e5dd7070Spatrick ++$i; 595e5dd7070Spatrick $Tmp = $Arg . $ARGV[$i]; 596e5dd7070Spatrick } 597e5dd7070Spatrick push @CompileOpts,$Tmp; 598e5dd7070Spatrick next; 599e5dd7070Spatrick } 600e5dd7070Spatrick 601e5dd7070Spatrick if ($Arg =~ /^-m.*/) { 602e5dd7070Spatrick push @CompileOpts,$Arg; 603e5dd7070Spatrick next; 604e5dd7070Spatrick } 605e5dd7070Spatrick 606e5dd7070Spatrick # Language. 607e5dd7070Spatrick if ($Arg eq '-x') { 608e5dd7070Spatrick $Lang = $ARGV[$i+1]; 609e5dd7070Spatrick ++$i; next; 610e5dd7070Spatrick } 611e5dd7070Spatrick 612e5dd7070Spatrick # Output file. 613e5dd7070Spatrick if ($Arg eq '-o') { 614e5dd7070Spatrick ++$i; 615e5dd7070Spatrick $Output = $ARGV[$i]; 616e5dd7070Spatrick next; 617e5dd7070Spatrick } 618e5dd7070Spatrick 619e5dd7070Spatrick # Get the link mode. 620e5dd7070Spatrick if ($Arg =~ /^-[l,L,O]/) { 621e5dd7070Spatrick if ($Arg eq '-O') { push @LinkOpts,'-O1'; } 622e5dd7070Spatrick elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; } 623e5dd7070Spatrick else { push @LinkOpts,$Arg; } 624e5dd7070Spatrick 625e5dd7070Spatrick # Must pass this along for the __OPTIMIZE__ macro 626e5dd7070Spatrick if ($Arg =~ /^-O/) { push @CompileOpts,$Arg; } 627e5dd7070Spatrick next; 628e5dd7070Spatrick } 629e5dd7070Spatrick 630e5dd7070Spatrick if ($Arg =~ /^-std=/) { 631e5dd7070Spatrick push @CompileOpts,$Arg; 632e5dd7070Spatrick next; 633e5dd7070Spatrick } 634e5dd7070Spatrick 635e5dd7070Spatrick # Get the compiler/link mode. 636e5dd7070Spatrick if ($Arg =~ /^-F(.+)$/) { 637e5dd7070Spatrick my $Tmp = $Arg; 638e5dd7070Spatrick if ($1 eq '') { 639e5dd7070Spatrick # FIXME: Check if we are going off the end. 640e5dd7070Spatrick ++$i; 641e5dd7070Spatrick $Tmp = $Arg . $ARGV[$i]; 642e5dd7070Spatrick } 643e5dd7070Spatrick push @CompileOpts,$Tmp; 644e5dd7070Spatrick push @LinkOpts,$Tmp; 645e5dd7070Spatrick next; 646e5dd7070Spatrick } 647e5dd7070Spatrick 648e5dd7070Spatrick # Input files. 649e5dd7070Spatrick if ($Arg eq '-filelist') { 650e5dd7070Spatrick # FIXME: Make sure we aren't walking off the end. 651e5dd7070Spatrick open(IN, $ARGV[$i+1]); 652e5dd7070Spatrick while (<IN>) { s/\015?\012//; push @Files,$_; } 653e5dd7070Spatrick close(IN); 654e5dd7070Spatrick ++$i; 655e5dd7070Spatrick next; 656e5dd7070Spatrick } 657e5dd7070Spatrick 658e5dd7070Spatrick if ($Arg =~ /^-f/) { 659e5dd7070Spatrick push @CompileOpts,$Arg; 660e5dd7070Spatrick push @LinkOpts,$Arg; 661e5dd7070Spatrick next; 662e5dd7070Spatrick } 663e5dd7070Spatrick 664e5dd7070Spatrick # Handle -Wno-. We don't care about extra warnings, but 665e5dd7070Spatrick # we should suppress ones that we don't want to see. 666e5dd7070Spatrick if ($Arg =~ /^-Wno-/) { 667e5dd7070Spatrick push @CompileOpts, $Arg; 668e5dd7070Spatrick next; 669e5dd7070Spatrick } 670e5dd7070Spatrick 671e5dd7070Spatrick # Handle -Xclang some-arg. Add both arguments to the compiler options. 672e5dd7070Spatrick if ($Arg =~ /^-Xclang$/) { 673e5dd7070Spatrick # FIXME: Check if we are going off the end. 674e5dd7070Spatrick ++$i; 675e5dd7070Spatrick push @CompileOpts, $Arg; 676e5dd7070Spatrick push @CompileOpts, $ARGV[$i]; 677e5dd7070Spatrick next; 678e5dd7070Spatrick } 679e5dd7070Spatrick 680e5dd7070Spatrick if (!($Arg =~ /^-/)) { 681e5dd7070Spatrick push @Files, $Arg; 682e5dd7070Spatrick next; 683e5dd7070Spatrick } 684e5dd7070Spatrick} 685e5dd7070Spatrick 686e5dd7070Spatrick# Forcedly enable debugging if requested by user. 687e5dd7070Spatrickif ($ForceAnalyzeDebugCode) { 688e5dd7070Spatrick push @CompileOpts, '-UNDEBUG'; 689e5dd7070Spatrick} 690e5dd7070Spatrick 691e5dd7070Spatrick# If we are on OSX and have an installation where the 692e5dd7070Spatrick# default SDK is inferred by xcrun use xcrun to infer 693e5dd7070Spatrick# the SDK. 694e5dd7070Spatrickif (not $HasSDK and $UseXCRUN) { 695e5dd7070Spatrick my $sdk = `/usr/bin/xcrun --show-sdk-path -sdk macosx`; 696e5dd7070Spatrick chomp $sdk; 697e5dd7070Spatrick push @CompileOpts, "-isysroot", $sdk; 698e5dd7070Spatrick} 699e5dd7070Spatrick 700e5dd7070Spatrickif ($Action eq 'compile' or $Action eq 'link') { 701e5dd7070Spatrick my @Archs = keys %ArchsSeen; 702e5dd7070Spatrick # Skip the file if we don't support the architectures specified. 703e5dd7070Spatrick exit 0 if ($HadArch && scalar(@Archs) == 0); 704e5dd7070Spatrick 705e5dd7070Spatrick foreach my $file (@Files) { 706e5dd7070Spatrick # Determine the language for the file. 707e5dd7070Spatrick my $FileLang = $Lang; 708e5dd7070Spatrick 709e5dd7070Spatrick if (!defined($FileLang)) { 710e5dd7070Spatrick # Infer the language from the extension. 711e5dd7070Spatrick if ($file =~ /[.]([^.]+)$/) { 712e5dd7070Spatrick $FileLang = $LangMap{$1}; 713e5dd7070Spatrick } 714e5dd7070Spatrick } 715e5dd7070Spatrick 716e5dd7070Spatrick # FileLang still not defined? Skip the file. 717e5dd7070Spatrick next if (!defined $FileLang); 718e5dd7070Spatrick 719e5dd7070Spatrick # Language not accepted? 720e5dd7070Spatrick next if (!defined $LangsAccepted{$FileLang}); 721e5dd7070Spatrick 722e5dd7070Spatrick my @CmdArgs; 723e5dd7070Spatrick my @AnalyzeArgs; 724e5dd7070Spatrick 725e5dd7070Spatrick if ($FileLang ne 'unknown') { 726e5dd7070Spatrick push @CmdArgs, '-x', $FileLang; 727e5dd7070Spatrick } 728e5dd7070Spatrick 729e5dd7070Spatrick if (defined $ConstraintsModel) { 730e5dd7070Spatrick push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel"; 731e5dd7070Spatrick } 732e5dd7070Spatrick 733e5dd7070Spatrick if (defined $InternalStats) { 734e5dd7070Spatrick push @AnalyzeArgs, "-analyzer-stats"; 735e5dd7070Spatrick } 736e5dd7070Spatrick 737e5dd7070Spatrick if (defined $Analyses) { 738e5dd7070Spatrick push @AnalyzeArgs, split '\s+', $Analyses; 739e5dd7070Spatrick } 740e5dd7070Spatrick 741e5dd7070Spatrick if (defined $Plugins) { 742e5dd7070Spatrick push @AnalyzeArgs, split '\s+', $Plugins; 743e5dd7070Spatrick } 744e5dd7070Spatrick 745e5dd7070Spatrick if (defined $OutputFormat) { 746e5dd7070Spatrick push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat; 747e5dd7070Spatrick if ($OutputFormat =~ /plist/ || $OutputFormat =~ /sarif/) { 748e5dd7070Spatrick # Change "Output" to be a file. 749e5dd7070Spatrick my $Suffix = $OutputFormat =~ /plist/ ? ".plist" : ".sarif"; 750e5dd7070Spatrick my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => $Suffix, 751e5dd7070Spatrick DIR => $HtmlDir); 752e5dd7070Spatrick $ResultFile = $f; 753e5dd7070Spatrick # If the HtmlDir is not set, we should clean up the plist files. 754e5dd7070Spatrick if (!defined $HtmlDir || $HtmlDir eq "") { 755e5dd7070Spatrick $CleanupFile = $f; 756e5dd7070Spatrick } 757e5dd7070Spatrick } 758e5dd7070Spatrick } 759e5dd7070Spatrick if (defined $ConfigOptions) { 760e5dd7070Spatrick push @AnalyzeArgs, split '\s+', $ConfigOptions; 761e5dd7070Spatrick } 762e5dd7070Spatrick 763e5dd7070Spatrick push @CmdArgs, @CompileOpts; 764e5dd7070Spatrick push @CmdArgs, $file; 765e5dd7070Spatrick 766e5dd7070Spatrick if (scalar @Archs) { 767e5dd7070Spatrick foreach my $arch (@Archs) { 768e5dd7070Spatrick my @NewArgs; 769e5dd7070Spatrick push @NewArgs, '-arch', $arch; 770e5dd7070Spatrick push @NewArgs, @CmdArgs; 771e5dd7070Spatrick Analyze($Clang, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output, 772e5dd7070Spatrick $Verbose, $HtmlDir, $file); 773e5dd7070Spatrick } 774e5dd7070Spatrick } 775e5dd7070Spatrick else { 776e5dd7070Spatrick Analyze($Clang, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output, 777e5dd7070Spatrick $Verbose, $HtmlDir, $file); 778e5dd7070Spatrick } 779e5dd7070Spatrick } 780e5dd7070Spatrick} 781