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