Reimplement ccc-analyzer in a language I actually know, and implement some obvious optimizations when processing command line arguments.

llvm-svn: 53783
This commit is contained in:
Ted Kremenek 2008-07-19 06:11:04 +00:00
parent fa8511bf08
commit f7ffd66551
1 changed files with 279 additions and 254 deletions

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env perl
#
# The LLVM Compiler Infrastructure
#
@ -7,278 +7,303 @@
#
##===----------------------------------------------------------------------===##
#
# A reduced version of the 'ccc' script that is designed to handle off
# actual compilation to gcc, but run the code passed to gcc through the
# static analyzer.
# A script designed to interpose between the build system and gcc. It invokes
# both gcc and the static analyzer.
#
##===----------------------------------------------------------------------===##
import sys
import subprocess
import os
use strict;
use warnings;
use Cwd;
def error(message):
print >> sys.stderr, 'ccc: ' + message
sys.exit(1)
##----------------------------------------------------------------------------##
# Running the analyzer.
##----------------------------------------------------------------------------##
def quote(arg):
if '"' in arg:
return repr(arg)
return arg
sub Analyze {
my ($Clang, $Args, $Lang, $Output, $Verbose, $HtmlDir, $file, $Analyses) = @_;
def run(args):
code = subprocess.call(args)
if code > 255:
code = 1
if code:
sys.exit(code)
def compile(args):
command = 'gcc'.split()
run(command + args)
def remove_pch_extension(path):
i = path.rfind('.gch')
if i < 0:
return path
return path[:i]
def analyze(clang, args,language,output,files,verbose,htmldir,file,analysis_type):
if language.rfind("c++") >= 0:
return
RunAnalyzer = 0;
if language.find("header") > 0:
target = remove_pch_extension(output)
command = ['cp']
args = command + files + [ target ]
else:
command = clang.split() + analysis_type.split()
args = command + "-DIBOutlet=__attribute__((iboutlet))".split() + args;
RunAnalyzer = 1
print_args = []
# Skip anything related to C++.
return if ($Lang =~ /c[+][+]/);
if verbose:
# We MUST print to stderr. Some clients use the stdout output of
# gcc for various purposes.
print >> sys.stderr, ' '.join(['\n[LOCATION]:', os.getcwd(), '\n' ])
i = 0
while i < len(args):
print_args.append(''.join([ '\'', args[i], '\'' ]))
i += 1
if verbose == 2:
print >> sys.stderr, '#SHELL (cd ' + os.getcwd() + ' && ' + ' '.join(print_args) + ')\n'
if RunAnalyzer and htmldir is not None:
args.append('-o')
print_args.append('-o')
args.append(htmldir)
print_args.append(htmldir)
my $RunAnalyzer = 0;
my $Cmd;
my @CmdArgs;
if verbose == 1:
if ($Lang =~ /header/) {
exit 0 if (!defined ($Output));
$Cmd = 'cp';
push @CmdArgs,$file;
# Remove the PCH extension.
$Output =~ s/[.]gch$//;
push @CmdArgs,$Output;
}
else {
$Cmd = $Clang;
push @CmdArgs,(split /\s/,$Analyses);
push @CmdArgs,'-DIBOutlet=__attribute__((iboutlet))';
push @CmdArgs,@$Args;
$RunAnalyzer = 1;
}
my @PrintArgs;
my $dir;
if ($Verbose) {
$dir = getcwd();
print STDERR "\n[LOCATION]: $dir\n";
push @PrintArgs,"'$Cmd'";
foreach my $arg (@CmdArgs) { push @PrintArgs,"\'$arg\'"; }
}
if ($Verbose == 1) {
# We MUST print to stderr. Some clients use the stdout output of
# gcc for various purposes.
print >> sys.stderr, ' '.join(print_args)
print >> sys.stderr, '\n'
subprocess.call(args)
def extension(path):
return path.split(".")[-1]
def changeextension(path, newext):
i = path.rfind('.')
if i < 0:
return path
j = path.rfind('/', 0, i)
if j < 0:
return path[:i] + "." + newext
return path[j+1:i] + "." + newext
def inferlanguage(extension):
if extension == "c":
return "c"
elif extension in ["cpp", "cc"]:
return "c++"
elif extension == "i":
return "c-cpp-output"
elif extension == "m":
return "objective-c"
elif extension == "mi":
return "objective-c-cpp-output"
elif extension in [ "s", "o", "a", "so" ]:
return "skip"
else:
return "skip" # Skip files with unknown extensions. This
# deviates from ccc, but this works very well for
# the analyzer.
def main(args):
old_args = args
action = 'link'
output = ''
compile_opts = [ ]
link_opts = [ ]
files = []
save_temps = 0
language = ''
print STDERR join(' ',@PrintArgs);
print STDERR "\n";
}
elsif ($Verbose == 2) {
print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n";
}
verbose = 0
clang = "clang"
if ($RunAnalyzer and defined($HtmlDir)) {
push @CmdArgs,'-o';
push @CmdArgs,$HtmlDir;
}
# Forward to GCC.
compile(args)
# Set the analyzer flag.
analysis_type = os.environ.get('CCC_ANALYZER_ANALYSIS')
if analysis_type is None:
analysis_type = "-checker-cfref"
system $Cmd,@CmdArgs;
}
# Determine the level of verbosity.
if os.environ.get('CCC_ANALYZER_VERBOSE') is not None:
verbose = 1
##----------------------------------------------------------------------------##
# Lookup tables.
##----------------------------------------------------------------------------##
my %CompileOptionMap = (
'-nostdinc' => 0,
'-fobjc-gc-only' => 0,
'-fobjc-gc' => 0,
'-include' => 1,
'-idirafter' => 1,
'-iprefix' => 1,
'-iquote' => 1,
'-isystem' => 1,
'-iwithprefix' => 1,
'-iwithprefixbefore' => 1
);
my %LinkerOptionMap = (
'-framework' => 1
);
my %CompilerLinkerOptionMap = (
'-isysroot' => 1,
'-arch' => 1,
'-v' => 0
);
my %IgnoredOptionMap = (
'-fsyntax-only' => 0,
'-save-temps' => 0,
'-install_name' => 1,
'-exported_symbols_list' => 1,
'-current_version' => 1,
'-compatibility_version' => 1,
'-init' => 1,
'-e' => 1,
'-seg1addr' => 1,
'-bundle_loader' => 1,
'-multiply_defined' => 1,
'-sectorder' => 3,
'--param' => 1,
'-u' => 1
);
my %LangMap = (
'c' => 'c',
'cpp' => 'c++',
'cc' => 'c++',
'i' => 'c-cpp-output',
'm' => 'objective-c',
'mi' => 'objective-c-cpp-output'
);
##----------------------------------------------------------------------------##
# Main Logic.
##----------------------------------------------------------------------------##
my $Action = 'link';
my @CompileOpts;
my @LinkOpts;
my @Files;
my $Lang;
my $Output;
# Forward arguments to gcc.
my $Status = system("gcc",@ARGV);
if ($Status) { exit($Status); }
# Get the analysis options.
my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
if (!defined($Analyses)) { $Analyses = '-checker-cfref'; }
# Determine the level of verbosity.
my $Verbose = 0;
if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
# Determine what clang executable to use.
my $Clang = $ENV{'CLANG'};
if (!defined $Clang) { $Clang = 'clang'; }
# Get the HTML output directory.
my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
# Process the arguments.
foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
my $Arg = $ARGV[$i];
# Modes ccc-analyzer supports
if ($Arg eq '-E') { $Action = 'preprocess'; }
elsif ($Arg eq '-c') { $Action = 'compile'; }
elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
if os.environ.get('CCC_ANALYZER_LOG') is not None:
verbose = 2
# Determine what clang executable to use.
clang_env = os.environ.get('CLANG')
# Options with possible arguments that should pass through to compiler.
if (defined $CompileOptionMap{$Arg}) {
my $Cnt = $CompileOptionMap{$Arg};
push @CompileOpts,$Arg;
while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
next;
}
# Options with possible arguments that should pass through to linker.
if (defined $LinkerOptionMap{$Arg}) {
my $Cnt = $LinkerOptionMap{$Arg};
push @LinkOpts,$Arg;
while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
next;
}
# Options with possible arguments that should pass through to both compiler
# and the linker.
if (defined $CompilerLinkerOptionMap{$Arg}) {
my $Cnt = $CompilerLinkerOptionMap{$Arg};
push @CompileOpts,$Arg;
push @LinkOpts,$Arg;
while ($Cnt > 0) {
++$i; --$Cnt;
push @CompileOpts, $ARGV[$i];
push @LinkOpts, $ARGV[$i];
}
next;
}
if clang_env is not None:
clang = clang_env
# Ignored options.
if (defined $IgnoredOptionMap{$Arg}) {
my $Cnt = $IgnoredOptionMap{$Arg};
while ($Cnt > 0) {
++$i; --$Cnt;
}
next;
}
# Get the HTML output directory.
htmldir = os.environ.get('CCC_ANALYZER_HTML')
# Compile mode flags.
if ($Arg =~ /^-[D,I,U](.*)$/) {
my $Tmp = $Arg;
if ($1 eq '') {
# FIXME: Check if we are going off the end.
++$i;
$Tmp = $Arg . $ARGV[$i];
}
push @CompileOpts,$Tmp;
next;
}
# Language.
if ($Arg eq '-x') {
$Lang = $ARGV[$i+1];
++$i; next;
}
# Process the arguments.
i = 0
while i < len(args):
arg = args[i]
# Output file.
if ($Arg eq '-o') {
++$i;
$Output = $ARGV[$i];
next;
}
# Get the link mode.
if ($Arg =~ /^-[l,L,O]/) {
if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
else { push @LinkOpts,$Arg; }
next;
}
if ($Arg =~ /^-std=/) {
push @CompileOpts,$Arg;
next;
}
# if ($Arg =~ /^-f/) {
# # FIXME: Not sure if the remaining -fxxxx options have no arguments.
# push @CompileOpts,$Arg;
# push @LinkOpts,$Arg; # FIXME: Not sure if these are link opts.
# }
# Get the compiler/link mode.
if ($Arg =~ /^-F(.+)$/) {
my $Tmp = $Arg;
if ($1 eq '') {
# FIXME: Check if we are going off the end.
++$i;
$Tmp = $Arg . $ARGV[$i];
}
push @CompileOpts,$Tmp;
push @LinkOpts,$Tmp;
next;
}
# Modes ccc supports
if arg == '-E':
action = 'preprocess'
if arg == '-c':
action = 'compile'
if arg.startswith('-print-prog-name'):
action = 'print-prog-name'
if arg == '-save-temps':
save_temps = 1
# Input files.
if ($Arg eq '-filelist') {
# FIXME: Make sure we aren't walking off the end.
open(IN, $ARGV[$i+1]);
while (<IN>) { s/\015?\012//; push @Files,$_; }
close(IN);
++$i; next;
}
if (!($Arg =~ /^-/)) {
push @Files,$Arg; next;
}
}
# Options with no arguments that should pass through
if arg in ['-v']:
compile_opts.append(arg)
link_opts.append(arg)
if ($Action eq 'compile' or $Action eq 'link') {
foreach my $file (@Files) {
# Determine the language for the file.
my $FileLang = $Lang;
if (!defined($FileLang)) {
# Infer the language from the extension.
if ($file =~ /[.]([^.]+)$/) {
$FileLang = $LangMap{$1};
}
}
# Options with one argument that should be ignored
if arg in ['--param', '-u']:
i += 1
next if (!defined $FileLang);
my @AnalyzeArgs;
if ($FileLang ne 'unknown') {
push @AnalyzeArgs,'-x';
push @AnalyzeArgs,$FileLang;
}
# Prefix matches for the compile mode
if arg[:2] in ['-D', '-I', '-U', '-F' ]:
if not arg[2:]:
arg += args[i+1]
i += 1
compile_opts.append(arg)
push @AnalyzeArgs,@CompileOpts;
push @AnalyzeArgs,$file;
Analyze($Clang, \@AnalyzeArgs, $FileLang, $Output,
$Verbose, $HtmlDir, $file, $Analyses);
}
}
if arg[:5] in ['-std=']:
compile_opts.append(arg)
# Options with one argument that should pass through to compiler
if arg in [ '-include', '-idirafter', '-iprefix',
'-iquote', '-isystem', '-iwithprefix',
'-iwithprefixbefore']:
compile_opts.append(arg)
compile_opts.append(args[i+1])
i += 1
# Options with no argument that should pass through to compiler
if arg in [ '-nostdinc', '-fobjc-gc-only', '-fobjc-gc' ]:
compile_opts.append(arg)
# Options with one argument that should pass through to linker
if arg == '-framework':
link_opts.append(arg)
link_opts.append(args[i+1])
i += 1
# Options with one argument that should pass through to both
if arg in ['-isysroot', '-arch']:
compile_opts.append(arg)
compile_opts.append(args[i+1])
link_opts.append(arg)
link_opts.append(args[i+1])
i += 1
# Prefix matches for the link mode
if arg[:2] in ['-l', '-L', '-O', '-F']:
if arg == '-O': arg = '-O1'
if arg == '-Os': arg = '-O2'
link_opts.append(arg)
# Input files
if arg == '-filelist':
f = open(args[i+1])
for line in f:
files.append(line.strip())
f.close()
i += 1
if arg == '-x':
language = args[i+1]
i += 1
if arg[0] != '-':
files.append(arg)
# Output file
if arg == '-o':
output = args[i+1]
i += 1
# Arguments we currently ignore with one option.
if arg in ['-install_name', '-exported_symbols_list',
'-current_version', '-compatibility_version', '-init', '-e',
'-seg1addr', '-bundle_loader', '-multiply_defined']:
i += 1
# Arguments we currently ignore with three options.
if arg in ['-sectorder']:
i += 3
i += 1
if action == 'print-prog-name':
# assume we can handle everything
print sys.argv[0]
return
if not files:
error('no input files')
if action == 'compile' or save_temps or action == 'link':
for i, file in enumerate(files):
file_language = language
if not language:
file_language = inferlanguage(extension(file))
if file_language == "skip":
continue
if save_temps and action != "compile":
# Need a temporary output file
coutput = changeextension(file, "o");
files[i] = coutput
elif not output:
coutput = changeextension(file, "o")
else:
coutput = output
analyze_args = [ file ]
if file_language != 'unknown':
analyze_args = [ '-x', file_language ] + analyze_args
analyze_args = analyze_args + compile_opts
analyze(clang, analyze_args, language, output, files, verbose, htmldir, file, analysis_type)
if __name__ == '__main__':
main(sys.argv[1:])