You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							224 lines
						
					
					
						
							9.1 KiB
						
					
					
				
			
		
		
	
	
							224 lines
						
					
					
						
							9.1 KiB
						
					
					
				# Copyright (c) 2015 - present Facebook, Inc.
 | 
						|
# All rights reserved.
 | 
						|
#
 | 
						|
# This source code is licensed under the BSD style license found in the
 | 
						|
# 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 logging
 | 
						|
import os
 | 
						|
import subprocess
 | 
						|
import traceback
 | 
						|
 | 
						|
from inferlib import config, issues, utils, bucklib
 | 
						|
from . import util
 | 
						|
 | 
						|
MODULE_NAME = __name__
 | 
						|
MODULE_DESCRIPTION = '''Run analysis of code built with a command like:
 | 
						|
buck [options] [target]
 | 
						|
 | 
						|
Analysis examples:
 | 
						|
infer -- buck build HelloWorld'''
 | 
						|
LANG = ['clang', 'java']
 | 
						|
 | 
						|
 | 
						|
def gen_instance(*args):
 | 
						|
    return BuckAnalyzer(*args)
 | 
						|
 | 
						|
 | 
						|
def string_in_quotes(value):
 | 
						|
    return value.strip('\'')
 | 
						|
 | 
						|
 | 
						|
# This creates an empty argparser for the module, which provides only
 | 
						|
# description/usage information and no arguments.
 | 
						|
def create_argparser(group_name=MODULE_NAME):
 | 
						|
    """This defines the set of arguments that get added by this module to the
 | 
						|
    set of global args defined in the infer top-level module
 | 
						|
    Do not use this function directly, it should be invoked by the infer
 | 
						|
    top-level module"""
 | 
						|
    parser = argparse.ArgumentParser(add_help=False)
 | 
						|
    group = parser.add_argument_group(
 | 
						|
        '{grp} module'.format(grp=MODULE_NAME),
 | 
						|
        description=MODULE_DESCRIPTION,
 | 
						|
    )
 | 
						|
    group.add_argument('--print-harness', action='store_true',
 | 
						|
                       help='Print generated harness code (Android only)')
 | 
						|
    group.add_argument('--use-flavors', action='store_true',
 | 
						|
                       help='Run Infer analysis through the use of flavors. '
 | 
						|
                            'Currently this is supported only for the cxx_* '
 | 
						|
                            'targets of Buck - e.g. cxx_library, cxx_binary - '
 | 
						|
                            'and not for Java. Note: this flag should be used '
 | 
						|
                            'in combination with passing the #infer flavor '
 | 
						|
                            'to the Buck target.')
 | 
						|
    group.add_argument('--use-compilation-database', action='store_true',
 | 
						|
                       help='Run Infer analysis through the use of the flavor '
 | 
						|
                            'compilation database. Currently this is '
 | 
						|
                            'supported only for the cxx_* targets of Buck '
 | 
						|
                            '- e.g. cxx_library, cxx_binary - and not for '
 | 
						|
                            'Java.')
 | 
						|
    group.add_argument('--xcode-developer-dir',
 | 
						|
                       help='Specify the path to Xcode developer directory '
 | 
						|
                            '(requires --use-flavors to work)')
 | 
						|
    group.add_argument('--blacklist-regex',
 | 
						|
                       help='Specify the regex for files to skip during '
 | 
						|
                            'the analysis (requires --use-flavors to work)')
 | 
						|
    group.add_argument('--merge-deps-files', action='store_true',
 | 
						|
                       help='Merge the infer-deps.txt files generated by Buck '
 | 
						|
                            'during the analysis (requires --use-flavors to '
 | 
						|
                            'work)')
 | 
						|
    group.add_argument('--Xbuck', action='append', default=[],
 | 
						|
                       type=string_in_quotes,
 | 
						|
                       help='Pass values as command-line arguments to '
 | 
						|
                            'invocations of `buck build`.'
 | 
						|
                            'NOTE: value should be wrapped in single quotes')
 | 
						|
 | 
						|
    return parser
 | 
						|
 | 
						|
 | 
						|
