| #!/usr/bin/env python |
| # |
| # The LLVM Compiler Infrastructure |
| # |
| # This file is distributed under the University of Illinois Open Source |
| # License. See LICENSE.TXT for details. |
| # |
| ##===----------------------------------------------------------------------===## |
| # |
| # 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. |
| # |
| ##===----------------------------------------------------------------------===## |
| |
| import sys |
| import subprocess |
| import os |
| |
| def error(message): |
| print >> sys.stderr, 'ccc: ' + message |
| sys.exit(1) |
| |
| def quote(arg): |
| if '"' in arg: |
| return repr(arg) |
| return arg |
| |
| 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 + args; |
| RunAnalyzer = 1 |
| |
| print_args = [] |
| |
| 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) |
| |
| 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 = '' |
| |
| verbose = 0 |
| clang = "clang" |
| |
| # Forward to GCC. |
| compile(args) |
| |
| # Set the analyzer flag. |
| analysis_type = os.environ.get('CCC_ANALYZER_ANALYSIS') |
| |
| if analysis_type is not None: |
| analysis_type = "-" + analysis_type |
| else: |
| analysis_type = "-warn-dead-stores -checker-cfref -warn-objc-methodsigs -warn-objc-missing-dealloc" |
| |
| # Determine the level of verbosity. |
| if os.environ.get('CCC_ANALYZER_VERBOSE') is not None: |
| verbose = 1 |
| |
| if os.environ.get('CCC_ANALYZER_LOG') is not None: |
| verbose = 2 |
| |
| # Determine what clang executable to use. |
| clang_env = os.environ.get('CLANG') |
| |
| if clang_env is not None: |
| clang = clang_env |
| |
| # Get the HTML output directory. |
| htmldir = os.environ.get('CCC_ANALYZER_HTML') |
| |
| # Process the arguments. |
| i = 0 |
| while i < len(args): |
| arg = args[i] |
| |
| # 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 |
| |
| # Options with no arguments that should pass through |
| if arg in ['-v']: |
| compile_opts.append(arg) |
| link_opts.append(arg) |
| |
| # Options with one argument that should be ignored |
| if arg in ['--param', '-u']: |
| i += 1 |
| |
| # 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) |
| |
| 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:]) |