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