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