ccc-analyzer revision 2dd7ad13bbbd43cb940040725a30cb7789bd2774
1#!/usr/bin/env perl
2#
3#                     The LLVM Compiler Infrastructure
4#
5# This file is distributed under the University of Illinois Open Source
6# License. See LICENSE.TXT for details.
7#
8##===----------------------------------------------------------------------===##
9#
10#  A script designed to interpose between the build system and gcc.  It invokes
11#  both gcc and the static analyzer.
12#
13##===----------------------------------------------------------------------===##
14
15use strict;
16use warnings;
17use Cwd;
18use File::Temp qw/ tempfile /;
19use File::Path qw / mkpath /;
20 
21##----------------------------------------------------------------------------##
22#  Process Clang Crashes.
23##----------------------------------------------------------------------------##
24
25sub GetPPExt {
26  my $Lang = shift;
27  if ($Lang =~ /objective-c/) { return ".mi"; }
28  return ".i";
29}
30
31sub ProcessClangFailure {
32  my ($Lang, $file, $Args, $HtmlDir, $ErrorType) = @_;
33  my $Dir = "$HtmlDir/crashes";
34  mkpath $Dir;
35  my ($PPH, $PPFile) = tempfile("clang_crash_XXXXXX",
36                                  SUFFIX => GetPPExt($Lang),
37                                  DIR => $Dir);
38
39  system "gcc", @$Args, "-E", "-o", $PPFile;  
40  close ($PPH);
41  open (OUT, ">", "$PPFile.info") or die "Cannot open $PPFile.info\n";
42  print OUT "$file\n";
43  print OUT "$ErrorType\n";
44  print OUT "@$Args\n";
45  close OUT;
46}
47
48##----------------------------------------------------------------------------##
49#  Running the analyzer.
50##----------------------------------------------------------------------------##
51
52sub Analyze {
53  my ($Clang, $Args, $Lang, $Output, $Verbose, $HtmlDir, $file, $Analyses) = @_;
54
55  # Skip anything related to C++.
56  return if ($Lang =~ /c[+][+]/);
57
58  my $RunAnalyzer = 0;
59  my $Cmd;
60  my @CmdArgs;
61  my @CmdArgsSansAnalyses;
62  
63  if ($Lang =~ /header/) {
64    exit 0 if (!defined ($Output));
65    $Cmd = 'cp';
66    push @CmdArgs,$file;
67    # Remove the PCH extension.
68    $Output =~ s/[.]gch$//;
69    push @CmdArgs,$Output;
70    @CmdArgsSansAnalyses = @CmdArgs;    
71  }
72  else {
73    $Cmd = $Clang;
74    push @CmdArgs,'-DIBOutlet=__attribute__((iboutlet))';
75    push @CmdArgs,@$Args;
76    @CmdArgsSansAnalyses = @CmdArgs;
77    push @CmdArgs,(split /\s/,$Analyses);
78    $RunAnalyzer = 1;
79  }
80  
81  my @PrintArgs;
82  my $dir;
83  
84  if ($Verbose) {
85    $dir = getcwd();
86    print STDERR "\n[LOCATION]: $dir\n";
87    push @PrintArgs,"'$Cmd'";
88    foreach my $arg (@CmdArgs) { push @PrintArgs,"\'$arg\'"; }
89  }
90  
91  if ($Verbose == 1) {
92    # We MUST print to stderr.  Some clients use the stdout output of
93    # gcc for various purposes. 
94    print STDERR join(' ',@PrintArgs);
95    print STDERR "\n";
96  }
97  elsif ($Verbose == 2) {
98    print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n";
99  }
100  
101  if ($RunAnalyzer and defined($HtmlDir)) {
102    push @CmdArgs,'-o';
103    push @CmdArgs,$HtmlDir;
104  }
105
106   system $Cmd,@CmdArgs;
107
108   # Did the command die because of a signal?
109   if ($? & 127 and $Cmd eq $Clang and defined $HtmlDir) {
110     ProcessClangFailure($Lang, $file, \@CmdArgsSansAnalyses, $HtmlDir,
111                          "Crash");
112   }
113   elsif ($?) {
114     ProcessClangFailure($Lang, $file, \@CmdArgsSansAnalyses, $HtmlDir,
115                          "Parser Rejects");
116   }
117}
118
119##----------------------------------------------------------------------------##
120#  Lookup tables.
121##----------------------------------------------------------------------------##
122
123my %CompileOptionMap = (
124  '-nostdinc' => 0,
125  '-fobjc-gc-only' => 0,
126  '-fobjc-gc' => 0,  
127  '-include' => 1,
128  '-idirafter' => 1,
129  '-iprefix' => 1,
130  '-iquote' => 1,
131  '-isystem' => 1,
132  '-iwithprefix' => 1,
133  '-iwithprefixbefore' => 1
134);
135
136my %LinkerOptionMap = (
137  '-framework' => 1
138);
139
140my %CompilerLinkerOptionMap = (
141  '-isysroot' => 1,
142  '-arch' => 1,
143  '-v' => 0
144);
145
146my %IgnoredOptionMap = (
147  '-MT' => 1,  # Ignore these preprocessor options.
148  '-MF' => 1,
149
150  '-fsyntax-only' => 0,
151  '-save-temps' => 0,
152  '-install_name' => 1,
153  '-exported_symbols_list' => 1,
154  '-current_version' => 1,
155  '-compatibility_version' => 1,
156  '-init' => 1,
157  '-e' => 1,
158  '-seg1addr' => 1,
159  '-bundle_loader' => 1,
160  '-multiply_defined' => 1,
161  '-sectorder' => 3,
162  '--param' => 1,
163  '-u' => 1
164);
165
166my %LangMap = (
167  'c'   => 'c',
168  'cpp' => 'c++',
169  'cc'  => 'c++',
170  'i'   => 'c-cpp-output',
171  'm'   => 'objective-c',
172  'mi'  => 'objective-c-cpp-output'
173);
174
175##----------------------------------------------------------------------------##
176#  Main Logic.
177##----------------------------------------------------------------------------##
178
179my $Action = 'link';
180my @CompileOpts;
181my @LinkOpts;
182my @Files;
183my $Lang;
184my $Output;
185
186# Forward arguments to gcc.
187my $Status = system("gcc",@ARGV);
188if ($Status) { exit($Status); }
189
190# Get the analysis options.
191my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
192if (!defined($Analyses)) { $Analyses = '-checker-cfref'; }
193
194# Determine the level of verbosity.
195my $Verbose = 0;
196if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
197if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
198
199# Determine what clang executable to use.
200my $Clang = $ENV{'CLANG'};
201if (!defined $Clang) { $Clang = 'clang'; }
202
203# Get the HTML output directory.
204my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
205
206
207# Process the arguments.
208foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
209  my $Arg = $ARGV[$i];
210  
211  # Modes ccc-analyzer supports
212  if ($Arg eq '-E') { $Action = 'preprocess'; }
213  elsif ($Arg eq '-c') { $Action = 'compile'; }
214  elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
215    
216  # Options with possible arguments that should pass through to compiler.
217  if (defined $CompileOptionMap{$Arg}) {
218    my $Cnt = $CompileOptionMap{$Arg};
219    push @CompileOpts,$Arg;
220    while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
221    next;
222  }
223
224  # Options with possible arguments that should pass through to linker.
225  if (defined $LinkerOptionMap{$Arg}) {
226    my $Cnt = $LinkerOptionMap{$Arg};
227    push @LinkOpts,$Arg;
228    while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
229    next;
230  }
231
232  # Options with possible arguments that should pass through to both compiler
233  # and the linker.
234  if (defined $CompilerLinkerOptionMap{$Arg}) {
235    my $Cnt = $CompilerLinkerOptionMap{$Arg};
236    push @CompileOpts,$Arg;    
237    push @LinkOpts,$Arg;
238    while ($Cnt > 0) {
239      ++$i; --$Cnt;
240      push @CompileOpts, $ARGV[$i];
241      push @LinkOpts, $ARGV[$i];
242    }
243    next;
244  }
245  
246  # Ignored options.
247  if (defined $IgnoredOptionMap{$Arg}) {
248    my $Cnt = $IgnoredOptionMap{$Arg};
249    while ($Cnt > 0) {
250      ++$i; --$Cnt;
251    }
252    next;
253  }
254  
255  # Compile mode flags.
256  if ($Arg =~ /^-[D,I,U](.*)$/) {
257    my $Tmp = $Arg;    
258    if ($1 eq '') {
259      # FIXME: Check if we are going off the end.
260      ++$i;
261      $Tmp = $Arg . $ARGV[$i];
262    }
263    push @CompileOpts,$Tmp;
264    next;
265  }
266  
267  # Language.
268  if ($Arg eq '-x') {
269    $Lang = $ARGV[$i+1];
270    ++$i; next;
271  }
272
273  # Output file.
274  if ($Arg eq '-o') {
275    ++$i;
276    $Output = $ARGV[$i];
277    next;
278  }
279  
280  # Get the link mode.
281  if ($Arg =~ /^-[l,L,O]/) {
282    if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
283    elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
284    else { push @LinkOpts,$Arg; }
285    next;
286  }
287  
288  if ($Arg =~ /^-std=/) {
289    push @CompileOpts,$Arg;
290    next;
291  }
292  
293#  if ($Arg =~ /^-f/) {
294#    # FIXME: Not sure if the remaining -fxxxx options have no arguments.
295#    push @CompileOpts,$Arg;
296#    push @LinkOpts,$Arg;  # FIXME: Not sure if these are link opts.
297#  }
298  
299  # Get the compiler/link mode.
300  if ($Arg =~ /^-F(.+)$/) {
301    my $Tmp = $Arg;
302    if ($1 eq '') {
303      # FIXME: Check if we are going off the end.
304      ++$i;
305      $Tmp = $Arg . $ARGV[$i];
306    }
307    push @CompileOpts,$Tmp;
308    push @LinkOpts,$Tmp;
309    next;
310  }
311
312  # Input files.
313  if ($Arg eq '-filelist') {
314    # FIXME: Make sure we aren't walking off the end.
315    open(IN, $ARGV[$i+1]);
316    while (<IN>) { s/\015?\012//; push @Files,$_; }
317    close(IN);
318    ++$i; next;
319  }
320  
321  if (!($Arg =~ /^-/)) {
322    push @Files,$Arg; next;
323  }
324}
325
326if ($Action eq 'compile' or $Action eq 'link') {
327  foreach my $file (@Files) {
328    # Determine the language for the file.
329    my $FileLang = $Lang;
330
331    if (!defined($FileLang)) {
332      # Infer the language from the extension.
333      if ($file =~ /[.]([^.]+)$/) {
334        $FileLang = $LangMap{$1};
335      }
336    }
337    
338    next if (!defined $FileLang);
339    
340    my @AnalyzeArgs;
341    
342    if ($FileLang ne 'unknown') {
343      push @AnalyzeArgs,'-x';
344      push @AnalyzeArgs,$FileLang;
345    }
346
347    push @AnalyzeArgs,@CompileOpts;
348    push @AnalyzeArgs,$file;
349    
350    Analyze($Clang, \@AnalyzeArgs, $FileLang, $Output,
351            $Verbose, $HtmlDir, $file, $Analyses);
352  }
353}
354
355