ccc-analyzer revision 1a8a8cbea639d0519f06285e12f64904d1158305
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-gc-only' => 0,
325  '-fobjc-gc' => 0,
326  '-ffreestanding' => 0,
327  '-include' => 1,
328  '-idirafter' => 1,
329  '-imacros' => 1,
330  '-iprefix' => 1,
331  '-iquote' => 1,
332  '-isystem' => 1,
333  '-iwithprefix' => 1,
334  '-iwithprefixbefore' => 1
335);
336
337my %LinkerOptionMap = (
338  '-framework' => 1
339);
340
341my %CompilerLinkerOptionMap = (
342  '-isysroot' => 1,
343  '-arch' => 1,
344  '-m32' => 0,
345  '-m64' => 0,
346  '-v' => 0,
347  '-fpascal-strings' => 0,
348  '-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '='
349  '-miphoneos-version-min' => 0 # This is really a 1 argument, but always has '='
350);
351
352my %IgnoredOptionMap = (
353  '-MT' => 1,  # Ignore these preprocessor options.
354  '-MF' => 1,
355
356  '-fsyntax-only' => 0,
357  '-save-temps' => 0,
358  '-install_name' => 1,
359  '-exported_symbols_list' => 1,
360  '-current_version' => 1,
361  '-compatibility_version' => 1,
362  '-init' => 1,
363  '-e' => 1,
364  '-seg1addr' => 1,
365  '-bundle_loader' => 1,
366  '-multiply_defined' => 1,
367  '-sectorder' => 3,
368  '--param' => 1,
369  '-u' => 1
370);
371
372my %LangMap = (
373  'c'   => 'c',
374  'cp'  => 'c++',
375  'cpp' => 'c++',
376  'cc'  => 'c++',
377  'ii'  => 'c++',
378  'i'   => 'c-cpp-output',
379  'm'   => 'objective-c',
380  'mi'  => 'objective-c-cpp-output',
381  'mm'  => 'objective-c++'
382);
383
384my %UniqueOptions = (
385  '-isysroot' => 0  
386);
387
388##----------------------------------------------------------------------------##
389# Languages accepted.
390##----------------------------------------------------------------------------##
391
392my %LangsAccepted = (
393  "objective-c" => 1,
394  "c" => 1,
395  "c++" => 1,
396  "objective-c++" => 1
397);
398
399##----------------------------------------------------------------------------##
400#  Main Logic.
401##----------------------------------------------------------------------------##
402
403my $Action = 'link';
404my @CompileOpts;
405my @LinkOpts;
406my @Files;
407my $Lang;
408my $Output;
409my %Uniqued;
410
411# Forward arguments to gcc.
412my $Status = system($Compiler,@ARGV);
413if  (defined $ENV{'CCC_ANALYZER_LOG'}) {
414  print "$Compiler @ARGV\n";
415}
416if ($Status) { exit($Status >> 8); }
417
418# Get the analysis options.
419my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
420
421# Get the store model.
422my $StoreModel = $ENV{'CCC_ANALYZER_STORE_MODEL'};
423
424# Get the constraints engine.
425my $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'};
426
427# Get the output format.
428my $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'};
429if (!defined $OutputFormat) { $OutputFormat = "html"; }
430
431# Determine the level of verbosity.
432my $Verbose = 0;
433if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
434if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
435
436# Get the HTML output directory.
437my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
438
439my %DisabledArchs = ('ppc' => 1, 'ppc64' => 1);
440my %ArchsSeen;
441my $HadArch = 0;
442
443# Process the arguments.
444foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
445  my $Arg = $ARGV[$i];  
446  my ($ArgKey) = split /=/,$Arg,2;
447
448  # Modes ccc-analyzer supports
449  if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; }
450  elsif ($Arg eq '-c') { $Action = 'compile'; }
451  elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
452
453  # Specially handle duplicate cases of -arch
454  if ($Arg eq "-arch") {
455    my $arch = $ARGV[$i+1];
456    # We don't want to process 'ppc' because of Clang's lack of support
457    # for Altivec (also some #defines won't likely be defined correctly, etc.)
458    if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; }
459    $HadArch = 1;
460    ++$i;
461    next;
462  }
463
464  # Options with possible arguments that should pass through to compiler.
465  if (defined $CompileOptionMap{$ArgKey}) {
466    my $Cnt = $CompileOptionMap{$ArgKey};
467    push @CompileOpts,$Arg;
468    while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
469    next;
470  }
471
472  # Options with possible arguments that should pass through to linker.
473  if (defined $LinkerOptionMap{$ArgKey}) {
474    my $Cnt = $LinkerOptionMap{$ArgKey};
475    push @LinkOpts,$Arg;
476    while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
477    next;
478  }
479
480  # Options with possible arguments that should pass through to both compiler
481  # and the linker.
482  if (defined $CompilerLinkerOptionMap{$ArgKey}) {
483    my $Cnt = $CompilerLinkerOptionMap{$ArgKey};
484    
485    # Check if this is an option that should have a unique value, and if so
486    # determine if the value was checked before.
487    if ($UniqueOptions{$Arg}) {
488      if (defined $Uniqued{$Arg}) {
489        $i += $Cnt;
490        next;
491      }
492      $Uniqued{$Arg} = 1;
493    }
494    
495    push @CompileOpts,$Arg;    
496    push @LinkOpts,$Arg;
497
498    while ($Cnt > 0) {
499      ++$i; --$Cnt;
500      push @CompileOpts, $ARGV[$i];
501      push @LinkOpts, $ARGV[$i];
502    }
503    next;
504  }
505  
506  # Ignored options.
507  if (defined $IgnoredOptionMap{$ArgKey}) {
508    my $Cnt = $IgnoredOptionMap{$ArgKey};
509    while ($Cnt > 0) {
510      ++$i; --$Cnt;
511    }
512    next;
513  }
514  
515  # Compile mode flags.
516  if ($Arg =~ /^-[D,I,U](.*)$/) {
517    my $Tmp = $Arg;    
518    if ($1 eq '') {
519      # FIXME: Check if we are going off the end.
520      ++$i;
521      $Tmp = $Arg . $ARGV[$i];
522    }
523    push @CompileOpts,$Tmp;
524    next;
525  }
526  
527  # Language.
528  if ($Arg eq '-x') {
529    $Lang = $ARGV[$i+1];
530    ++$i; next;
531  }
532
533  # Output file.
534  if ($Arg eq '-o') {
535    ++$i;
536    $Output = $ARGV[$i];
537    next;
538  }
539  
540  # Get the link mode.
541  if ($Arg =~ /^-[l,L,O]/) {
542    if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
543    elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
544    else { push @LinkOpts,$Arg; }
545    next;
546  }
547  
548  if ($Arg =~ /^-std=/) {
549    push @CompileOpts,$Arg;
550    next;
551  }
552  
553#  if ($Arg =~ /^-f/) {
554#    # FIXME: Not sure if the remaining -fxxxx options have no arguments.
555#    push @CompileOpts,$Arg;
556#    push @LinkOpts,$Arg;  # FIXME: Not sure if these are link opts.
557#  }
558  
559  # Get the compiler/link mode.
560  if ($Arg =~ /^-F(.+)$/) {
561    my $Tmp = $Arg;
562    if ($1 eq '') {
563      # FIXME: Check if we are going off the end.
564      ++$i;
565      $Tmp = $Arg . $ARGV[$i];
566    }
567    push @CompileOpts,$Tmp;
568    push @LinkOpts,$Tmp;
569    next;
570  }
571
572  # Input files.
573  if ($Arg eq '-filelist') {
574    # FIXME: Make sure we aren't walking off the end.
575    open(IN, $ARGV[$i+1]);
576    while (<IN>) { s/\015?\012//; push @Files,$_; }
577    close(IN);
578    ++$i;
579    next;
580  }
581  
582  # Handle -Wno-.  We don't care about extra warnings, but
583  # we should suppress ones that we don't want to see.
584  if ($Arg =~ /^-Wno-/) {
585    push @CompileOpts, $Arg;
586    next;
587  }
588
589  if (!($Arg =~ /^-/)) {
590    push @Files, $Arg;
591    next;
592  }
593}
594
595if ($Action eq 'compile' or $Action eq 'link') {
596  my @Archs = keys %ArchsSeen;
597  # Skip the file if we don't support the architectures specified.
598  exit 0 if ($HadArch && scalar(@Archs) == 0);
599  
600  foreach my $file (@Files) {
601    # Determine the language for the file.
602    my $FileLang = $Lang;
603
604    if (!defined($FileLang)) {
605      # Infer the language from the extension.
606      if ($file =~ /[.]([^.]+)$/) {
607        $FileLang = $LangMap{$1};
608      }
609    }
610    
611    # FileLang still not defined?  Skip the file.
612    next if (!defined $FileLang);
613
614    # Language not accepted?
615    next if (!defined $LangsAccepted{$FileLang});
616
617    my @CmdArgs;
618    my @AnalyzeArgs;    
619    
620    if ($FileLang ne 'unknown') {
621      push @CmdArgs, '-x', $FileLang;
622    }
623
624    if (defined $StoreModel) {
625      push @AnalyzeArgs, "-analyzer-store=$StoreModel";
626    }
627
628    if (defined $ConstraintsModel) {
629      push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel";
630    }
631    
632    if (defined $Analyses) {
633      push @AnalyzeArgs, split '\s+', $Analyses;
634    }
635
636    if (defined $OutputFormat) {
637      push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat;
638      if ($OutputFormat =~ /plist/) {
639        # Change "Output" to be a file.
640        my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => ".plist",
641                               DIR => $HtmlDir);
642        $ResultFile = $f;
643        # If the HtmlDir is not set, we sould clean up the plist files.
644        if (!defined $HtmlDir || -z $HtmlDir) {
645        	$CleanupFile = $f; 
646        }
647      }
648    }
649
650    push @CmdArgs, @CompileOpts;
651    push @CmdArgs, $file;
652
653    if (scalar @Archs) {
654      foreach my $arch (@Archs) {
655        my @NewArgs;
656        push @NewArgs, '-arch', $arch;
657        push @NewArgs, @CmdArgs;
658        Analyze($Clang, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output,
659                $Verbose, $HtmlDir, $file);
660      }
661    }
662    else {
663      Analyze($Clang, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output,
664              $Verbose, $HtmlDir, $file);
665    }
666  }
667}
668
669exit($Status >> 8);
670
671