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