ccc-analyzer revision d8fdde119630fa717457ab23b077f447bb2e9e3a
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    
164    if (defined $ENV{"CCC_EXPERIMENTAL_CHECKS"}) {
165      push @CmdArgs,"-analyzer-experimental-internal-checks";
166      push @CmdArgs,"-analyzer-experimental-checks";
167    }
168    
169    $RunAnalyzer = 1;
170  }
171  
172  # Add the analysis arguments passed down from scan-build.
173  foreach my $Arg (@$AnalyzeArgs) {
174    push @CmdArgs, $Arg;
175  }
176  
177  my @PrintArgs;
178  my $dir;
179
180  if ($RunAnalyzer) {
181    if (defined $ResultFile) {
182      push @CmdArgs,'-o';
183      push @CmdArgs, $ResultFile;
184    }
185    elsif (defined $HtmlDir) {
186      push @CmdArgs,'-o';
187      push @CmdArgs, $HtmlDir;
188    }
189  }
190  
191  if ($Verbose) {
192    $dir = getcwd();
193    print STDERR "\n[LOCATION]: $dir\n";
194    push @PrintArgs,"'$Cmd'";
195    foreach my $arg (@CmdArgs) { push @PrintArgs,"\'$arg\'"; }
196  }
197  
198  if ($Verbose == 1) {
199    # We MUST print to stderr.  Some clients use the stdout output of
200    # gcc for various purposes. 
201    print STDERR join(' ',@PrintArgs);
202    print STDERR "\n";
203  }
204  elsif ($Verbose == 2) {
205    print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n";
206  }
207  
208  if (defined $ENV{'CCC_UBI'}) {   
209    push @CmdArgs,"--analyzer-viz-egraph-ubigraph";
210  }
211  
212  # Capture the STDERR of clang and send it to a temporary file.
213  # Capture the STDOUT of clang and reroute it to ccc-analyzer's STDERR.
214  # We save the output file in the 'crashes' directory if clang encounters
215  # any problems with the file.  
216  pipe (FROM_CHILD, TO_PARENT);
217  my $pid = fork();
218  if ($pid == 0) {
219    close FROM_CHILD;
220    open(STDOUT,">&", \*TO_PARENT);
221    open(STDERR,">&", \*TO_PARENT);
222    exec $Cmd, @CmdArgs;
223  }
224  
225  close TO_PARENT;
226  my ($ofh, $ofile) = tempfile("clang_output_XXXXXX", DIR => $HtmlDir);
227  
228  while (<FROM_CHILD>) {
229    print $ofh $_;
230    print STDERR $_;    
231  }
232
233  waitpid($pid,0);
234  close(FROM_CHILD);
235  my $Result = $?;
236
237  # Did the command die because of a signal?
238  if ($ReportFailures) {
239    if ($Result & 127 and $Cmd eq $ClangCC and defined $HtmlDir) {
240      ProcessClangFailure($ClangCC, $Lang, $file, \@CmdArgsSansAnalyses,
241                          $HtmlDir, "Crash", $ofile);
242    }
243    elsif ($Result) {
244      if ($IncludeParserRejects && !($file =~/conftest/)) {
245        ProcessClangFailure($ClangCC, $Lang, $file, \@CmdArgsSansAnalyses,
246                            $HtmlDir, $ParserRejects, $ofile);
247      }
248    }
249    else {
250      # Check if there were any unhandled attributes.
251      if (open(CHILD, $ofile)) {
252        my %attributes_not_handled;
253      
254        # Don't flag warnings about the following attributes that we
255        # know are currently not supported by Clang.
256        $attributes_not_handled{"cdecl"} = 1;
257      
258        my $ppfile;
259        while (<CHILD>) {
260          next if (! /warning: '([^\']+)' attribute ignored/);
261
262          # Have we already spotted this unhandled attribute?
263          next if (defined $attributes_not_handled{$1});
264          $attributes_not_handled{$1} = 1;
265        
266          # Get the name of the attribute file.
267          my $dir = "$HtmlDir/failures";
268          my $afile = "$dir/attribute_ignored_$1.txt";
269        
270          # Only create another preprocessed file if the attribute file
271          # doesn't exist yet.
272          next if (-e $afile);
273        
274          # Add this file to the list of files that contained this attribute.
275          # Generate a preprocessed file if we haven't already.
276          if (!(defined $ppfile)) {
277            $ppfile = ProcessClangFailure($ClangCC, $Lang, $file,
278                                          \@CmdArgsSansAnalyses,
279                                          $HtmlDir, $AttributeIgnored, $ofile);
280          }
281
282          mkpath $dir;
283          open(AFILE, ">$afile");
284          print AFILE "$ppfile\n";
285          close(AFILE);
286        }
287        close CHILD;
288      }
289    }
290  }
291  
292  unlink($ofile);
293}
294
295##----------------------------------------------------------------------------##
296#  Lookup tables.
297##----------------------------------------------------------------------------##
298
299my %CompileOptionMap = (
300  '-nostdinc' => 0,
301  '-fblocks' => 0,
302  '-fobjc-gc-only' => 0,
303  '-fobjc-gc' => 0,
304  '-ffreestanding' => 0,
305  '-include' => 1,
306  '-idirafter' => 1,
307  '-iprefix' => 1,
308  '-iquote' => 1,
309  '-isystem' => 1,
310  '-iwithprefix' => 1,
311  '-iwithprefixbefore' => 1
312);
313
314my %LinkerOptionMap = (
315  '-framework' => 1
316);
317
318my %CompilerLinkerOptionMap = (
319  '-isysroot' => 1,
320  '-arch' => 1,
321  '-v' => 0,
322  '-fpascal-strings' => 0,
323  '-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '='
324  '-miphoneos-version-min' => 0 # This is really a 1 argument, but always has '='
325);
326
327my %IgnoredOptionMap = (
328  '-MT' => 1,  # Ignore these preprocessor options.
329  '-MF' => 1,
330
331  '-fsyntax-only' => 0,
332  '-save-temps' => 0,
333  '-install_name' => 1,
334  '-exported_symbols_list' => 1,
335  '-current_version' => 1,
336  '-compatibility_version' => 1,
337  '-init' => 1,
338  '-e' => 1,
339  '-seg1addr' => 1,
340  '-bundle_loader' => 1,
341  '-multiply_defined' => 1,
342  '-sectorder' => 3,
343  '--param' => 1,
344  '-u' => 1
345);
346
347my %LangMap = (
348  'c'   => 'c',
349  'cpp' => 'c++',
350  'cc'  => 'c++',
351  'i'   => 'c-cpp-output',
352  'm'   => 'objective-c',
353  'mi'  => 'objective-c-cpp-output'
354);
355
356my %UniqueOptions = (
357  '-isysroot' => 0  
358);
359
360my %LangsAccepted = (
361  "objective-c" => 1,
362  "c" => 1
363);
364
365##----------------------------------------------------------------------------##
366#  Main Logic.
367##----------------------------------------------------------------------------##
368
369my $Action = 'link';
370my @CompileOpts;
371my @LinkOpts;
372my @Files;
373my $Lang;
374my $Output;
375my %Uniqued;
376
377# Forward arguments to gcc.
378my $Status = system($CC,@ARGV);
379if ($Status) { exit($Status >> 8); }
380
381# Get the analysis options.
382my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
383if (!defined($Analyses)) { $Analyses = '-checker-cfref'; }
384
385# Get the store model.
386my $StoreModel = $ENV{'CCC_ANALYZER_STORE_MODEL'};
387if (!defined $StoreModel) { $StoreModel = "region"; }
388
389# Get the constraints engine.
390my $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'};
391if (!defined $ConstraintsModel) { $ConstraintsModel = "range"; }
392
393# Get the output format.
394my $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'};
395if (!defined $OutputFormat) { $OutputFormat = "html"; }
396
397# Determine the level of verbosity.
398my $Verbose = 0;
399if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
400if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
401
402# Determine what clang-cc executable to use.
403my $ClangCC = $ENV{'CLANG_CC'};
404if (!defined $ClangCC) { $ClangCC = 'clang-cc'; }
405
406# Get the HTML output directory.
407my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
408
409my %DisabledArchs = ('ppc' => 1, 'ppc64' => 1);
410my %ArchsSeen;
411my $HadArch = 0;
412
413# Process the arguments.
414foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
415  my $Arg = $ARGV[$i];  
416  my ($ArgKey) = split /=/,$Arg,2;
417
418  # Modes ccc-analyzer supports
419  if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; }
420  elsif ($Arg eq '-c') { $Action = 'compile'; }
421  elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
422
423  # Specially handle duplicate cases of -arch
424  if ($Arg eq "-arch") {
425    my $arch = $ARGV[$i+1];
426    # We don't want to process 'ppc' because of Clang's lack of support
427    # for Altivec (also some #defines won't likely be defined correctly, etc.)
428    if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; }
429    $HadArch = 1;
430    ++$i;
431    next;
432  }
433
434  # Options with possible arguments that should pass through to compiler.
435  if (defined $CompileOptionMap{$ArgKey}) {
436    my $Cnt = $CompileOptionMap{$ArgKey};
437    push @CompileOpts,$Arg;
438    while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
439    next;
440  }
441
442  # Options with possible arguments that should pass through to linker.
443  if (defined $LinkerOptionMap{$ArgKey}) {
444    my $Cnt = $LinkerOptionMap{$ArgKey};
445    push @LinkOpts,$Arg;
446    while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
447    next;
448  }
449
450  # Options with possible arguments that should pass through to both compiler
451  # and the linker.
452  if (defined $CompilerLinkerOptionMap{$ArgKey}) {
453    my $Cnt = $CompilerLinkerOptionMap{$ArgKey};
454    
455    # Check if this is an option that should have a unique value, and if so
456    # determine if the value was checked before.
457    if ($UniqueOptions{$Arg}) {
458      if (defined $Uniqued{$Arg}) {
459        $i += $Cnt;
460        next;
461      }
462      $Uniqued{$Arg} = 1;
463    }
464    
465    push @CompileOpts,$Arg;    
466    push @LinkOpts,$Arg;
467
468    while ($Cnt > 0) {
469      ++$i; --$Cnt;
470      push @CompileOpts, $ARGV[$i];
471      push @LinkOpts, $ARGV[$i];
472    }
473    next;
474  }
475  
476  # Ignored options.
477  if (defined $IgnoredOptionMap{$ArgKey}) {
478    my $Cnt = $IgnoredOptionMap{$ArgKey};
479    while ($Cnt > 0) {
480      ++$i; --$Cnt;
481    }
482    next;
483  }
484  
485  # Compile mode flags.
486  if ($Arg =~ /^-[D,I,U](.*)$/) {
487    my $Tmp = $Arg;    
488    if ($1 eq '') {
489      # FIXME: Check if we are going off the end.
490      ++$i;
491      $Tmp = $Arg . $ARGV[$i];
492    }
493    push @CompileOpts,$Tmp;
494    next;
495  }
496  
497  # Language.
498  if ($Arg eq '-x') {
499    $Lang = $ARGV[$i+1];
500    ++$i; next;
501  }
502
503  # Output file.
504  if ($Arg eq '-o') {
505    ++$i;
506    $Output = $ARGV[$i];
507    next;
508  }
509  
510  # Get the link mode.
511  if ($Arg =~ /^-[l,L,O]/) {
512    if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
513    elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
514    else { push @LinkOpts,$Arg; }
515    next;
516  }
517  
518  if ($Arg =~ /^-std=/) {
519    push @CompileOpts,$Arg;
520    next;
521  }
522  
523#  if ($Arg =~ /^-f/) {
524#    # FIXME: Not sure if the remaining -fxxxx options have no arguments.
525#    push @CompileOpts,$Arg;
526#    push @LinkOpts,$Arg;  # FIXME: Not sure if these are link opts.
527#  }
528  
529  # Get the compiler/link mode.
530  if ($Arg =~ /^-F(.+)$/) {
531    my $Tmp = $Arg;
532    if ($1 eq '') {
533      # FIXME: Check if we are going off the end.
534      ++$i;
535      $Tmp = $Arg . $ARGV[$i];
536    }
537    push @CompileOpts,$Tmp;
538    push @LinkOpts,$Tmp;
539    next;
540  }
541
542  # Input files.
543  if ($Arg eq '-filelist') {
544    # FIXME: Make sure we aren't walking off the end.
545    open(IN, $ARGV[$i+1]);
546    while (<IN>) { s/\015?\012//; push @Files,$_; }
547    close(IN);
548    ++$i;
549    next;
550  }
551  
552  # Handle -Wno-.  We don't care about extra warnings, but
553  # we should suppress ones that we don't want to see.
554  if ($Arg =~ /^-Wno-/) {
555    push @CompileOpts, $Arg;
556    next;
557  }
558
559  if (!($Arg =~ /^-/)) {
560    push @Files, $Arg;
561    next;
562  }
563}
564
565if ($Action eq 'compile' or $Action eq 'link') {
566  my @Archs = keys %ArchsSeen;
567  # Skip the file if we don't support the architectures specified.
568  exit 0 if ($HadArch && scalar(@Archs) == 0);
569  
570  foreach my $file (@Files) {
571    # Determine the language for the file.
572    my $FileLang = $Lang;
573
574    if (!defined($FileLang)) {
575      # Infer the language from the extension.
576      if ($file =~ /[.]([^.]+)$/) {
577        $FileLang = $LangMap{$1};
578      }
579    }
580    
581    next if (!defined $FileLang);
582    next if (!defined $LangsAccepted{$FileLang});
583    
584    my @CmdArgs;
585    my @AnalyzeArgs;    
586    
587    if ($FileLang ne 'unknown') {
588      push @CmdArgs,'-x';
589      push @CmdArgs,$FileLang;
590    }
591
592    if (defined $StoreModel) {
593      push @AnalyzeArgs, "-analyzer-store=$StoreModel";
594    }
595
596    if (defined $ConstraintsModel) {
597      push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel";
598    }
599
600    if (defined $OutputFormat) {
601      push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat;
602      if ($OutputFormat =~ /plist/) {
603        # Change "Output" to be a file.
604        my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => ".plist",
605                               DIR => $HtmlDir);
606        $ResultFile = $f;
607        $CleanupFile = $f;
608      }
609    }
610
611    push @CmdArgs,@CompileOpts;
612    push @CmdArgs,$file;
613
614    if (scalar @Archs) {
615      foreach my $arch (@Archs) {
616        my @NewArgs;
617        push @NewArgs, '-arch';
618        push @NewArgs, $arch;
619        push @NewArgs, @CmdArgs;
620        Analyze($ClangCC, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output,
621                $Verbose, $HtmlDir, $file, $Analyses);
622      }
623    }
624    else {
625      Analyze($ClangCC, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output,
626              $Verbose, $HtmlDir, $file, $Analyses);
627    }
628  }
629}
630
631exit($Status >> 8);
632
633