From 2f569e2b97f88aadb4082972fab49cfbddf6c8b2 Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Tue, 27 Jun 2017 07:17:52 -0700 Subject: [PATCH] [driver] move things around Summary: This will be needed to re-use the functions now in Driver.ml in other contexts without always adding to infer.ml. For instance, this is used in a later diff to do a diff-analysis orchestrator that needs to run the capture and analysis several times. Reviewed By: jberdine Differential Revision: D5319862 fbshipit-source-id: caf9551 --- infer/src/Makefile | 2 +- infer/src/backend/infer.ml | 587 ++------------------------- infer/src/base/Utils.ml | 22 + infer/src/base/Utils.mli | 3 + infer/src/integration/Driver.ml | 518 +++++++++++++++++++++++ infer/src/integration/Driver.mli | 44 ++ infer/src/integration/ReportDiff.ml | 36 ++ infer/src/integration/ReportDiff.mli | 11 + 8 files changed, 663 insertions(+), 560 deletions(-) create mode 100644 infer/src/integration/Driver.ml create mode 100644 infer/src/integration/Driver.mli create mode 100644 infer/src/integration/ReportDiff.ml create mode 100644 infer/src/integration/ReportDiff.mli diff --git a/infer/src/Makefile b/infer/src/Makefile index 7e4bb24e9..609f49868 100644 --- a/infer/src/Makefile +++ b/infer/src/Makefile @@ -272,7 +272,7 @@ rei: %.rei : %.mli $(SCRIPT_DIR)/refmt.sh -parse ml -print re $< > $*.rei -roots:=Infer StatsAggregator +roots:=Infer ifeq ($(IS_FACEBOOK_TREE),yes) roots += $(INFER_CREATE_TRACEVIEW_LINKS_MODULE) endif diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index b946f3c79..7d48f9523 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -18,500 +18,6 @@ module L = Logging module F = Format -let rec rmtree name = - match Unix.((lstat name).st_kind) with - | S_DIR -> - let dir = Unix.opendir name in - let rec rmdir dir = - match Unix.readdir dir with - | entry -> - if not (String.equal entry Filename.current_dir_name || - String.equal entry Filename.parent_dir_name) - then ( - rmtree (name ^/ entry) - ); - rmdir dir - | exception End_of_file -> - Unix.closedir dir ; - Unix.rmdir name in - rmdir dir - | _ -> - Unix.unlink name - | exception Unix.Unix_error (Unix.ENOENT, _, _) -> - () - - -type build_system = - | BAnalyze | BAnt | BBuck | BClang | BGradle | BJava | BJavac | BMake | BMvn - | BNdk | BXcode -[@@deriving compare] - -let equal_build_system = [%compare.equal : build_system] - -(* List of ([build system], [executable name]). Several executables may map to the same build - system. In that case, the first one in the list will be used for printing, eg, in which mode - infer is running. *) -let build_system_exe_assoc = [ - BAnalyze, "analyze"; BAnt, "ant"; BBuck, "buck"; BGradle, "gradle"; BGradle, "gradlew"; - BJava, "java"; BJavac, "javac"; - BClang, "cc"; BClang, "clang"; BClang, "gcc"; BClang, "clang++"; BClang, "c++"; BClang, "g++"; - BMake, "make"; BMake, "configure"; BMake, "cmake"; BMake, "waf"; - BMvn, "mvn"; BMvn, "mvnw"; BNdk, "ndk-build"; BXcode, "xcodebuild"; -] - -let build_system_of_exe_name name = - try - List.Assoc.find_exn (List.Assoc.inverse build_system_exe_assoc) name - with Not_found -> - invalid_argf "Unsupported build command %s" name - -let string_of_build_system build_system = - List.Assoc.find_exn build_system_exe_assoc build_system - -(* based on the build_system and options passed to infer, we run in different driver modes *) -type driver_mode = - | Analyze - | BuckGenrule of string - | BuckCompilationDB of string * string list - | Clang of Clang.compiler * string * string list - | ClangCompilationDB of [ `Escaped of string | `Raw of string ] list - | Javac of Javac.compiler * string * string list - | Maven of string * string list - | PythonCapture of build_system * string list - | XcodeXcpretty of string * string list -[@@deriving compare] - -let equal_driver_mode = [%compare.equal : driver_mode] - -let pp_driver_mode fmt driver_mode = - let log_argfile_arg fname = - try - F.fprintf fmt "-- Contents of '%s'@\n" fname; - In_channel.iter_lines ~f:(F.fprintf fmt "%s@\n") (In_channel.create fname); - F.fprintf fmt "-- /Contents of '%s'@." fname; - with exn -> - F.fprintf fmt " Error reading file '%s':@\n %a@." fname Exn.pp exn in - match driver_mode with - | Analyze | BuckGenrule _ | BuckCompilationDB _ | ClangCompilationDB _ | PythonCapture (_,_) - | XcodeXcpretty _ -> - (* these are pretty boring, do not log anything *) - () - | Javac (_, prog, args) -> - F.fprintf fmt "Javac driver mode:@\nprog = %s@\n" prog; - let log_arg arg = - F.fprintf fmt "Arg: %s@\n" arg; - (* "@fname" means that fname is an arg file containing additional arguments to pass to - javac. *) - String.chop_prefix ~prefix:"@" arg - |> - (* Sometimes these argfiles go away at the end of the build and we cannot inspect them after - the fact, so log them now. *) - Option.iter ~f:log_argfile_arg in - List.iter ~f:log_arg args - | Maven (prog, args) -> - F.fprintf fmt "Maven driver mode:@\nprog = %s@\n" prog; - List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args - | Clang (_, prog, args) -> - 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 - | BuckCompilationDB (prog, _) - | Clang (_, prog, _) -> - Some (prog ^ " clean") - | XcodeXcpretty (prog, args) -> - Some (String.concat ~sep:" " (List.append (prog::args) ["clean"])) - | _ -> None - -let remove_results_dir () = - rmtree Config.results_dir - -let create_results_dir () = - Unix.mkdir_p (Config.results_dir ^/ Config.attributes_dir_name) ; - Unix.mkdir_p (Config.results_dir ^/ Config.captured_dir_name) ; - Unix.mkdir_p (Config.results_dir ^/ Config.specs_dir_name); - L.setup_log_file () - -(* Clean up the results dir to select only what's relevant to go in the Buck cache. In particular, - get rid of non-deterministic outputs.*) -let clean_results_dir () = - (* In Buck flavors mode we keep all capture data, but in Java mode we keep only the tenv *) - let should_delete_dir = - let dirs_to_delete = - "backend_stats" :: "classnames" :: "filelists" :: "frontend_stats" :: "multicore" :: - "reporting_stats" :: "sources" :: - if Config.flavors then [] - else ["attributes"] in - List.mem ~equal:String.equal dirs_to_delete in - let should_delete_file = - let suffixes_to_delete = - ".txt" :: ".csv" :: ".json" :: - if Config.flavors then [] - else [ ".cfg"; ".cg" ] in - fun name -> - (* Keep the JSON report *) - not (String.equal (Filename.basename name) "report.json") - && List.exists ~f:(Filename.check_suffix name) suffixes_to_delete in - let rec clean name = - let rec cleandir dir = match Unix.readdir dir with - | entry -> - if should_delete_dir entry then ( - rmtree (name ^/ entry) - ) else if not (String.equal entry Filename.current_dir_name - || String.equal entry Filename.parent_dir_name) then ( - clean (name ^/ entry) - ); - cleandir dir (* next entry *) - | exception End_of_file -> - Unix.closedir dir in - match Unix.opendir name with - | dir -> - cleandir dir - | exception Unix.Unix_error (Unix.ENOTDIR, _, _) -> - if should_delete_file name then - Unix.unlink name; - () - | exception Unix.Unix_error (Unix.ENOENT, _, _) -> - () 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 -> - L.user_warning "@\nNothing to compile. Try running `%s` first.@." clean_command - | None -> - L.user_warning "@\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 - let stats_base = Config.perf_stats_prefix ^ ".json" in - let stats_file = Filename.concat stats_dir stats_base in - PerfStats.register_report_at_exit stats_file - -let reset_duplicates_file () = - let start = Config.results_dir ^/ Config.duplicates_filename in - let delete () = - Unix.unlink start in - let create () = - Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in - if Sys.file_exists start = `Yes then delete (); - create () - -(* Create the .start file, and update the timestamp unless in continue mode *) -let touch_start_file_unless_continue () = - let start = Config.results_dir ^/ Config.start_filename in - let delete () = - Unix.unlink start in - let create () = - Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in - if not (Sys.file_exists start = `Yes) then create () - else if not Config.continue_capture then (delete (); create ()) - - -let run_command ~prog ~args cleanup = - Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ()) - |> fun status - -> cleanup status - ; ok_exn (Unix.Exit_or_signal.or_error status) - -let check_xcpretty () = - match Unix.system "xcpretty --version" with - | Ok () -> () - | Error _ -> - L.user_error - "@\nxcpretty not found in the path. Please consider installing xcpretty \ - for a more robust integration with xcodebuild. Otherwise use the option \ - --no-xcpretty.@\n@." - -let capture_with_compilation_database db_files = - let root = Unix.getcwd () in - Config.clang_compilation_dbs := List.map db_files ~f:(function - | `Escaped fname -> `Escaped (Utils.filename_to_absolute ~root fname) - | `Raw fname -> `Raw (Utils.filename_to_absolute ~root fname) - ); - let compilation_database = CompilationDatabase.from_json_files db_files in - CaptureCompilationDatabase.capture_files_in_database compilation_database - -let capture ~changed_files = function - | Analyze -> - () - | BuckCompilationDB (prog, args) -> - L.progress "Capturing using Buck's compilation database...@."; - let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck ~prog ~args in - capture_with_compilation_database ~changed_files json_cdb - | BuckGenrule path -> - L.progress "Capturing for Buck genrule compatibility...@."; - JMain.from_arguments path - | Clang (compiler, prog, args) -> - L.progress "Capturing in make/cc mode...@."; - Clang.capture compiler ~prog ~args - | ClangCompilationDB db_files -> - L.progress "Capturing using compilation database...@."; - capture_with_compilation_database ~changed_files db_files - | Javac (compiler, prog, args) -> - L.progress "Capturing in javac mode...@."; - Javac.capture compiler ~prog ~args - | Maven (prog, args) -> - L.progress "Capturing in maven mode...@."; - Maven.capture ~prog ~args - | PythonCapture (build_system, build_cmd) -> - L.progress "Capturing in %s mode...@." (string_of_build_system build_system); - let in_buck_mode = equal_build_system build_system BBuck in - let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in - let args = - List.rev_append Config.anon_args ( - ["--analyzer"; - List.Assoc.find_exn ~equal:Config.equal_analyzer - (List.map ~f:(fun (n,a) -> (a,n)) Config.string_to_analyzer) Config.analyzer] @ - (match Config.blacklist with - | Some s when in_buck_mode -> ["--blacklist-regex"; s] - | _ -> []) @ - (if not Config.create_harness then [] else - ["--android-harness"]) @ - (match Config.java_jar_compiler with None -> [] | Some p -> - ["--java-jar-compiler"; p]) @ - (match List.rev Config.buck_build_args with - | args when in_buck_mode -> - List.map ~f:(fun arg -> ["--Xbuck"; "'" ^ arg ^ "'"]) args |> List.concat - | _ -> []) @ - (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"]) @ - "-j" :: (string_of_int Config.jobs) :: - (match Config.load_average with None -> [] | Some l -> - ["-l"; string_of_float l]) @ - (if not Config.pmd_xml then [] else - ["--pmd-xml"]) @ - ["--project-root"; Config.project_root] @ - (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 in_buck_mode && Config.flavors then - (* let children infer processes know that they are inside Buck *) - let infer_args_with_buck = String.concat ~sep:(String.of_char CLOpt.env_var_sep) - (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) in - Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck; - Buck.add_flavors_to_buck_command build_cmd - else build_cmd - ) in - run_command ~prog:infer_py ~args - (function - | Result.Error (`Exit_non_zero exit_code) -> - if Int.equal exit_code Config.infer_py_argparse_error_exit_code then - (* swallow infer.py argument parsing error *) - Config.print_usage_exit () - | _ -> - () - ) - | XcodeXcpretty (prog, args) -> - L.progress "Capturing using xcodebuild and xcpretty...@."; - check_xcpretty (); - let json_cdb = - CaptureCompilationDatabase.get_compilation_database_files_xcodebuild ~prog ~args in - capture_with_compilation_database ~changed_files json_cdb - -let run_parallel_analysis ~changed_files = - let multicore_dir = Config.results_dir ^/ Config.multicore_dir_name in - rmtree multicore_dir ; - Unix.mkdir_p multicore_dir ; - InferAnalyze.main ~changed_files ~makefile:(multicore_dir ^/ "Makefile") ; - run_command - ~prog:"make" ~args:( - "-C" :: multicore_dir :: - "-k" :: - "-j" :: (string_of_int Config.jobs) :: - (Option.value_map ~f:(fun l -> ["-l"; string_of_float l]) ~default:[] Config.load_average) @ - (if Config.debug_mode then [] else ["-s"]) - ) (fun _ -> ()) - -let execute_analyze ~changed_files = - if Int.equal Config.jobs 1 || Config.cluster_cmdline <> None then - InferAnalyze.main ~changed_files ~makefile:"" - else - run_parallel_analysis ~changed_files - -let report () = - let report_csv = - if Config.buck_cache_mode then None else Some (Config.results_dir ^/ "report.csv") in - let report_json = Some (Config.results_dir ^/ "report.json") in - InferPrint.main ~report_csv ~report_json ; - (* Post-process the report according to the user config. By default, calls report.py to create a - human-readable report. - - Do not bother calling the report hook when called from within Buck or in quiet mode. *) - match Config.quiet || Config.buck_cache_mode, Config.report_hook with - | true, _ - | false, None -> - () - | false, Some prog -> - let if_some key opt args = match opt with None -> args | Some arg -> key :: arg :: args in - let if_true key opt args = if not opt then args else key :: args in - let args = - if_some "--issues-csv" report_csv @@ - if_some "--issues-json" report_json @@ - if_some "--issues-txt" Config.bugs_txt @@ - if_true "--pmd-xml" Config.pmd_xml [ - "--project-root"; Config.project_root; - "--results-dir"; Config.results_dir - ] in - if is_error (Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())) then - L.external_error - "** Error running the reporting script:@\n** %s %s@\n** See error above@." - prog (String.concat ~sep:" " args) - -let analyze driver_mode ~changed_files = - let should_analyze, should_report = match driver_mode, Config.analyzer with - | PythonCapture (BBuck, _), _ -> - (* 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. *) - false, false - | _ when Config.maven -> - (* Called from Maven, only do capture. *) - false, false - | _, (CaptureOnly | CompileOnly) -> - false, false - | _, (BiAbduction | Checkers | Crashcontext | Eradicate) -> - true, true - | _, Linters -> - false, true in - if (should_analyze || should_report) && - (((Sys.file_exists Config.captured_dir) <> `Yes) || - check_captured_empty driver_mode) then ( - L.user_error "There was nothing to analyze.@\n@." ; - ) else if should_analyze then - execute_analyze ~changed_files; - if should_report && Config.report then report () - -(** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *) -let fail_on_issue_epilogue () = - let issues_json = DB.Results_dir.(path_to_filename Abs_root ["report.json"]) - |> DB.filename_to_string in - match Utils.read_file issues_json with - | Ok lines -> - let issues = Jsonbug_j.report_of_string @@ String.concat ~sep:"" lines in - if issues <> [] then exit Config.fail_on_issue_exit_code - | Error error -> - L.internal_error "Failed to read report file '%s': %s@." issues_json error ; - () - -let log_infer_args driver_mode = - L.environment_info "INFER_ARGS = %s@\n" - (Option.value (Sys.getenv CLOpt.args_env_var) ~default:""); - List.iter ~f:(L.environment_info "anon arg: %s@\n") Config.anon_args; - List.iter ~f:(L.environment_info "rest arg: %s@\n") Config.rest; - L.environment_info "Project root = %s@\n" Config.project_root; - L.environment_info "CWD = %s@\n" (Sys.getcwd ()); - L.environment_info "Driver mode:@\n%a@." pp_driver_mode driver_mode - -let assert_supported_mode required_analyzer requested_mode_string = - let analyzer_enabled = match required_analyzer with - | `Clang -> Version.clang_enabled - | `Java -> Version.java_enabled - | `Xcode -> Version.clang_enabled && Version.xcode_enabled in - if not analyzer_enabled then - let analyzer_string = match required_analyzer with - | `Clang -> "clang" - | `Java -> "java" - | `Xcode -> "clang and xcode" in - failwithf - "Unsupported build mode: %s@\nInfer was built with %s analyzers disabled.@ Please rebuild \ - infer with %s enabled.@." - requested_mode_string analyzer_string analyzer_string - -let assert_supported_build_system build_system = match build_system with - | BAnt | BGradle | BJava | BJavac | BMvn -> - string_of_build_system build_system - |> assert_supported_mode `Java - | BClang | BMake | BNdk -> - string_of_build_system build_system - |> assert_supported_mode `Clang - | BXcode -> - string_of_build_system build_system - |> assert_supported_mode `Xcode - | BBuck -> - let (analyzer, build_string) = - if Config.flavors then - (`Clang, "buck with flavors") - else if Option.is_some Config.buck_compilation_database then - (`Clang, "buck compilation database") - else ( - if Config.reactive_mode then - L.user_error - "WARNING: The reactive analysis mode is not compatible with the Buck integration for \ - Java"; - (`Java, string_of_build_system build_system) - ) in - assert_supported_mode analyzer build_string - | BAnalyze -> - () - -let driver_mode_of_build_cmd build_cmd = - match build_cmd with - | [] -> - if not (List.is_empty !Config.clang_compilation_dbs) then ( - assert_supported_mode `Clang "clang compilation database"; - ClangCompilationDB !Config.clang_compilation_dbs - ) else - Analyze - | prog :: args -> - let build_system = build_system_of_exe_name (Filename.basename prog) in - assert_supported_build_system build_system; - match build_system_of_exe_name (Filename.basename prog) with - | BAnalyze -> - CLOpt.warnf - "WARNING: `infer -- analyze` is deprecated; \ - use the `infer analyze` subcommand instead@."; - Analyze - | BBuck when Option.is_some Config.buck_compilation_database -> - BuckCompilationDB (prog, List.append args (List.rev Config.buck_build_args)) - | BClang -> - Clang (Clang.Clang, prog, args) - | BMake -> - Clang (Clang.Make, prog, args) - | BJava -> - Javac (Javac.Java, prog, args) - | BJavac -> - Javac (Javac.Javac, prog, args) - | BMvn -> - Maven (prog, args) - | BXcode when Config.xcpretty -> - XcodeXcpretty (prog, args) - | BAnt | BBuck | BGradle | BNdk | BXcode as build_system -> - PythonCapture (build_system, build_cmd) - -let get_driver_mode () = - match Config.generated_classes with - | _ when Config.maven -> - (* infer is pretending to be javac in the Maven integration *) - let build_args = match Array.to_list Sys.argv with - | _::args -> args - | [] -> [] in - Javac (Javac.Javac, "javac", build_args) - | Some path -> - assert_supported_mode `Java "Buck genrule"; - BuckGenrule path - | None -> - driver_mode_of_build_cmd (List.rev Config.rest) - let read_config_changed_files () = match Config.changed_files_index with | None -> @@ -523,67 +29,22 @@ let read_config_changed_files () = L.external_error "Error reading the changed files index '%s': %s@." index error ; None -let infer_mode () = - let driver_mode = get_driver_mode () in - if not (equal_driver_mode driver_mode Analyze || - Config.(buck || continue_capture || maven || reactive_mode)) then - remove_results_dir () ; - create_results_dir () ; - if CLOpt.is_originator then L.environment_info "%a@\n" Config.pp_version () ; - if Config.debug_mode || Config.stats_mode then log_infer_args driver_mode ; - if Config.dump_duplicate_symbols then reset_duplicates_file (); - (* infer might be called from a Makefile and itself uses `make` to run the analysis in parallel, - but cannot communicate with the parent make command. Since infer won't interfere with them - anyway, pretend that we are not called from another make to prevent make falling back to a - mono-threaded execution. *) - Unix.unsetenv "MAKEFLAGS"; - register_perf_stats_report () ; - if not Config.buck_cache_mode then touch_start_file_unless_continue () ; +let run driver_mode = + let open Driver in + run_prologue driver_mode ; let changed_files = read_config_changed_files () in capture driver_mode ~changed_files ; - analyze driver_mode ~changed_files ; - if CLOpt.is_originator then ( - let in_buck_mode = match driver_mode with | PythonCapture (BBuck, _) -> true | _ -> false in - StatsAggregator.generate_files () ; - if Config.equal_analyzer Config.analyzer Config.Crashcontext then - Crashcontext.crashcontext_epilogue ~in_buck_mode; - if Config.fail_on_bug then - fail_on_issue_epilogue () ; - ); - if Config.buck_cache_mode then clean_results_dir (); - () + analyze_and_report driver_mode ~changed_files ; + run_epilogue driver_mode + +let remove_results_dir () = + Utils.rmtree Config.results_dir -let differential_mode () = - (* at least one report must be passed in input to compute differential *) - (match Config.report_current, Config.report_previous with - | None, None -> - failwith "Expected at least one argument among 'report-current' and 'report-previous'@\n" - | _ -> ()); - let load_report filename_opt : Jsonbug_t.report = - let empty_report = [] in - Option.value_map - ~f:(fun filename -> Jsonbug_j.report_of_string (In_channel.read_all filename)) - ~default:empty_report filename_opt in - let current_report = load_report Config.report_current in - let previous_report = load_report Config.report_previous in - let diff = - let unfiltered_diff = Differential.of_reports ~current_report ~previous_report in - if Config.filtering then - let file_renamings = match Config.file_renamings with - | Some f -> DifferentialFilters.FileRenamings.from_json_file f - | None -> DifferentialFilters.FileRenamings.empty in - let interesting_paths = Option.map ~f:(fun fname -> - List.map ~f:(SourceFile.create ~warn_on_error:false) (In_channel.read_lines fname)) - Config.differential_filter_files in - DifferentialFilters.do_filter - unfiltered_diff - file_renamings - ~skip_duplicated_types:Config.skip_duplicated_types - ~interesting_paths - else unfiltered_diff in - let out_path = Config.results_dir ^/ "differential" in - Unix.mkdir_p out_path; - Differential.to_files diff out_path +let create_results_dir () = + Unix.mkdir_p (Config.results_dir ^/ Config.attributes_dir_name) ; + Unix.mkdir_p (Config.results_dir ^/ Config.captured_dir_name) ; + Unix.mkdir_p (Config.results_dir ^/ Config.specs_dir_name); + L.setup_log_file () let assert_results_dir advice = if Sys.file_exists Config.results_dir <> `Yes then ( @@ -597,10 +58,12 @@ let setup_results_dir () = match Config.command with | Analyze -> assert_results_dir "have you run capture before?" | Clang | Report | ReportDiff -> create_results_dir () - | Capture | Compile | Run -> - (* These have their own logic depending on the rest of the command line. It particular they - might delete the previous results directory. Let them do their thing. *) - () + | Capture | Compile | Run -> + let driver_mode = Lazy.force Driver.mode_from_command_line in + if not (Driver.(equal_driver_mode driver_mode Analyze) || + Config.(buck || continue_capture || maven || reactive_mode)) then + remove_results_dir (); + create_results_dir () let () = if Config.print_builtins then Builtin.print_and_exit (); @@ -614,7 +77,7 @@ let () = | Some cluster -> F.fprintf fmt "of cluster %s" (Filename.basename cluster) in L.environment_info "Starting analysis %a" pp_cluster_opt Config.cluster_cmdline; InferAnalyze.register_perf_stats_report (); - analyze Analyze ~changed_files:(read_config_changed_files ()) + Driver.analyze_and_report Analyze ~changed_files:(read_config_changed_files ()) | Clang -> let prog, args = match Array.to_list Sys.argv with | prog::args -> prog, args @@ -623,6 +86,12 @@ let () = | Report -> InferPrint.main_from_config () | ReportDiff -> - differential_mode () + (* at least one report must be passed in input to compute differential *) + (match Config.report_current, Config.report_previous with + | None, None -> + failwith "Expected at least one argument among 'report-current' and 'report-previous'\n" + | _ -> ()); + ReportDiff.reportdiff ~current_report:Config.report_current + ~previous_report:Config.report_previous | Capture | Compile | Run -> - infer_mode () + run (Lazy.force Driver.mode_from_command_line) diff --git a/infer/src/base/Utils.ml b/infer/src/base/Utils.ml index 880c39853..945dd33cb 100644 --- a/infer/src/base/Utils.ml +++ b/infer/src/base/Utils.ml @@ -315,3 +315,25 @@ let write_file_with_locking ?(delete=false) ~f:do_write fname = if delete then try Unix.unlink fname with | Unix.Unix_error _ -> () + +let rec rmtree name = + match Unix.((lstat name).st_kind) with + | S_DIR -> + let dir = Unix.opendir name in + let rec rmdir dir = + match Unix.readdir dir with + | entry -> + if not (String.equal entry Filename.current_dir_name || + String.equal entry Filename.parent_dir_name) + then ( + rmtree (name ^/ entry) + ); + rmdir dir + | exception End_of_file -> + Unix.closedir dir ; + Unix.rmdir name in + rmdir dir + | _ -> + Unix.unlink name + | exception Unix.Unix_error (Unix.ENOENT, _, _) -> + () diff --git a/infer/src/base/Utils.mli b/infer/src/base/Utils.mli index 0643c79fc..dac00cc7f 100644 --- a/infer/src/base/Utils.mli +++ b/infer/src/base/Utils.mli @@ -89,3 +89,6 @@ val compare_versions : string -> string -> int (** Lock file passed as argument and write into it using [f]. If [delete] then the file is unlinked once this is done. *) val write_file_with_locking : ?delete:bool -> f:(out_channel -> unit) -> string -> unit + +(** [rmtree path] removes [path] and, if [path] is a directory, recursively removes its contents *) +val rmtree : string -> unit diff --git a/infer/src/integration/Driver.ml b/infer/src/integration/Driver.ml new file mode 100644 index 000000000..ab568929a --- /dev/null +++ b/infer/src/integration/Driver.ml @@ -0,0 +1,518 @@ +(* + * Copyright (c) 2017 - 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. + *) +open! IStd +open! PVariant + +(** entry points for top-level functionalities such as capture, analysis, and reporting *) + +module CLOpt = CommandLineOption +module L = Logging +module F = Format + +type build_system = + | BAnalyze | BAnt | BBuck | BClang | BGradle | BJava | BJavac | BMake | BMvn + | BNdk | BXcode +[@@deriving compare] + +let equal_build_system = [%compare.equal : build_system] + +(* List of ([build system], [executable name]). Several executables may map to the same build + system. In that case, the first one in the list will be used for printing, eg, in which mode + infer is running. *) +let build_system_exe_assoc = [ + BAnalyze, "analyze"; BAnt, "ant"; BBuck, "buck"; BGradle, "gradle"; BGradle, "gradlew"; + BJava, "java"; BJavac, "javac"; + BClang, "cc"; BClang, "clang"; BClang, "gcc"; BClang, "clang++"; BClang, "c++"; BClang, "g++"; + BMake, "make"; BMake, "configure"; BMake, "cmake"; BMake, "waf"; + BMvn, "mvn"; BMvn, "mvnw"; BNdk, "ndk-build"; BXcode, "xcodebuild"; +] + +let build_system_of_exe_name name = + try + List.Assoc.find_exn (List.Assoc.inverse build_system_exe_assoc) name + with Not_found -> + invalid_argf "Unsupported build command %s" name + +let string_of_build_system build_system = + List.Assoc.find_exn build_system_exe_assoc build_system + +(* based on the build_system and options passed to infer, we run in different driver modes *) +type mode = + | Analyze + | BuckGenrule of string + | BuckCompilationDB of string * string list + | Clang of Clang.compiler * string * string list + | ClangCompilationDB of [ `Escaped of string | `Raw of string ] list + | Javac of Javac.compiler * string * string list + | Maven of string * string list + | PythonCapture of build_system * string list + | XcodeXcpretty of string * string list +[@@deriving compare] + +let equal_driver_mode = [%compare.equal : mode] + +let pp_driver_mode fmt driver_mode = + let log_argfile_arg fname = + try + F.fprintf fmt "-- Contents of '%s'@\n" fname; + In_channel.iter_lines ~f:(F.fprintf fmt "%s@\n") (In_channel.create fname); + F.fprintf fmt "-- /Contents of '%s'@." fname; + with exn -> + F.fprintf fmt " Error reading file '%s':@\n %a@." fname Exn.pp exn in + match driver_mode with + | Analyze | BuckGenrule _ | BuckCompilationDB _ | ClangCompilationDB _ | PythonCapture (_,_) + | XcodeXcpretty _ -> + (* these are pretty boring, do not log anything *) + () + | Javac (_, prog, args) -> + F.fprintf fmt "Javac driver mode:@\nprog = %s@\n" prog; + let log_arg arg = + F.fprintf fmt "Arg: %s@\n" arg; + (* "@fname" means that fname is an arg file containing additional arguments to pass to + javac. *) + String.chop_prefix ~prefix:"@" arg + |> + (* Sometimes these argfiles go away at the end of the build and we cannot inspect them after + the fact, so log them now. *) + Option.iter ~f:log_argfile_arg in + List.iter ~f:log_arg args + | Maven (prog, args) -> + F.fprintf fmt "Maven driver mode:@\nprog = %s@\n" prog; + List.iter ~f:(F.fprintf fmt "Arg: %s@\n") args + | Clang (_, prog, args) -> + 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 + | BuckCompilationDB (prog, _) + | Clang (_, prog, _) -> + Some (prog ^ " clean") + | XcodeXcpretty (prog, args) -> + Some (String.concat ~sep:" " (List.append (prog::args) ["clean"])) + | _ -> None + +(* Clean up the results dir to select only what's relevant to go in the Buck cache. In particular, + get rid of non-deterministic outputs.*) +let clean_results_dir () = + (* In Buck flavors mode we keep all capture data, but in Java mode we keep only the tenv *) + let should_delete_dir = + let dirs_to_delete = + "backend_stats" :: "classnames" :: "filelists" :: "frontend_stats" :: "multicore" :: + "reporting_stats" :: "sources" :: + if Config.flavors then [] + else ["attributes"] in + List.mem ~equal:String.equal dirs_to_delete in + let should_delete_file = + let suffixes_to_delete = + ".txt" :: ".csv" :: ".json" :: + if Config.flavors then [] + else [ ".cfg"; ".cg" ] in + fun name -> + (* Keep the JSON report *) + not (String.equal (Filename.basename name) "report.json") + && List.exists ~f:(Filename.check_suffix name) suffixes_to_delete in + let rec clean name = + let rec cleandir dir = match Unix.readdir dir with + | entry -> + if should_delete_dir entry then ( + Utils.rmtree (name ^/ entry) + ) else if not (String.equal entry Filename.current_dir_name + || String.equal entry Filename.parent_dir_name) then ( + clean (name ^/ entry) + ); + cleandir dir (* next entry *) + | exception End_of_file -> + Unix.closedir dir in + match Unix.opendir name with + | dir -> + cleandir dir + | exception Unix.Unix_error (Unix.ENOTDIR, _, _) -> + if should_delete_file name then + Unix.unlink name; + () + | exception Unix.Unix_error (Unix.ENOENT, _, _) -> + () 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 -> + L.user_warning "@\nNothing to compile. Try running `%s` first.@." clean_command + | None -> + L.user_warning "@\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 + let stats_base = Config.perf_stats_prefix ^ ".json" in + let stats_file = Filename.concat stats_dir stats_base in + PerfStats.register_report_at_exit stats_file + +let reset_duplicates_file () = + let start = Config.results_dir ^/ Config.duplicates_filename in + let delete () = + Unix.unlink start in + let create () = + Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in + if Sys.file_exists start = `Yes then delete (); + create () + +(* Create the .start file, and update the timestamp unless in continue mode *) +let touch_start_file_unless_continue () = + let start = Config.results_dir ^/ Config.start_filename in + let delete () = + Unix.unlink start in + let create () = + Unix.close (Unix.openfile ~perm:0o0666 ~mode:[Unix.O_CREAT; Unix.O_WRONLY] start) in + if not (Sys.file_exists start = `Yes) then create () + else if not Config.continue_capture then (delete (); create ()) + + +let run_command ~prog ~args cleanup = + Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ()) + |> fun status + -> cleanup status + ; ok_exn (Unix.Exit_or_signal.or_error status) + +let check_xcpretty () = + match Unix.system "xcpretty --version" with + | Ok () -> () + | Error _ -> + L.user_error + "@\nxcpretty not found in the path. Please consider installing xcpretty \ + for a more robust integration with xcodebuild. Otherwise use the option \ + --no-xcpretty.@\n@." + +let capture_with_compilation_database db_files = + let root = Unix.getcwd () in + Config.clang_compilation_dbs := List.map db_files ~f:(function + | `Escaped fname -> `Escaped (Utils.filename_to_absolute ~root fname) + | `Raw fname -> `Raw (Utils.filename_to_absolute ~root fname) + ); + let compilation_database = CompilationDatabase.from_json_files db_files in + CaptureCompilationDatabase.capture_files_in_database compilation_database + +let capture ~changed_files = function + | Analyze -> + () + | BuckCompilationDB (prog, args) -> + L.progress "Capturing using Buck's compilation database...@."; + let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck ~prog ~args in + capture_with_compilation_database ~changed_files json_cdb + | BuckGenrule path -> + L.progress "Capturing for Buck genrule compatibility...@."; + JMain.from_arguments path + | Clang (compiler, prog, args) -> + L.progress "Capturing in make/cc mode...@."; + Clang.capture compiler ~prog ~args + | ClangCompilationDB db_files -> + L.progress "Capturing using compilation database...@."; + capture_with_compilation_database ~changed_files db_files + | Javac (compiler, prog, args) -> + L.progress "Capturing in javac mode...@."; + Javac.capture compiler ~prog ~args + | Maven (prog, args) -> + L.progress "Capturing in maven mode...@."; + Maven.capture ~prog ~args + | PythonCapture (build_system, build_cmd) -> + L.progress "Capturing in %s mode...@." (string_of_build_system build_system); + let in_buck_mode = equal_build_system build_system BBuck in + let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in + let args = + List.rev_append Config.anon_args ( + ["--analyzer"; + List.Assoc.find_exn ~equal:Config.equal_analyzer + (List.map ~f:(fun (n,a) -> (a,n)) Config.string_to_analyzer) Config.analyzer] @ + (match Config.blacklist with + | Some s when in_buck_mode -> ["--blacklist-regex"; s] + | _ -> []) @ + (if not Config.create_harness then [] else + ["--android-harness"]) @ + (match Config.java_jar_compiler with None -> [] | Some p -> + ["--java-jar-compiler"; p]) @ + (match List.rev Config.buck_build_args with + | args when in_buck_mode -> + List.map ~f:(fun arg -> ["--Xbuck"; "'" ^ arg ^ "'"]) args |> List.concat + | _ -> []) @ + (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"]) @ + "-j" :: (string_of_int Config.jobs) :: + (match Config.load_average with None -> [] | Some l -> + ["-l"; string_of_float l]) @ + (if not Config.pmd_xml then [] else + ["--pmd-xml"]) @ + ["--project-root"; Config.project_root] @ + (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 in_buck_mode && Config.flavors then + (* let children infer processes know that they are inside Buck *) + let infer_args_with_buck = String.concat ~sep:(String.of_char CLOpt.env_var_sep) + (Option.to_list (Sys.getenv CLOpt.args_env_var) @ ["--buck"]) in + Unix.putenv ~key:CLOpt.args_env_var ~data:infer_args_with_buck; + Buck.add_flavors_to_buck_command build_cmd + else build_cmd + ) in + run_command ~prog:infer_py ~args + (function + | Result.Error (`Exit_non_zero exit_code) -> + if Int.equal exit_code Config.infer_py_argparse_error_exit_code then + (* swallow infer.py argument parsing error *) + Config.print_usage_exit () + | _ -> + () + ) + | XcodeXcpretty (prog, args) -> + L.progress "Capturing using xcodebuild and xcpretty...@."; + check_xcpretty (); + let json_cdb = + CaptureCompilationDatabase.get_compilation_database_files_xcodebuild ~prog ~args in + capture_with_compilation_database ~changed_files json_cdb + +let run_parallel_analysis ~changed_files = + let multicore_dir = Config.results_dir ^/ Config.multicore_dir_name in + Utils.rmtree multicore_dir ; + Unix.mkdir_p multicore_dir ; + InferAnalyze.main ~changed_files ~makefile:(multicore_dir ^/ "Makefile") ; + run_command + ~prog:"make" ~args:( + "-C" :: multicore_dir :: + "-k" :: + "-j" :: (string_of_int Config.jobs) :: + (Option.value_map ~f:(fun l -> ["-l"; string_of_float l]) ~default:[] Config.load_average) @ + (if Config.debug_mode then [] else ["-s"]) + ) (fun _ -> ()) + +let execute_analyze ~changed_files = + if Int.equal Config.jobs 1 || Config.cluster_cmdline <> None then + InferAnalyze.main ~changed_files ~makefile:"" + else + run_parallel_analysis ~changed_files + +let report () = + let report_csv = + if Config.buck_cache_mode then None else Some (Config.results_dir ^/ "report.csv") in + let report_json = Some (Config.results_dir ^/ "report.json") in + InferPrint.main ~report_csv ~report_json ; + (* Post-process the report according to the user config. By default, calls report.py to create a + human-readable report. + + Do not bother calling the report hook when called from within Buck or in quiet mode. *) + match Config.quiet || Config.buck_cache_mode, Config.report_hook with + | true, _ + | false, None -> + () + | false, Some prog -> + let if_some key opt args = match opt with None -> args | Some arg -> key :: arg :: args in + let if_true key opt args = if not opt then args else key :: args in + let args = + if_some "--issues-csv" report_csv @@ + if_some "--issues-json" report_json @@ + if_some "--issues-txt" Config.bugs_txt @@ + if_true "--pmd-xml" Config.pmd_xml [ + "--project-root"; Config.project_root; + "--results-dir"; Config.results_dir + ] in + if is_error (Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())) then + L.external_error + "** Error running the reporting script:@\n** %s %s@\n** See error above@." + prog (String.concat ~sep:" " args) + +let analyze_and_report ~changed_files driver_mode = + let should_analyze, should_report = match driver_mode, Config.analyzer with + | PythonCapture (BBuck, _), _ -> + (* 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. *) + false, false + | _ when Config.maven -> + (* Called from Maven, only do capture. *) + false, false + | _, (CaptureOnly | CompileOnly) -> + false, false + | _, (BiAbduction | Checkers | Crashcontext | Eradicate) -> + true, true + | _, Linters -> + false, true in + if (should_analyze || should_report) && + (((Sys.file_exists Config.captured_dir) <> `Yes) || + check_captured_empty driver_mode) then ( + L.user_error "There was nothing to analyze.@\n@." ; + ) else if should_analyze then + execute_analyze ~changed_files; + if should_report && Config.report then report () + +(** as the Config.fail_on_bug flag mandates, exit with error when an issue is reported *) +let fail_on_issue_epilogue () = + let issues_json = DB.Results_dir.(path_to_filename Abs_root ["report.json"]) + |> DB.filename_to_string in + match Utils.read_file issues_json with + | Ok lines -> + let issues = Jsonbug_j.report_of_string @@ String.concat ~sep:"" lines in + if issues <> [] then exit Config.fail_on_issue_exit_code + | Error error -> + L.internal_error "Failed to read report file '%s': %s@." issues_json error ; + () + +let log_infer_args driver_mode = + L.environment_info "INFER_ARGS = %s@\n" + (Option.value (Sys.getenv CLOpt.args_env_var) ~default:""); + List.iter ~f:(L.environment_info "anon arg: %s@\n") Config.anon_args; + List.iter ~f:(L.environment_info "rest arg: %s@\n") Config.rest; + L.environment_info "Project root = %s@\n" Config.project_root; + L.environment_info "CWD = %s@\n" (Sys.getcwd ()); + L.environment_info "Driver mode:@\n%a@." pp_driver_mode driver_mode + +let assert_supported_mode required_analyzer requested_mode_string = + let analyzer_enabled = match required_analyzer with + | `Clang -> Version.clang_enabled + | `Java -> Version.java_enabled + | `Xcode -> Version.clang_enabled && Version.xcode_enabled in + if not analyzer_enabled then + let analyzer_string = match required_analyzer with + | `Clang -> "clang" + | `Java -> "java" + | `Xcode -> "clang and xcode" in + failwithf + "Unsupported build mode: %s@\nInfer was built with %s analyzers disabled.@ Please rebuild \ + infer with %s enabled.@." + requested_mode_string analyzer_string analyzer_string + +let assert_supported_build_system build_system = match build_system with + | BAnt | BGradle | BJava | BJavac | BMvn -> + string_of_build_system build_system + |> assert_supported_mode `Java + | BClang | BMake | BNdk -> + string_of_build_system build_system + |> assert_supported_mode `Clang + | BXcode -> + string_of_build_system build_system + |> assert_supported_mode `Xcode + | BBuck -> + let (analyzer, build_string) = + if Config.flavors then + (`Clang, "buck with flavors") + else if Option.is_some Config.buck_compilation_database then + (`Clang, "buck compilation database") + else ( + if Config.reactive_mode then + L.user_error + "WARNING: The reactive analysis mode is not compatible with the Buck integration for \ + Java"; + (`Java, string_of_build_system build_system) + ) in + assert_supported_mode analyzer build_string + | BAnalyze -> + () + +let driver_mode_of_build_cmd build_cmd = + match build_cmd with + | [] -> + if not (List.is_empty !Config.clang_compilation_dbs) then ( + assert_supported_mode `Clang "clang compilation database"; + ClangCompilationDB !Config.clang_compilation_dbs + ) else + Analyze + | prog :: args -> + let build_system = build_system_of_exe_name (Filename.basename prog) in + assert_supported_build_system build_system; + match build_system_of_exe_name (Filename.basename prog) with + | BAnalyze -> + CLOpt.warnf + "WARNING: `infer -- analyze` is deprecated; \ + use the `infer analyze` subcommand instead@."; + Analyze + | BBuck when Option.is_some Config.buck_compilation_database -> + BuckCompilationDB (prog, List.append args (List.rev Config.buck_build_args)) + | BClang -> + Clang (Clang.Clang, prog, args) + | BMake -> + Clang (Clang.Make, prog, args) + | BJava -> + Javac (Javac.Java, prog, args) + | BJavac -> + Javac (Javac.Javac, prog, args) + | BMvn -> + Maven (prog, args) + | BXcode when Config.xcpretty -> + XcodeXcpretty (prog, args) + | BAnt | BBuck | BGradle | BNdk | BXcode as build_system -> + PythonCapture (build_system, build_cmd) + +let get_driver_mode () = + match Config.generated_classes with + | _ when Config.maven -> + (* infer is pretending to be javac in the Maven integration *) + let build_args = match Array.to_list Sys.argv with + | _::args -> args + | [] -> [] in + Javac (Javac.Javac, "javac", build_args) + | Some path -> + assert_supported_mode `Java "Buck genrule"; + BuckGenrule path + | None -> + driver_mode_of_build_cmd (List.rev Config.rest) + +let mode_from_command_line = lazy ( + match Config.generated_classes with + | _ when Config.maven -> + (* infer is pretending to be javac in the Maven integration *) + let build_args = match Array.to_list Sys.argv with + | _::args -> args + | [] -> [] in + Javac (Javac.Javac, "javac", build_args) + | Some path -> + assert_supported_mode `Java "Buck genrule"; + BuckGenrule path + | None -> + driver_mode_of_build_cmd (List.rev Config.rest) +) + +let run_prologue driver_mode = + if CLOpt.is_originator then L.environment_info "%a@\n" Config.pp_version () ; + if Config.debug_mode || Config.stats_mode then log_infer_args driver_mode ; + if Config.dump_duplicate_symbols then reset_duplicates_file (); + (* infer might be called from a Makefile and itself uses `make` to run the analysis in parallel, + but cannot communicate with the parent make command. Since infer won't interfere with them + anyway, pretend that we are not called from another make to prevent make falling back to a + mono-threaded execution. *) + Unix.unsetenv "MAKEFLAGS"; + register_perf_stats_report () ; + if not Config.buck_cache_mode then touch_start_file_unless_continue () ; + () + +let run_epilogue driver_mode = + if CLOpt.is_originator then ( + let in_buck_mode = match driver_mode with | PythonCapture (BBuck, _) -> true | _ -> false in + StatsAggregator.generate_files () ; + if Config.equal_analyzer Config.analyzer Config.Crashcontext then + Crashcontext.crashcontext_epilogue ~in_buck_mode; + if Config.fail_on_bug then + fail_on_issue_epilogue () ; + ); + if Config.buck_cache_mode then clean_results_dir (); + () diff --git a/infer/src/integration/Driver.mli b/infer/src/integration/Driver.mli new file mode 100644 index 000000000..892a27c30 --- /dev/null +++ b/infer/src/integration/Driver.mli @@ -0,0 +1,44 @@ +(* + * Copyright (c) 2017 - 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. + *) +open! IStd + +(** entry points for top-level functionalities such as capture under various build systems, + analysis, and reporting *) + +type build_system + +(** based on the build_system and options passed to infer, we run in different driver modes *) +type mode = + | Analyze + | BuckGenrule of string + | BuckCompilationDB of string * string list + | Clang of Clang.compiler * string * string list + | ClangCompilationDB of [ `Escaped of string | `Raw of string ] list + | Javac of Javac.compiler * string * string list + | Maven of string * string list + | PythonCapture of build_system * string list + | XcodeXcpretty of string * string list +[@@deriving compare] + +val equal_driver_mode : mode -> mode -> bool + +(** driver mode computed from the command-line arguments and settings in Config *) +val mode_from_command_line : mode Lazy.t + +(** prepare the environment for running the given mode *) +val run_prologue : mode -> unit + +(** run the capture for the given mode *) +val capture : changed_files:SourceFile.Set.t option -> mode -> unit + +(** run the analysis for the given mode *) +val analyze_and_report : changed_files:SourceFile.Set.t option -> mode -> unit + +(** cleanup infer-out/ for Buck, generate stats, and generally post-process the results of a run *) +val run_epilogue : mode -> unit diff --git a/infer/src/integration/ReportDiff.ml b/infer/src/integration/ReportDiff.ml new file mode 100644 index 000000000..79be54431 --- /dev/null +++ b/infer/src/integration/ReportDiff.ml @@ -0,0 +1,36 @@ +(* + * Copyright (c) 2017 - 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. + *) +open! IStd + +let reportdiff ~current_report:current_report_fname ~previous_report:previous_report_fname = + let load_report filename_opt : Jsonbug_t.report = + let empty_report = [] in + Option.value_map + ~f:(fun filename -> Jsonbug_j.report_of_string (In_channel.read_all filename)) + ~default:empty_report filename_opt in + let current_report = load_report current_report_fname in + let previous_report = load_report previous_report_fname in + let diff = + let unfiltered_diff = Differential.of_reports ~current_report ~previous_report in + if Config.filtering then + let file_renamings = match Config.file_renamings with + | Some f -> DifferentialFilters.FileRenamings.from_json_file f + | None -> DifferentialFilters.FileRenamings.empty in + let interesting_paths = Option.map ~f:(fun fname -> + List.map ~f:(SourceFile.create ~warn_on_error:false) (In_channel.read_lines fname)) + Config.differential_filter_files in + DifferentialFilters.do_filter + unfiltered_diff + file_renamings + ~skip_duplicated_types:Config.skip_duplicated_types + ~interesting_paths + else unfiltered_diff in + let out_path = Config.results_dir ^/ "differential" in + Unix.mkdir_p out_path; + Differential.to_files diff out_path diff --git a/infer/src/integration/ReportDiff.mli b/infer/src/integration/ReportDiff.mli new file mode 100644 index 000000000..636a438a0 --- /dev/null +++ b/infer/src/integration/ReportDiff.mli @@ -0,0 +1,11 @@ +(* + * Copyright (c) 2017 - 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. + *) +open! IStd + +val reportdiff : current_report:string option -> previous_report:string option -> unit