xref: /openbsd-src/gnu/llvm/clang/tools/scan-build/libexec/ccc-analyzer (revision 12c855180aad702bbcca06e0398d774beeafb155)
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