From 216812a615c48e0b7016ce0268d8f9fb8475130b Mon Sep 17 00:00:00 2001 From: Josh Berdine Date: Wed, 2 Nov 2016 16:52:10 -0700 Subject: [PATCH] [driver] Add skeleton capture and analysis driver to infer.ml Summary: This diff adds a skeleton implementation of the capture and analysis driver to infer.ml, and removes some unnecessary code from infer.py. With this, individual capture and analysis modules can be added, or moved from python. Reviewed By: jvillard Differential Revision: D4109547 fbshipit-source-id: 0dce2bf --- infer/lib/python/infer.py | 4 - infer/src/backend/infer.ml | 213 +++++++++++++++++++++++++++---------- 2 files changed, 154 insertions(+), 63 deletions(-) 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