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: caf9551master
							parent
							
								
									83148a71aa
								
							
						
					
					
						commit
						2f569e2b97
					
				| @ -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:"<not found>"); | ||||||
|  |   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 (); | ||||||
|  |   () | ||||||
| @ -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 | ||||||
| @ -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 | ||||||
| @ -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 | ||||||
					Loading…
					
					
				
		Reference in new issue