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