diff --git a/infer/lib/python/infer b/infer/lib/python/infer index 216e7a832..380e1e324 100755 --- a/infer/lib/python/infer +++ b/infer/lib/python/infer @@ -160,7 +160,7 @@ def main(): if not os.path.exists(os.path.join(args.infer_out, 'captured')): print('There was nothing to analyze, exiting') exit(os.EX_USAGE) - analysis = analyze.Infer(args) + analysis = analyze.AnalyzerWrapper(args) analysis.analyze_and_report() analysis.save_stats() diff --git a/infer/lib/python/inferlib/analyze.py b/infer/lib/python/inferlib/analyze.py index 79f74a40f..76715fd94 100644 --- a/infer/lib/python/inferlib/analyze.py +++ b/infer/lib/python/inferlib/analyze.py @@ -23,7 +23,7 @@ import subprocess import sys import time -from . import config, issues, jwlib, utils +from . import config, issues, utils # Increase the limit of the CSV parser to sys.maxlimit csv.field_size_limit(sys.maxsize) @@ -220,37 +220,26 @@ def run_command(cmd, debug_mode, javac_arguments, step, analyzer): raise e -class Infer: +class AnalyzerWrapper(object): - def __init__(self, args, javac_cmd=None, javac_args=[]): + javac = None + + def __init__(self, args): self.args = args if self.args.analyzer not in config.ANALYZERS: help_exit('Unknown analysis mode \"{0}\"' .format(self.args.analyzer)) - self.javac = jwlib.CompilerCall(javac_cmd, javac_args) - - if not self.javac.args.version: - if javac_args is None: - help_exit('No javac command detected') - - if self.args.infer_out is None: - help_exit('Expect Infer results directory') - - if self.args.buck: - self.args.infer_out = os.path.join( - self.javac.args.classes_out, - config.BUCK_INFER_OUT) - self.args.infer_out = os.path.abspath(self.args.infer_out) + if self.args.infer_out is None: + help_exit('Expect Infer results directory') + try: + os.mkdir(self.args.infer_out) + except OSError as e: + if not os.path.isdir(self.args.infer_out): + raise e - try: - os.mkdir(self.args.infer_out) - except OSError as e: - if not os.path.isdir(self.args.infer_out): - raise e - - self.stats = {'int': {}} - self.timing = {} + self.stats = {'int': {}} + self.timing = {} if self.args.specs_dirs: # Each dir passed in input is prepended by '-lib'. @@ -270,79 +259,12 @@ class Infer: ['-specs-dir-list-file', os.path.abspath(self.args.specs_dir_list_file)] - def clean_exit(self): if os.path.isdir(self.args.infer_out): utils.stdout('removing {}'.format(self.args.infer_out)) shutil.rmtree(self.args.infer_out) exit(os.EX_OK) - # create a classpath to pass to the frontend - def create_frontend_classpath(self): - classes_out = '.' - if self.javac.args.classes_out is not None: - classes_out = self.javac.args.classes_out - classes_out = os.path.abspath(classes_out) - original_classpath = [] - if self.javac.args.bootclasspath is not None: - original_classpath = self.javac.args.bootclasspath.split(':') - if self.javac.args.classpath is not None: - original_classpath += self.javac.args.classpath.split(':') - if len(original_classpath) > 0: - # add classes_out, unless it's already in the classpath - classpath = [os.path.abspath(p) for p in original_classpath] - if not classes_out in classpath: - classpath = [classes_out] + classpath - # remove models.jar; it's added by the frontend - models_jar = os.path.abspath(config.MODELS_JAR) - if models_jar in classpath: - classpath.remove(models_jar) - return ':'.join(classpath) - return classes_out - - - def run_infer_frontend(self): - - infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')] - infer_cmd += ['-classpath', self.create_frontend_classpath()] - infer_cmd += ['-class_source_map', self.javac.class_source_map] - - if not self.args.absolute_paths: - infer_cmd += ['-project_root', self.args.project_root] - - infer_cmd += [ - '-results_dir', self.args.infer_out, - '-verbose_out', self.javac.verbose_out, - ] - - if os.path.isfile(config.MODELS_JAR): - infer_cmd += ['-models', config.MODELS_JAR] - - infer_cmd.append('-no-static_final') - - if self.args.debug: - infer_cmd.append('-debug') - if self.args.analyzer == config.ANALYZER_TRACING: - infer_cmd.append('-tracing') - if self.args.android_harness: - infer_cmd.append('-harness') - - if (self.args.android_harness - or self.args.analyzer in [config.ANALYZER_CHECKERS, - config.ANALYZER_ERADICATE]): - os.environ['INFER_CREATE_CALLEE_PDESC'] = 'Y' - - return run_command( - infer_cmd, - self.args.debug, - self.javac.original_arguments, - 'frontend', - self.args.analyzer - ) - - def compile(self): - return self.javac.run() - def analyze(self): logging.info('Starting analysis') infer_analyze = [ @@ -408,7 +330,7 @@ class Infer: exit_status = os.EX_OK - if self.args.buck: + if self.javac is not None and self.args.buck: infer_options += ['-project_root', os.getcwd(), '-java'] if self.javac.args.classpath is not None: for path in self.javac.args.classpath.split(os.pathsep): @@ -423,13 +345,16 @@ class Infer: os.environ['INFER_OPTIONS'] = ' '.join(infer_options) + javac_original_arguments = \ + self.javac.original_arguments if self.javac is not None else [] + if self.args.multicore == 1: analysis_start_time = time.time() analyze_cmd = infer_analyze + infer_options exit_status = run_command( analyze_cmd, self.args.debug, - self.javac.original_arguments, + javac_original_arguments, 'analysis', self.args.analyzer ) @@ -450,7 +375,7 @@ class Infer: makefile_status = run_command( analyze_cmd, self.args.debug, - self.javac.original_arguments, + javac_original_arguments, 'create_makefile', self.args.analyzer ) @@ -465,7 +390,7 @@ class Infer: make_status = run_command( make_cmd, self.args.debug, - self.javac.original_arguments, + javac_original_arguments, 'run_makefile', self.args.analyzer ) @@ -506,7 +431,7 @@ class Infer: '-procs', procs_report, '-analyzer', self.args.analyzer ] - if self.javac.annotations_out is not None: + if self.javac is not None and self.javac.annotations_out is not None: infer_print_options += [ '-local_config', self.javac.annotations_out] if self.args.debug or self.args.debug_exceptions: @@ -551,11 +476,6 @@ class Infer: stats_path = os.path.join(self.args.infer_out, config.STATS_FILENAME) utils.dump_json_to_path(self.stats, stats_path) - - def close(self): - os.remove(self.javac.verbose_out) - os.remove(self.javac.annotations_out) - def analyze_and_report(self): should_print_errors = False if self.args.analyzer not in [config.ANALYZER_COMPILE, @@ -578,29 +498,3 @@ class Infer: files_total = self.stats['int']['files'] files_str = utils.get_plural('file', files_total) print('Analyzed {}'.format(files_str)) - - def start(self): - if self.javac.args.version: - if self.args.buck: - key = self.args.analyzer - utils.stderr(utils.infer_key(key), errors="strict") - else: - return self.javac.run() - else: - start_time = time.time() - - self.compile() - if self.args.analyzer == config.ANALYZER_COMPILE: - return os.EX_OK - - self.run_infer_frontend() - self.timing['capture'] = utils.elapsed_time(start_time) - if self.args.analyzer == config.ANALYZER_CAPTURE: - return os.EX_OK - - self.analyze_and_report() - self.close() - self.timing['total'] = utils.elapsed_time(start_time) - self.save_stats() - - return self.stats diff --git a/infer/lib/python/inferlib/capture/javac.py b/infer/lib/python/inferlib/capture/javac.py index f9b19ac70..fe5b96545 100644 --- a/infer/lib/python/inferlib/capture/javac.py +++ b/infer/lib/python/inferlib/capture/javac.py @@ -10,7 +10,7 @@ import subprocess import traceback import util -from inferlib import analyze +from inferlib import analyze, jwlib MODULE_NAME = __name__ MODULE_DESCRIPTION = '''Run analysis of code built with a command like: @@ -31,7 +31,11 @@ create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) class JavacCapture: def __init__(self, args, cmd): - self.analysis = analyze.Infer(args, cmd[0], cmd[1:]) + self.analysis = jwlib.AnalyzerWithFrontendWrapper( + args, + cmd[0], + cmd[1:], + ) def capture(self): try: diff --git a/infer/lib/python/inferlib/capture/util.py b/infer/lib/python/inferlib/capture/util.py index baf4fa5ad..e57806ade 100644 --- a/infer/lib/python/inferlib/capture/util.py +++ b/infer/lib/python/inferlib/capture/util.py @@ -18,7 +18,7 @@ import logging import subprocess import traceback -from inferlib import analyze, utils +from inferlib import analyze, jwlib, utils def get_build_output(build_cmd): # TODO make it return generator to be able to handle large builds diff --git a/infer/lib/python/inferlib/jwlib.py b/infer/lib/python/inferlib/jwlib.py index 7d967d6ca..07b1b0ec9 100644 --- a/infer/lib/python/inferlib/jwlib.py +++ b/infer/lib/python/inferlib/jwlib.py @@ -6,14 +6,18 @@ # LICENSE file in the root directory of this source tree. An additional grant # of patent rights can be found in the PATENTS file in the same directory. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + import argparse import os -import tempfile import subprocess +import tempfile +import time -import analyze -import config -import utils +from . import analyze, config, utils # javac options parser = argparse.ArgumentParser() @@ -52,9 +56,11 @@ def create_infer_command(args, javac_arguments): infer_args.append('--debug') infer_args += ['--analyzer', 'capture'] - return analyze.Infer(analyze.infer_parser.parse_args(infer_args), - 'javac', - _get_javac_args(['javac'] + javac_arguments)) + return AnalyzerWithFrontendWrapper( + analyze.infer_parser.parse_args(infer_args), + 'javac', + _get_javac_args(['javac'] + javac_arguments) + ) class AnnotationProcessorNotFound(Exception): @@ -66,9 +72,10 @@ class AnnotationProcessorNotFound(Exception): return repr(self.path + ' not found') -class CompilerCall: +class CompilerCall(object): def __init__(self, javac_cmd, arguments): + assert javac_cmd is not None and arguments is not None self.javac_cmd = javac_cmd self.original_arguments = arguments self.args, self.remaining_args = parser.parse_known_args(arguments) @@ -156,3 +163,114 @@ class CompilerCall: subprocess.check_call(failing_cmd) return os.EX_OK + + +class AnalyzerWithFrontendWrapper(analyze.AnalyzerWrapper): + + def __init__(self, args, javac_cmd, javac_args): + self.javac = CompilerCall(javac_cmd, javac_args) + if not self.javac.args.version: + if javac_args is None: + help_exit('No javac command detected') + + analyze.AnalyzerWrapper.__init__(self, args) + + if self.args.buck: + self.args.infer_out = os.path.join( + self.javac.args.classes_out, + config.BUCK_INFER_OUT) + self.args.infer_out = os.path.abspath(self.args.infer_out) + + def start(self): + if self.javac.args.version: + if self.args.buck: + key = self.args.analyzer + print(utils.infer_key(key), file=sys.stderr) + else: + return self.javac.run() + else: + start_time = time.time() + + self._compile() + if self.args.analyzer == config.ANALYZER_COMPILE: + return os.EX_OK + + self._run_infer_frontend() + self.timing['capture'] = utils.elapsed_time(start_time) + if self.args.analyzer == config.ANALYZER_CAPTURE: + return os.EX_OK + + self.analyze_and_report() + self._close() + self.timing['total'] = utils.elapsed_time(start_time) + self.save_stats() + + return self.stats + + # create a classpath to pass to the frontend + def _create_frontend_classpath(self): + classes_out = '.' + if self.javac.args.classes_out is not None: + classes_out = self.javac.args.classes_out + classes_out = os.path.abspath(classes_out) + original_classpath = [] + if self.javac.args.bootclasspath is not None: + original_classpath = self.javac.args.bootclasspath.split(':') + if self.javac.args.classpath is not None: + original_classpath += self.javac.args.classpath.split(':') + if len(original_classpath) > 0: + # add classes_out, unless it's already in the classpath + classpath = [os.path.abspath(p) for p in original_classpath] + if classes_out not in classpath: + classpath = [classes_out] + classpath + # remove models.jar; it's added by the frontend + models_jar = os.path.abspath(config.MODELS_JAR) + if models_jar in classpath: + classpath.remove(models_jar) + return ':'.join(classpath) + return classes_out + + def _run_infer_frontend(self): + infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')] + infer_cmd += ['-classpath', self._create_frontend_classpath()] + infer_cmd += ['-class_source_map', self.javac.class_source_map] + + if not self.args.absolute_paths: + infer_cmd += ['-project_root', self.args.project_root] + + infer_cmd += [ + '-results_dir', self.args.infer_out, + '-verbose_out', self.javac.verbose_out, + ] + + if os.path.isfile(config.MODELS_JAR): + infer_cmd += ['-models', config.MODELS_JAR] + + infer_cmd.append('-no-static_final') + + if self.args.debug: + infer_cmd.append('-debug') + if self.args.analyzer == config.ANALYZER_TRACING: + infer_cmd.append('-tracing') + if self.args.android_harness: + infer_cmd.append('-harness') + + if (self.args.android_harness or + self.args.analyzer in [config.ANALYZER_CHECKERS, + config.ANALYZER_ERADICATE]): + os.environ['INFER_CREATE_CALLEE_PDESC'] = 'Y' + + return analyze.run_command( + infer_cmd, + self.args.debug, + self.javac.original_arguments, + 'frontend', + self.args.analyzer + ) + + def _compile(self): + return self.javac.run() + + def _close(self): + os.remove(self.javac.verbose_out) + os.remove(self.javac.annotations_out)