ccc-analyzer revision de19bb1f030813a0096de28be68d70533dd1ba9e
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 FindBin;
18use Cwd qw/ getcwd abs_path /;
19use File::Temp qw/ tempfile /;
20use File::Path qw / mkpath /;
21use File::Basename;
22use Text::ParseWords;
23
24##===----------------------------------------------------------------------===##
25# Compiler command setup.
26##===----------------------------------------------------------------------===##
27
28my $Compiler;
29my $Clang;
30
31if ($FindBin::Script =~ /c\+\+-analyzer/) {
32  $Compiler = $ENV{'CCC_CXX'};
33  if (!defined $Compiler) { $Compiler = "g++"; }
34  
35  $Clang = $ENV{'CLANG_CXX'};
36  if (!defined $Clang) { $Clang = 'clang++'; }
37}
38else {
39  $Compiler = $ENV{'CCC_CC'};
40  if (!defined $Compiler) { $Compiler = "gcc"; }
41
42  $Clang = $ENV{'CLANG'};
43  if (!defined $Clang) { $Clang = 'clang'; }
44}
45
46##===----------------------------------------------------------------------===##
47# Cleanup.
48##===----------------------------------------------------------------------===##
49
50my $ReportFailures = $ENV{'CCC_REPORT_FAILURES'};
51if (!defined $ReportFailures) { $ReportFailures = 1; }
52
53my $CleanupFile;
54my $ResultFile;
55
56# Remove any stale files at exit.
57END { 
58  if (defined $ResultFile && -z $ResultFile) {
59    `rm -f $ResultFile`;
60  }
61  if (defined $CleanupFile) {
62    `rm -f $CleanupFile`;
63  }
64}
65
66##----------------------------------------------------------------------------##
67#  Process Clang Crashes.
68##----------------------------------------------------------------------------##
69
70sub GetPPExt {
71  my $Lang = shift;
72  if ($Lang =~ /objective-c\+\+/) { return ".mii" };
73  if ($Lang =~ /objective-c/) { return ".mi"; }
74  if ($Lang =~ /c\+\+/) { return ".ii"; }
75  return ".i";
76}
77
78# Set this to 1 if we want to include 'parser rejects' files.
79my $IncludeParserRejects = 0;
80my $ParserRejects = "Parser Rejects";
81my $AttributeIgnored = "Attribute Ignored";
82my $OtherError = "Other Error";
83
84sub ProcessClangFailure {
85  my ($Clang, $Lang, $file, $Args, $HtmlDir, $ErrorType, $ofile) = @_;
86  my $Dir = "$HtmlDir/failures";
87  mkpath $Dir;
88  
89  my $prefix = "clang_crash";
90  if ($ErrorType eq $ParserRejects) {
91    $prefix = "clang_parser_rejects";
92  }
93  elsif ($ErrorType eq $AttributeIgnored) {
94    $prefix = "clang_attribute_ignored";
95  }
96  elsif ($ErrorType eq $OtherError) {
97    $prefix = "clang_other_error";
98  }
99
100  # Generate the preprocessed file with Clang.
101  my ($PPH, $PPFile) = tempfile( $prefix . "_XXXXXX",
102                                 SUFFIX => GetPPExt($Lang),
103                                 DIR => $Dir);
104  system $Clang, @$Args, "-E", "-o", $PPFile;
105  close ($PPH);
106  
107  # Create the info file.
108  open (OUT, ">", "$PPFile.info.txt") or die "Cannot open $PPFile.info.txt\n";
109  print OUT abs_path($file), "\n";
110  print OUT "$ErrorType\n";
111  print OUT "@$Args\n";
112  close OUT;
113  `uname -a >> $PPFile.info.txt 2>&1`;
114  `$Compiler -v >> $PPFile.info.txt 2>&1`;
115  system 'mv',$ofile,"$PPFile.stderr.txt";
116  return (basename $PPFile);
117}
118
119##----------------------------------------------------------------------------##
120#  Running the analyzer.
121##----------------------------------------------------------------------------##
122
123sub GetCCArgs {
124  my $mode = shift;
125  my $Args = shift;
126  
127  pipe (FROM_CHILD, TO_PARENT);
128  my $pid = fork();
129  if ($pid == 0) {
130    close FROM_CHILD;
131    open(STDOUT,">&", \*TO_PARENT);
132    open(STDERR,">&", \*TO_PARENT);
133    exec $Clang, "-###", $mode, @$Args;
134  }  
135  close(TO_PARENT);
136  my $line;
137  while (<FROM_CHILD>) {
138    next if (!/-cc1/);
139    $line = $_;
140  }
141
142  waitpid($pid,0);
143  close(FROM_CHILD);
144  
145  die "could not find clang line\n" if (!defined $line);
146  # Strip the newline and initial whitspace
147  chomp $line;
148  $line =~ s/^\s+//;
149  my @items = quotewords('\s+', 0, $line);
150  my $cmd = shift @items;
151  die "cannot find 'clang' in 'clang' command\n" if (!($cmd =~ /clang/));
152  return \@items;
153}
154
155sub Analyze {
156  my ($Clang, $OriginalArgs, $AnalyzeArgs, $Lang, $Output, $Verbose, $HtmlDir,
157      $file) = @_;
158
159  my @Args = @$OriginalArgs;
160  my $Cmd;
161  my @CmdArgs;
162  my @CmdArgsSansAnalyses;
163
164  if ($Lang =~ /header/) {
165    exit 0 if (!defined ($Output));
166    $Cmd = 'cp';
167    push @CmdArgs, $file;
168    # Remove the PCH extension.
169    $Output =~ s/[.]gch$//;
170    push @CmdArgs, $Output;
171    @CmdArgsSansAnalyses = @CmdArgs;
172  }
173  else {
174    $Cmd = $Clang;
175    if ($Lang eq "objective-c" || $Lang eq "objective-c++") {
176      push @Args,'-DIBOutlet=__attribute__((iboutlet))';
177      push @Args,'-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection)))';
178      push @Args,'-DIBAction=void)__attribute__((ibaction)';
179    }
180
181    # Create arguments for doing regular parsing.
182    my $SyntaxArgs = GetCCArgs("-fsyntax-only", \@Args);
183    @CmdArgsSansAnalyses = @$SyntaxArgs;
184
185    # Create arguments for doing static analysis.
186    if (defined $ResultFile) {
187      push @Args, '-o', $ResultFile;
188    }
189    elsif (defined $HtmlDir) {
190      push @Args, '-o', $HtmlDir;
191    }
192    if ($Verbose) {
193      push @Args, "-Xclang", "-analyzer-display-progress";
194    }
195
196    foreach my $arg (@$AnalyzeArgs) {
197      push @Args, "-Xclang", $arg;
198    }
199
200    # Display Ubiviz graph?
201    if (defined $ENV{'CCC_UBI'}) {   
202      push @Args, "-Xclang", "-analyzer-viz-egraph-ubigraph";
203    }
204
205    my $AnalysisArgs = GetCCArgs("--analyze", \@Args);
206    @CmdArgs = @$AnalysisArgs;
207  }
208
209  my @PrintArgs;
210  my $dir;
211
212  if ($Verbose) {
213    $dir = getcwd();
214    print STDERR "\n[LOCATION]: $dir\n";
215    push @PrintArgs,"'$Cmd'";
216    foreach my $arg (@CmdArgs) {
217        push @PrintArgs,"\'$arg\'";
218    }
219  }
220  if ($Verbose == 1) {
221    # We MUST print to stderr.  Some clients use the stdout output of
222    # gcc for various purposes. 
223    print STDERR join(' ', @PrintArgs);
224    print STDERR "\n";
225  }
226  elsif ($Verbose == 2) {
227    print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n";
228  }
229
230  # Capture the STDERR of clang and send it to a temporary file.
231  # Capture the STDOUT of clang and reroute it to ccc-analyzer's STDERR.
232  # We save the output file in the 'crashes' directory if clang encounters
233  # any problems with the file.  
234  pipe (FROM_CHILD, TO_PARENT);
235  my $pid = fork();
236  if ($pid == 0) {
237    close FROM_CHILD;
238    open(STDOUT,">&", \*TO_PARENT);
239    open(STDERR,">&", \*TO_PARENT);
240    exec $Cmd, @CmdArgs;
241  }
242
243  close TO_PARENT;
244  my ($ofh, $ofile) = tempfile("clang_output_XXXXXX", DIR => $HtmlDir);
245  
246  while (<FROM_CHILD>) {
247    print $ofh $_;
248    print STDERR $_;
249  }
250
251  waitpid($pid,0);
252  close(FROM_CHILD);
253  my $Result = $?;
254
255  # Did the command die because of a signal?
256  if ($ReportFailures) {
257    if ($Result & 127 and $Cmd eq $Clang and defined $HtmlDir) {
258      ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses,
259                          $HtmlDir, "Crash", $ofile);
260    }
261    elsif ($Result) {
262      if ($IncludeParserRejects && !($file =~/conftest/)) {
263        ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses,
264                            $HtmlDir, $ParserRejects, $ofile);
265      } else {
266        ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses,
267                            $HtmlDir, $OtherError, $ofile);      	
268      }
269    }
270    else {
271      # Check if there were any unhandled attributes.
272      if (open(CHILD, $ofile)) {
273        my %attributes_not_handled;
274
275        # Don't flag warnings about the following attributes that we
276        # know are currently not supported by Clang.
277        $attributes_not_handled{"cdecl"} = 1;
278
279        my $ppfile;
280        while (<CHILD>) {
281          next if (! /warning: '([^\']+)' attribute ignored/);
282
283          # Have we already spotted this unhandled attribute?
284          next if (defined $attributes_not_handled{$1});
285          $attributes_not_handled{$1} = 1;
286        
287          # Get the name of the attribute file.
288          my $dir = "$HtmlDir/failures";
289          my $afile = "$dir/attribute_ignored_$1.txt";
290        
291          # Only create another preprocessed file if the attribute file
292          # doesn't exist yet.
293          next if (-e $afile);
294        
295          # Add this file to the list of files that contained this attribute.
296          # Generate a preprocessed file if we haven't already.
297          if (!(defined $ppfile)) {
298            $ppfile = ProcessClangFailure($Clang, $Lang, $file,
299                                          \@CmdArgsSansAnalyses,
300                                          $HtmlDir, $AttributeIgnored, $ofile);
301          }
302
303          mkpath $dir;
304          open(AFILE, ">$afile");
305          print AFILE "$ppfile\n";
306          close(AFILE);
307        }
308        close CHILD;
309      }
310    }
311  }
312  
313  unlink($ofile);
314}
315
316##----------------------------------------------------------------------------##
317#  Lookup tables.
318##----------------------------------------------------------------------------##
319
320my %CompileOptionMap = (
321  '-nostdinc' => 0,
322  '-fblocks' => 0,
323  '-fno-builtin' => 0,
324  '-fobjc-arc' => 0,
325  '-fobjc-gc-only' => 0,
326  '-fobjc-gc' => 0,
327  '-ffreestanding' => 0,
328  '-include' => 1,
329  '-idirafter' => 1,
330  '-imacros' => 1,
331  '-iprefix' => 1,
332  '-iquote' => 1,
333  '-isystem' => 1,
334  '-iwithprefix' => 1,
335  '-iwithprefixbefore' => 1
336);
337
338my %LinkerOptionMap = (
339  '-framework' => 1
340);
341
342my %CompilerLinkerOptionMap = (
343  '-isysroot' => 1,
344  '-arch' => 1,
345  '-m32' => 0,
346  '-m64' => 0,
347  '-v' => 0,
348  '-fpascal-strings' => 0,
349  '-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '='
350  '-miphoneos-version-min' => 0 # This is really a 1 argument, but always has '='
351);
352
353my %IgnoredOptionMap = (
354  '-MT' => 1,  # Ignore these preprocessor options.
355  '-MF' => 1,
356
357  '-fsyntax-only' => 0,
358  '-save-temps' => 0,
359  '-install_name' => 1,
360  '-exported_symbols_list' => 1,
361  '-current_version' => 1,
362  '-compatibility_version' => 1,
363  '-init' => 1,
364  '-e' => 1,
365  '-seg1addr' => 1,
366  '-bundle_loader' => 1,
367  '-multiply_defined' => 1,
368  '-sectorder' => 3,
369  '--param' => 1,
370  '-u' => 1
371);
372
373my %LangMap = (
374  'c'   => 'c',
375  'cp'  => 'c++',
376  'cpp' => 'c++',
377  'cc'  => 'c++',
378  'ii'  => 'c++',
379  'i'   => 'c-cpp-output',
380  'm'   => 'objective-c',
381  'mi'  => 'objective-c-cpp-output',
382  'mm'  => 'objective-c++'
383);
384
385my %UniqueOptions = (
386  '-isysroot' => 0  
387);
388
389##----------------------------------------------------------------------------##
390# Languages accepted.
391##----------------------------------------------------------------------------##
392
393my %LangsAccepted = (
394  "objective-c" => 1,
395  "c" => 1,
396  "c++" => 1,
397  "objective-c++" => 1
398);
399
400##----------------------------------------------------------------------------##
401#  Main Logic.
402##----------------------------------------------------------------------------##
403
404my $Action = 'link';
405my @CompileOpts;
406my @LinkOpts;
407my @Files;
408my $Lang;
409my $Output;
410my %Uniqued;
411
412# Forward arguments to gcc.
413my $Status = system($Compiler,@ARGV);
414if  (defined $ENV{'CCC_ANALYZER_LOG'}) {
415  print "$Compiler @ARGV\n";
416}
417if ($Status) { exit($Status >> 8); }
418
419# Get the analysis options.
420my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
421
422# Get the store model.
423my $StoreModel = $ENV{'CCC_ANALYZER_STORE_MODEL'};
424
425# Get the constraints engine.
426my $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'};
427
428# Get the output format.
429my $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'};
430if (!defined $OutputFormat) { $OutputFormat = "html"; }
431
432# Determine the level of verbosity.
433my $Verbose = 0;
434if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
435if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
436
437# Get the HTML output directory.
438my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
439
440my %DisabledArchs = ('ppc' => 1, 'ppc64' => 1);
441my %ArchsSeen;
442my $HadArch = 0;
443
444# Process the arguments.
445foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
446  my $Arg = $ARGV[$i];  
447  my ($ArgKey) = split /=/,$Arg,2;
448
449  # Modes ccc-analyzer supports
450  if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; }
451  elsif ($Arg eq '-c') { $Action = 'compile'; }
452  elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
453
454  # Specially handle duplicate cases of -arch
455  if ($Arg eq "-arch") {
456    my $arch = $ARGV[$i+1];
457    # We don't want to process 'ppc' because of Clang's lack of support
458    # for Altivec (also some #defines won't likely be defined correctly, etc.)
459    if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; }
460    $HadArch = 1;
461    ++$i;
462    next;
463  }
464
465  # Options with possible arguments that should pass through to compiler.
466  if (defined $CompileOptionMap{$ArgKey}) {
467    my $Cnt = $CompileOptionMap{$ArgKey};
468    push @CompileOpts,$Arg;
469    while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
470    next;
471  }
472
473  # Options with possible arguments that should pass through to linker.
474  if (defined $LinkerOptionMap{$ArgKey}) {
475    my $Cnt = $LinkerOptionMap{$ArgKey};
476    push @LinkOpts,$Arg;
477    while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
478    next;
479  }
480
481  # Options with possible arguments that should pass through to both compiler
482  # and the linker.
483  if (defined $CompilerLinkerOptionMap{$ArgKey}) {
484    my $Cnt = $CompilerLinkerOptionMap{$ArgKey};
485    
486    # Check if this is an option that should have a unique value, and if so
487    # determine if the value was checked before.
488    if ($UniqueOptions{$Arg}) {
489      if (defined $Uniqued{$Arg}) {
490        $i += $Cnt;
491        next;
492      }
493      $Uniqued{$Arg} = 1;
494    }
495    
496    push @CompileOpts,$Arg;    
497    push @LinkOpts,$Arg;
498
499    while ($Cnt > 0) {
500      ++$i; --$Cnt;
501      push @CompileOpts, $ARGV[$i];
502      push @LinkOpts, $ARGV[$i];
503    }
504    next;
505  }
506  
507  # Ignored options.
508  if (defined $IgnoredOptionMap{$ArgKey}) {
509    my $Cnt = $IgnoredOptionMap{$ArgKey};
510    while ($Cnt > 0) {
511      ++$i; --$Cnt;
512    }
513    next;
514  }
515  
516  # Compile mode flags.
517  if ($Arg =~ /^-[D,I,U](.*)$/) {
518    my $Tmp = $Arg;    
519    if ($1 eq '') {
520      # FIXME: Check if we are going off the end.
521      ++$i;
522      $Tmp = $Arg . $ARGV[$i];
523    }
524    push @CompileOpts,$Tmp;
525    next;
526  }
527  
528  # Language.
529  if ($Arg eq '-x') {
530    $Lang = $ARGV[$i+1];
531    ++$i; next;
532  }
533
534  # Output file.
535  if ($Arg eq '-o') {
536    ++$i;
537    $Output = $ARGV[$i];
538    next;
539  }
540  
541  # Get the link mode.
542  if ($Arg =~ /^-[l,L,O]/) {
543    if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
544    elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
545    else { push @LinkOpts,$Arg; }
546    next;
547  }
548  
549  if ($Arg =~ /^-std=/) {
550    push @CompileOpts,$Arg;
551    next;
552  }
553  
554#  if ($Arg =~ /^-f/) {
555#    # FIXME: Not sure if the remaining -fxxxx options have no arguments.
556#    push @CompileOpts,$Arg;
557#    push @LinkOpts,$Arg;  # FIXME: Not sure if these are link opts.
558#  }
559  
560  # Get the compiler/link mode.
561  if ($Arg =~ /^-F(.+)$/) {
562    my $Tmp = $Arg;
563    if ($1 eq '') {
564      # FIXME: Check if we are going off the end.
565      ++$i;
566      $Tmp = $Arg . $ARGV[$i];
567    }
568    push @CompileOpts,$Tmp;
569    push @LinkOpts,$Tmp;
570    next;
571  }
572
573  # Input files.
574  if ($Arg eq '-filelist') {
575    # FIXME: Make sure we aren't walking off the end.
576    open(IN, $ARGV[$i+1]);
577    while (<IN>) { s/\015?\012//; push @Files,$_; }
578    close(IN);
579    ++$i;
580    next;
581  }
582  
583  # Handle -Wno-.  We don't care about extra warnings, but
584  # we should suppress ones that we don't want to see.
585  if ($Arg =~ /^-Wno-/) {
586    push @CompileOpts, $Arg;
587    next;
588  }
589
590  if (!($Arg =~ /^-/)) {
591    push @Files, $Arg;
592    next;
593  }
594}
595
596if ($Action eq 'compile' or $Action eq 'link') {
597  my @Archs = keys %ArchsSeen;
598  # Skip the file if we don't support the architectures specified.
599  exit 0 if ($HadArch && scalar(@Archs) == 0);
600  
601  foreach my $file (@Files) {
602    # Determine the language for the file.
603    my $FileLang = $Lang;
604
605    if (!defined($FileLang)) {
606      # Infer the language from the extension.
607      if ($file =~ /[.]([^.]+)$/) {
608        $FileLang = $LangMap{$1};
609      }
610    }
611    
612    # FileLang still not defined?  Skip the file.
613    next if (!defined $FileLang);
614
615    # Language not accepted?
616    next if (!defined $LangsAccepted{$FileLang});
617
618    my @CmdArgs;
619    my @AnalyzeArgs;    
620    
621    if ($FileLang ne 'unknown') {
622      push @CmdArgs, '-x', $FileLang;
623    }
624
625    if (defined $StoreModel) {
626      push @AnalyzeArgs, "-analyzer-store=$StoreModel";
627    }
628
629    if (defined $ConstraintsModel) {
630      push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel";
631    }
632    
633    if (defined $Analyses) {
634      push @AnalyzeArgs, split '\s+', $Analyses;
635    }
636
637    if (defined $OutputFormat) {
638      push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat;
639      if ($OutputFormat =~ /plist/) {
640        # Change "Output" to be a file.
641        my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => ".plist",
642                               DIR => $HtmlDir);
643        $ResultFile = $f;
644        # If the HtmlDir is not set, we sould clean up the plist files.
645        if (!defined $HtmlDir || -z $HtmlDir) {
646        	$CleanupFile = $f; 
647        }
648      }
649    }
650
651    push @CmdArgs, @CompileOpts;
652    push @CmdArgs, $file;
653
654    if (scalar @Archs) {
655      foreach my $arch (@Archs) {
656        my @NewArgs;
657        push @NewArgs, '-arch', $arch;
658        push @NewArgs, @CmdArgs;
659        Analyze($Clang, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output,
660                $Verbose, $HtmlDir, $file);
661      }
662    }
663    else {
664      Analyze($Clang, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output,
665              $Verbose, $HtmlDir, $file);
666    }
667  }
668}
669
670exit($Status >> 8);
671
672