class BuckAnalyzer:
 | 
						|
    def __init__(self, args, cmd):
 | 
						|
        self.args = args
 | 
						|
        self.cmd = cmd
 | 
						|
        util.log_java_version()
 | 
						|
        logging.info(util.run_cmd_ignore_fail(['buck', '--version']))
 | 
						|
 | 
						|
    def capture(self):
 | 
						|
        try:
 | 
						|
            if self.args.use_flavors and \
 | 
						|
                    not self.args.use_compilation_database:
 | 
						|
                return self.capture_with_flavors()
 | 
						|
            elif self.args.use_compilation_database:
 | 
						|
                return self.capture_with_compilation_database()
 | 
						|
            else:
 | 
						|
                return self.capture_without_flavors()
 | 
						|
        except subprocess.CalledProcessError as exc:
 | 
						|
            if self.args.debug:
 | 
						|
                traceback.print_exc()
 | 
						|
            return exc.returncode
 | 
						|
 | 
						|
    def create_cxx_buck_configuration_args(self):
 | 
						|
        # return a string that can be passed in input to buck
 | 
						|
        # and configures the paths to infer/clang/plugin/xcode
 | 
						|
        facebook_clang_plugins_root = config.FCP_DIRECTORY
 | 
						|
        clang_path = os.path.join(
 | 
						|
            facebook_clang_plugins_root,
 | 
						|
            'clang',
 | 
						|
            'install',
 | 
						|
            'bin',
 | 
						|
            'clang',
 | 
						|
        )
 | 
						|
        plugin_path = os.path.join(
 | 
						|
            facebook_clang_plugins_root,
 | 
						|
            'libtooling',
 | 
						|
            'build',
 | 
						|
            'FacebookClangPlugin.dylib',
 | 
						|
        )
 | 
						|
        args = [
 | 
						|
            '--config',
 | 
						|
            '*//infer.infer_bin={bin}'
 | 
						|
            .format(bin=config.BIN_DIRECTORY),
 | 
						|
            '--config',
 | 
						|
            '*//infer.clang_compiler={clang}'.format(clang=clang_path),
 | 
						|
            '--config',
 | 
						|
            '*//infer.clang_plugin={plugin}'.format(plugin=plugin_path),
 | 
						|
            '--config',
 | 
						|
            '*//cxx.pch_enabled=false',
 | 
						|
        ] + self.args.Xbuck
 | 
						|
 | 
						|
        if self.args.xcode_developer_dir is not None:
 | 
						|
            args.append('--config')
 | 
						|
            args.append('apple.xcode_developer_dir={devdir}'.format(
 | 
						|
                devdir=self.args.xcode_developer_dir))
 | 
						|
        if self.args.blacklist_regex:
 | 
						|
            args.append('--config')
 | 
						|
            args.append('*//infer.blacklist_regex={regex}'.format(
 | 
						|
                regex=self.args.blacklist_regex))
 | 
						|
        return args
 | 
						|
 | 
						|
    def _get_analysis_result_paths(self):
 | 
						|
        # TODO(8610738): Make targets extraction smarter
 | 
						|
        buck_results_cmd = [
 | 
						|
            self.cmd[0],
 | 
						|
            'targets',
 | 
						|
            '--show-output'
 | 
						|
        ] + self.cmd[2:] + self.create_cxx_buck_configuration_args()
 | 
						|
        proc = subprocess.Popen(buck_results_cmd, stdout=subprocess.PIPE)
 | 
						|
        (buck_output, _) = proc.communicate()
 | 
						|
        # remove target name prefixes from each line and split them into a list
 | 
						|
        out = [x.split(None, 1)[1] for x in buck_output.strip().split('\n')]
 | 
						|
        return [os.path.dirname(x)
 | 
						|
                if os.path.isfile(x) else x
 | 
						|
                for x in out if os.path.exists(x)]
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _merge_infer_dep_files(root_paths, merged_out_path):
 | 
						|
        potential_dep_files = [os.path.join(p, config.INFER_BUCK_DEPS_FILENAME)
 | 
						|
                               for p in root_paths]
 | 
						|
        dep_files = filter(os.path.exists, potential_dep_files)
 | 
						|
        utils.merge_and_dedup_files_into_path(dep_files, merged_out_path)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _merge_infer_report_files(root_paths, merged_out_path):
 | 
						|
        potential_report_files = [os.path.join(p, config.JSON_REPORT_FILENAME)
 | 
						|
                                  for p in root_paths]
 | 
						|
        report_files = filter(os.path.exists, potential_report_files)
 | 
						|
        all_results = issues.merge_reports_from_paths(report_files)
 | 
						|
        utils.dump_json_to_path(all_results, merged_out_path)
 | 
						|
 | 
						|
    def _run_buck_with_flavors(self):
 | 
						|
        # TODO: Use buck to identify the project's root folder
 | 
						|
        if not os.path.isfile('.buckconfig'):
 | 
						|
            print('Please run this command from the folder where .buckconfig '
 | 
						|
                  'is located')
 | 
						|
            return os.EX_USAGE
 | 
						|
        env_vars = utils.read_env()
 | 
						|
        env_vars['FCP_RUN_SYNTAX_ONLY'] = '1'
 | 
						|
        env = utils.encode_env(env_vars)
 | 
						|
        command = self.cmd
 | 
						|
        command += ['-j', str(self.args.multicore)]
 | 
						|
        if self.args.load_average is not None:
 | 
						|
            command += ['-L', str(self.args.load_average)]
 | 
						|
        command += self.create_cxx_buck_configuration_args()
 | 
						|
        subprocess.check_call(command, env=env)
 | 
						|
        return os.EX_OK
 | 
						|
 | 
						|
    def capture_with_flavors(self):
 | 
						|
        ret = self._run_buck_with_flavors()
 | 
						|
        if not ret == os.EX_OK:
 | 
						|
            return ret
 | 
						|
        result_paths = self._get_analysis_result_paths()
 | 
						|
        merged_reports_path = os.path.join(
 | 
						|
            self.args.infer_out, config.JSON_REPORT_FILENAME)
 | 
						|
        merged_deps_path = os.path.join(
 | 
						|
            self.args.infer_out, config.INFER_BUCK_DEPS_FILENAME)
 | 
						|
        self._merge_infer_report_files(result_paths, merged_reports_path)
 | 
						|
        self._merge_infer_dep_files(result_paths, merged_deps_path)
 | 
						|
        bugs_out = os.path.join(self.args.infer_out, config.BUGS_FILENAME)
 | 
						|
        xml_out = None
 | 
						|
        if self.args.pmd_xml:
 | 
						|
            xml_out = os.path.join(
 | 
						|
                self.args.infer_out, config.PMD_XML_FILENAME)
 | 
						|
        issues.print_and_save_errors(merged_reports_path, bugs_out, xml_out)
 | 
						|
        return os.EX_OK
 | 
						|
 | 
						|
    def capture_with_compilation_database(self):
 | 
						|
        buck_args = self.cmd
 | 
						|
        cmd = [utils.get_cmd_in_bin_dir('InferBuckCompilationDatabase')]
 | 
						|
        for arg in buck_args:
 | 
						|
            cmd += ['--Xbuck'] + [arg]
 | 
						|
        if self.args.project_root:
 | 
						|
            cmd += ['--project-root'] + [self.args.project_root]
 | 
						|
        return subprocess.check_call(cmd)
 | 
						|
 | 
						|
    def capture_without_flavors(self):
 | 
						|
        # Java is a special case, and we run the analysis from here
 | 
						|
        buck_wrapper = bucklib.Wrapper(self.args, self.cmd)
 | 
						|
        return buck_wrapper.run()
 |