diff --git a/infer/lib/python/infer.py b/infer/lib/python/infer.py index 8569912af..f46f7ab5a 100755 --- a/infer/lib/python/infer.py +++ b/infer/lib/python/infer.py @@ -211,10 +211,6 @@ def main(): if not (buck_not_in_compilation_database_mode or mod_name == 'javac' or mod_name == 'java'): - # Something should be already captured, otherwise analysis would fail - 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.AnalyzerWrapper(args) analysis.analyze_and_report() analysis.save_stats() diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index 9dbfd87d7..40f48466d 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -9,7 +9,27 @@ open! Utils -(** Top-level driver that orchestrates build system integration, frontends, and backend *) +(** Top-level driver that orchestrates build system integration, frontends, backend, and + reporting *) + +module L = Logging + +let rec rmtree name = + match Unix.opendir name with + | dir -> ( + match Unix.readdir dir with + | entry when entry = Filename.current_dir_name || entry = Filename.parent_dir_name -> + () + | entry -> + rmtree entry + | exception End_of_file -> + Unix.closedir dir ; + Unix.rmdir name + ) + | exception Unix.Unix_error (Unix.ENOTDIR, _, _) -> + Unix.unlink name + | exception Unix.Unix_error (Unix.ENOENT, _, _) -> + () (** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *) let fail_on_issue_epilogue () = @@ -20,69 +40,144 @@ let fail_on_issue_epilogue () = if issues <> [] then exit Config.fail_on_issue_exit_code | None -> () -let () = - let infer_py = Config.lib_dir // "python" // "infer.py" in - let build_cmd = IList.rev Config.rest in - let in_buck_mode = match build_cmd with "buck" :: _ -> true | _ -> false in - let args_py = - Array.of_list ( - infer_py :: - Config.anon_args @ - (match Config.analyzer with None -> [] | Some a -> - ["--analyzer"; - IList.assoc (=) a (IList.map (fun (n,a) -> (a,n)) Config.string_to_analyzer)]) @ - (match Config.blacklist with - | Some s when in_buck_mode -> ["--blacklist-regex"; s] - | _ -> []) @ - (if not Config.create_harness then [] else - ["--android-harness"]) @ - (if not Config.buck then [] else - ["--buck"]) @ - (match Config.java_jar_compiler with None -> [] | Some p -> - ["--java-jar-compiler"; p]) @ - (match IList.rev Config.buck_build_args with - | args when in_buck_mode -> - IList.map (fun arg -> ["--Xbuck"; "'" ^ arg ^ "'"]) args |> IList.flatten - | _ -> []) @ - (if not Config.continue_capture then [] else - ["--continue"]) @ - (if not Config.debug_mode then [] else - ["--debug"]) @ - (if not Config.debug_exceptions then [] else - ["--debug-exceptions"]) @ - (if Config.filtering then [] else - ["--no-filtering"]) @ - (if not Config.flavors || not in_buck_mode then [] else - ["--use-flavors"]) @ - (if Option.is_none Config.use_compilation_database || not in_buck_mode then [] else - ["--use-compilation-database"]) @ - "-j" :: (string_of_int Config.jobs) :: - "-l" :: (string_of_float Config.load_average) :: - (if not Config.pmd_xml then [] else - ["--pmd-xml"]) @ - (if not Config.reactive_mode then [] else - ["--reactive"]) @ - "--out" :: Config.results_dir :: - (match Config.xcode_developer_dir with None -> [] | Some d -> - ["--xcode-developer-dir"; d]) @ - (if Config.rest = [] then [] else - ("--" :: build_cmd)) - ) in - let pid = Unix.create_process args_py.(0) args_py Unix.stdin Unix.stdout Unix.stderr in +(* permissions used for created files *) +let file_perm = 0o0666 + +let create_results_dir () = + create_path (Config.results_dir // Config.captured_dir_name) ; + create_path (Config.results_dir // Config.sources_dir_name) ; + create_path (Config.results_dir // Config.specs_dir_name) + +let touch_start_file () = + let start = Config.results_dir // Config.start_filename in + let flags = + Unix.O_CREAT :: Unix.O_WRONLY :: (if Config.continue_capture then [Unix.O_EXCL] else []) in + (* create new file, or open existing file for writing to update modified timestamp *) + try Unix.close (Unix.openfile start flags file_perm) + with Unix.Unix_error (Unix.EEXIST, _, _) -> () + +type build_mode = Analyze | Ant | Buck | Gradle | Java | Javac | Make | Mvn | Ndk | Xcode + +let build_mode_of_string path = + match Filename.basename path with + | "analyze" -> Analyze + | "ant" -> Ant + | "buck" -> Buck + | "gradle" | "gradlew" -> Gradle + | "java" -> Java + | "javac" -> Javac + | "cc" | "clang" | "clang++" | "cmake" | "configure" | "g++" | "gcc" | "make" | "waf" -> Make + | "mvn" -> Mvn + | "ndk-build" -> Ndk + | "xcodebuild" -> Xcode + | cmd -> failwithf "Unsupported build command %s" cmd + +let remove_results_dir build_mode = + if not (build_mode = Analyze || Config.buck || Config.reactive_mode) then + rmtree Config.results_dir + +let run_command cmd_list after_wait = + let cmd = Array.of_list cmd_list in + let pid = Unix.create_process cmd.(0) cmd Unix.stdin Unix.stdout Unix.stderr in let _, status = Unix.waitpid [] pid in - let exit_code = match status with - | Unix.WEXITED i -> i - | _ -> 1 in - if exit_code = Config.infer_py_argparse_error_exit_code then - (* swallow infer.py argument parsing error *) - Config.print_usage_exit (); + let exit_code = match status with Unix.WEXITED i -> i | _ -> 1 in + after_wait exit_code ; if exit_code <> 0 then ( - prerr_endline ("Failed to execute: " ^ (String.concat " " (Array.to_list args_py))) ; + L.err "Failed to execute: %s@\n" (String.concat " " cmd_list) ; exit exit_code - ); + ) + +let capture build_cmd = function + | build_mode -> + let in_buck_mode = build_mode = Buck in + let infer_py = Config.lib_dir // "python" // "infer.py" in + run_command ( + infer_py :: + Config.anon_args @ + (match Config.analyzer with None -> [] | Some a -> + ["--analyzer"; + IList.assoc (=) a (IList.map (fun (n,a) -> (a,n)) Config.string_to_analyzer)]) @ + (match Config.blacklist with + | Some s when in_buck_mode -> ["--blacklist-regex"; s] + | _ -> []) @ + (if not Config.create_harness then [] else + ["--android-harness"]) @ + (if not Config.buck then [] else + ["--buck"]) @ + (match Config.java_jar_compiler with None -> [] | Some p -> + ["--java-jar-compiler"; p]) @ + (match IList.rev Config.buck_build_args with + | args when in_buck_mode -> + IList.map (fun arg -> ["--Xbuck"; "'" ^ arg ^ "'"]) args |> IList.flatten + | _ -> []) @ + (if not Config.continue_capture then [] else + ["--continue"]) @ + (if not Config.debug_mode then [] else + ["--debug"]) @ + (if not Config.debug_exceptions then [] else + ["--debug-exceptions"]) @ + (if Config.filtering then [] else + ["--no-filtering"]) @ + (if not Config.flavors || not in_buck_mode then [] else + ["--use-flavors"]) @ + (if Option.is_none Config.use_compilation_database || not in_buck_mode then [] else + ["--use-compilation-database"]) @ + "-j" :: (string_of_int Config.jobs) :: + "-l" :: (string_of_float Config.load_average) :: + (if not Config.pmd_xml then [] else + ["--pmd-xml"]) @ + (if not Config.reactive_mode then [] else + ["--reactive"]) @ + "--out" :: Config.results_dir :: + (match Config.xcode_developer_dir with None -> [] | Some d -> + ["--xcode-developer-dir"; d]) @ + (if Config.rest = [] then [] else + ("--" :: build_cmd)) + ) (fun exit_code -> + if exit_code = Config.infer_py_argparse_error_exit_code then + (* swallow infer.py argument parsing error *) + Config.print_usage_exit () + ) + +let analyze = function + | Buck when Config.use_compilation_database = None -> + (* In Buck mode when compilation db is not used, analysis is invoked either from capture or a + separate Analyze invocation is necessary, depending on the buck flavor used. *) + () + | Java | Javac -> + (* In Java and Javac modes, analysis is invoked from capture. *) + () + | Analyze | Ant | Buck | Gradle | Make | Mvn | Ndk | Xcode -> + if not (Sys.file_exists Config.(results_dir // captured_dir_name)) then ( + L.err "There was nothing to analyze, exiting" ; + Config.print_usage_exit () + ); + (match Config.analyzer with + | None | Some (Infer | Eradicate | Checkers | Tracing | Crashcontext | Quandary) -> + (* Still handled by infer.py through capture function above *) + () + | Some Linters -> + (* Still handled by infer.py through capture function above *) + () + | Some (Capture | Compile) -> + (* Still handled by infer.py through capture function above *) + () + ) + +let epilogue build_mode = if Config.is_originator then ( if Config.analyzer = Some Config.Crashcontext then - Crashcontext.crashcontext_epilogue ~in_buck_mode; + Crashcontext.crashcontext_epilogue ~in_buck_mode:(build_mode = Buck); if Config.fail_on_bug then fail_on_issue_epilogue (); ) + +let () = + let build_cmd = IList.rev Config.rest in + let build_mode = match build_cmd with path :: _ -> build_mode_of_string path | [] -> Analyze in + remove_results_dir build_mode ; + create_results_dir () ; + touch_start_file () ; + capture build_cmd build_mode ; + analyze build_mode ; + epilogue build_mode