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