diff --git a/Makefile.config b/Makefile.config index fb62a4005..309371186 100644 --- a/Makefile.config +++ b/Makefile.config @@ -87,7 +87,7 @@ OBJC_MODELS_FILE = $(SPECS_LIB_DIR)/objc_models CLANG_DEPS_NO_MODELS = \ $(addprefix $(PYTHON_LIB_DIR)/, \ analyze.py config.py issues.py source.py utils.py) \ - $(addprefix $(CAPTURE_LIB_DIR)/, make.py util.py) \ + $(addprefix $(CAPTURE_LIB_DIR)/, util.py) \ $(INFER_BIN) $(INFERANALYZE_BIN) $(INFERCLANG_BIN) $(INFERPRINT_BIN) CLANG_DEPS = $(CLANG_DEPS_NO_MODELS) $(C_MODELS_FILE) $(CPP_MODELS_FILE) \ diff --git a/infer/lib/python/infer.py b/infer/lib/python/infer.py index 175154b90..b318d7731 100755 --- a/infer/lib/python/infer.py +++ b/infer/lib/python/infer.py @@ -22,7 +22,6 @@ import sys import inferlib from inferlib import analyze, config, utils -from inferlib.capture import make CAPTURE_PACKAGE = 'capture' @@ -37,7 +36,6 @@ MODULE_TO_COMMAND = { 'ant': ['ant'], 'buck': ['buck'], 'gradle': ['gradle', 'gradlew'], - 'make': make.SUPPORTED_COMMANDS, 'xcodebuild': ['xcodebuild'], 'ndk-build': ['ndk-build'], } diff --git a/infer/lib/python/inferlib/capture/make.py b/infer/lib/python/inferlib/capture/make.py deleted file mode 100644 index 7ba43e15b..000000000 --- a/infer/lib/python/inferlib/capture/make.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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. - -import logging -import os -import subprocess -import traceback - -import util -from inferlib import config, utils - -MODULE_NAME = 'make/cc/clang/gcc' -MODULE_DESCRIPTION = '''Run analysis of code built with commands like: -make [target] -clang [compiler_options] -gcc [compiler_options] -cc [compiler_options] - -Analysis examples: -infer -- make all -infer -- clang -c srcfile.m -infer -- gcc -c srcfile.c''' -LANG = ['clang'] - -ALIASED_COMMANDS = ['clang', 'clang++', 'cc', 'gcc', 'g++'] -BUILD_COMMANDS = ['cmake', 'configure', 'make', 'waf'] -SUPPORTED_COMMANDS = ALIASED_COMMANDS + BUILD_COMMANDS - - -def gen_instance(*args): - return MakeCapture(*args) - - -# This creates an empty argparser for the module, which provides only -# description/usage information and no arguments. -create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) - - -class MakeCapture: - def __init__(self, args, cmd): - self.args = args - self.cmd = cmd - command_name = os.path.basename(cmd[0]) - if command_name in ALIASED_COMMANDS: - # remove absolute paths for these commands as we want to - # substitue our own wrappers instead using a PATH trick - cmd[0] = command_name - - def get_envvars(self): - env_vars = utils.read_env() - wrappers_path = config.WRAPPERS_DIRECTORY - # INFER_RESULTS_DIR and INFER_OLD_PATH are used by javac wrapper only - env_vars['INFER_OLD_PATH'] = env_vars['PATH'] - env_vars['PATH'] = '{wrappers}{sep}{path}'.format( - wrappers=wrappers_path, - sep=os.pathsep, - path=env_vars['PATH'], - ) - env_vars['INFER_RESULTS_DIR'] = self.args.infer_out - return env_vars - - def capture(self): - try: - env = utils.encode_env(self.get_envvars()) - cmd = map(utils.encode, self.cmd) - logging.info('Running command %s with env:\n%s' % (cmd, env)) - subprocess.check_call(cmd, env=env) - capture_dir = os.path.join(self.args.infer_out, 'captured') - if len(os.listdir(capture_dir)) < 1: - # Don't return with a failure code unless we're - # running make. It could be normal to have captured - # nothing (eg, empty source file). Further output will - # alert the user that there was nothing to analyze. - if self.cmd[0] == 'make': - # reuse code from gradle, etc. integration - return util.run_compilation_commands([], 'make clean') - return os.EX_OK - except subprocess.CalledProcessError as exc: - if self.args.debug: - traceback.print_exc() - return exc.returncode diff --git a/infer/lib/python/inferlib/capture/ndk-build.py b/infer/lib/python/inferlib/capture/ndk-build.py index 50063bcd0..00054910d 100644 --- a/infer/lib/python/inferlib/capture/ndk-build.py +++ b/infer/lib/python/inferlib/capture/ndk-build.py @@ -5,8 +5,13 @@ # 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. -import make +import logging +import os +import subprocess +import traceback + import util +from inferlib import config, utils MODULE_NAME = 'ndk-build/clang' MODULE_DESCRIPTION = '''Run analysis of code built with ndk-build @@ -15,6 +20,7 @@ MODULE_DESCRIPTION = '''Run analysis of code built with ndk-build infer -- ndk-build''' LANG = ['clang'] +ALIASED_COMMANDS = ['clang', 'clang++', 'cc', 'gcc', 'g++'] def gen_instance(*args): return NdkBuildCapture(*args) @@ -23,8 +29,48 @@ def gen_instance(*args): create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) -class NdkBuildCapture(make.MakeCapture): +class NdkBuildCapture(): def __init__(self, args, cmd): cmd = [cmd[0], 'NDK_TOOLCHAIN_VERSION=clang', 'TARGET_CC=clang', 'TARGET_CXX=clang++', 'TARGET_LD=ld'] + cmd[1:] - make.MakeCapture.__init__(self, args, cmd) + self.args = args + self.cmd = cmd + command_name = os.path.basename(cmd[0]) + if command_name in ALIASED_COMMANDS: + # remove absolute paths for these commands as we want to + # substitue our own wrappers instead using a PATH trick + cmd[0] = command_name + + def get_envvars(self): + env_vars = utils.read_env() + wrappers_path = config.WRAPPERS_DIRECTORY + # INFER_RESULTS_DIR and INFER_OLD_PATH are used by javac wrapper only + env_vars['INFER_OLD_PATH'] = env_vars['PATH'] + env_vars['PATH'] = '{wrappers}{sep}{path}'.format( + wrappers=wrappers_path, + sep=os.pathsep, + path=env_vars['PATH'], + ) + env_vars['INFER_RESULTS_DIR'] = self.args.infer_out + return env_vars + + def capture(self): + try: + env = utils.encode_env(self.get_envvars()) + cmd = map(utils.encode, self.cmd) + logging.info('Running command %s with env:\n%s' % (cmd, env)) + subprocess.check_call(cmd, env=env) + capture_dir = os.path.join(self.args.infer_out, 'captured') + if len(os.listdir(capture_dir)) < 1: + # Don't return with a failure code unless we're + # running make. It could be normal to have captured + # nothing (eg, empty source file). Further output will + # alert the user that there was nothing to analyze. + if self.cmd[0] == 'make': + # reuse code from gradle, etc. integration + return util.run_compilation_commands([], 'make clean') + return os.EX_OK + except subprocess.CalledProcessError as exc: + if self.args.debug: + traceback.print_exc() + return exc.returncode diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index bbb3ead74..f98005b8a 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -115,6 +115,14 @@ let pp_driver_mode fmt driver_mode = F.fprintf fmt "Clang driver mode:@\nprog = %s@\n" prog; List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args +(* A clean command for each driver mode to be suggested to the user + in case nothing got captured. *) +let clean_compilation_command driver_mode = + match driver_mode with + | Clang (_, prog, _) -> + Some (prog ^ " clean") + | _ -> None + let remove_results_dir () = rmtree Config.results_dir @@ -156,6 +164,20 @@ let clean_results_dir () = () in clean Config.results_dir +let check_captured_empty driver_mode = + let clean_command_opt = clean_compilation_command driver_mode in + (* if merge is passed, the captured folder will be empty at this point, + but will be filled later on. *) + if Utils.dir_is_empty Config.captured_dir && not Config.merge then (( + match clean_command_opt with + | Some clean_command -> + Logging.stderr "@\nNothing to compile. Try running `%s` first.@." clean_command + | None -> + Logging.stderr "@\nNothing to compile. Try cleaning the build first.@." + ); + true + ) else + false let register_perf_stats_report () = let stats_dir = Filename.concat Config.results_dir Config.backend_stats_dir_name in @@ -355,11 +377,11 @@ let analyze driver_mode = | _, Linters -> false, true in if (should_analyze || should_report) && - (Sys.file_exists Config.(results_dir ^/ captured_dir_name)) <> `Yes then ( - L.stderr "There was nothing to analyze, exiting@." ; - exit 1 - ); - if should_analyze then execute_analyze (); + (((Sys.file_exists Config.captured_dir) <> `Yes) || + check_captured_empty driver_mode) then ( + L.stderr "There was nothing to analyze.@\n@." ; + ) else if should_analyze then + execute_analyze (); if should_report then report () (** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *) diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 613bcd04d..891c486a1 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -1751,6 +1751,8 @@ and analysis_suppress_errors analyzer = let checkers_enabled = not (eradicate || crashcontext || quandary || threadsafety || checkers_repeated_calls) +let captured_dir = results_dir ^/ captured_dir_name + let clang_frontend_do_capture, clang_frontend_do_lint = match !clang_frontend_action with | Some `Lint -> false, true (* no capture, lint *) diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 0493434ca..c21bddf3f 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -187,6 +187,10 @@ val bugs_txt : string option val bugs_xml : string option val changed_files_index : string option val calls_csv : string option + +(** directory where the results of the capture phase are stored *) +val captured_dir : string + val checkers : bool val checkers_enabled : bool val checkers_repeated_calls : bool diff --git a/infer/src/base/DB.ml b/infer/src/base/DB.ml index 5c533a280..17fa2534d 100644 --- a/infer/src/base/DB.ml +++ b/infer/src/base/DB.ml @@ -31,17 +31,14 @@ let source_dir_get_internal_file source_dir extension = let fname = source_dir_name ^ extension in Filename.concat source_dir fname -let captured_dir = - Filename.concat Config.results_dir Config.captured_dir_name - (** get the source directory corresponding to a source file *) let source_dir_from_source_file source_file = - Filename.concat captured_dir (SourceFile.encoding source_file) + Filename.concat Config.captured_dir (SourceFile.encoding source_file) (** Find the source directories in the results dir *) let find_source_dirs () = let source_dirs = ref [] in - let files_in_results_dir = Array.to_list (Sys.readdir captured_dir) in + let files_in_results_dir = Array.to_list (Sys.readdir Config.captured_dir) in let add_cg_files_from_dir dir = let files = Array.to_list (Sys.readdir dir) in List.iter ~f:(fun fname -> @@ -49,7 +46,7 @@ let find_source_dirs () = if Filename.check_suffix path ".cg" then source_dirs := dir :: !source_dirs) files in List.iter ~f:(fun fname -> - let dir = Filename.concat captured_dir fname in + let dir = Filename.concat Config.captured_dir fname in if Sys.is_directory dir = `Yes then add_cg_files_from_dir dir) files_in_results_dir; List.rev !source_dirs @@ -214,7 +211,7 @@ end let global_tenv_fname = let basename = Config.global_tenv_filename in - filename_concat captured_dir basename + filename_concat Config.captured_dir basename let is_source_file path = List.exists diff --git a/infer/src/base/DB.mli b/infer/src/base/DB.mli index ac56b42a0..0186ef798 100644 --- a/infer/src/base/DB.mli +++ b/infer/src/base/DB.mli @@ -85,9 +85,6 @@ val source_dir_get_internal_file : source_dir -> string -> filename (** get the source directory corresponding to a source file *) val source_dir_from_source_file : SourceFile.t -> source_dir -(** directory where the results of the capture phase are stored *) -val captured_dir : filename - (** create the directory containing the file bane *) val filename_create_dir : filename -> unit diff --git a/infer/src/base/Utils.ml b/infer/src/base/Utils.ml index 4834e27f3..e98a8bb4e 100644 --- a/infer/src/base/Utils.ml +++ b/infer/src/base/Utils.ml @@ -176,6 +176,19 @@ let directory_iter f path = else f path +let dir_is_empty path = + let dir_handle = Unix.opendir path in + let is_empty = ref true in + (try + while !is_empty; + do if not (List.mem ~equal:String.equal["."; ".."] (Unix.readdir dir_handle)) then + is_empty := false; + done; + with End_of_file -> () + ); + Unix.closedir dir_handle; + !is_empty + let string_crc_hex32 s = Digest.to_hex (Digest.string s) diff --git a/infer/src/base/Utils.mli b/infer/src/base/Utils.mli index 2267070b4..c0309d831 100644 --- a/infer/src/base/Utils.mli +++ b/infer/src/base/Utils.mli @@ -52,6 +52,9 @@ val directory_fold : ('a -> string -> 'a) -> 'a -> string -> 'a (** Functional iter function over all the file of a directory *) val directory_iter : (string -> unit) -> string -> unit +(** Returns true if a given directory is empty. The directory is assumed to exist. *) +val dir_is_empty : string -> bool + val read_optional_json_file : string -> (Yojson.Basic.json, string) Result.t val with_file : string -> f:(out_channel -> 'a) -> 'a