From e269f2a3fda69820c81777c389f05ca027b1fee9 Mon Sep 17 00:00:00 2001 From: Jeremy Dubreil Date: Tue, 29 Nov 2016 14:02:48 -0800 Subject: [PATCH] [infer][java] basic support to run Infer using Buck genrules Summary: Add some basic command line API to run Infer using Buck genrules. Remains to fix issues with absolute vs relative paths and to see how to create these genrules on the fly for a given java or android library. Reviewed By: sblackshear Differential Revision: D4245622 fbshipit-source-id: 1cda4ee --- infer/src/backend/infer.ml | 11 ++- infer/src/base/Config.ml | 35 ++++++- infer/src/base/Config.mli | 5 + infer/src/java/jClasspath.ml | 114 +++++++++++++++++----- infer/tests/codetoanalyze/java/infer/BUCK | 22 ++++- 5 files changed, 157 insertions(+), 30 deletions(-) diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index f9bdc34cb..6ee2bd3cc 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -39,7 +39,7 @@ let rec rmtree name = type build_mode = | Analyze | Ant | Buck | ClangCompilationDatabase | Gradle | Java | Javac | Make | Mvn | Ndk - | Xcode + | Xcode | Genrule let build_mode_of_string path = match Filename.basename path with @@ -47,6 +47,7 @@ let build_mode_of_string path = | "ant" -> Ant | "buck" -> Buck | "clang-compilation-database" -> ClangCompilationDatabase + | "genrule" -> Genrule | "gradle" | "gradlew" -> Gradle | "java" -> Java | "javac" -> Javac @@ -61,6 +62,7 @@ let string_of_build_mode = function | Ant -> "ant" | Buck -> "buck" | ClangCompilationDatabase -> "clang-compilation-database" + | Genrule -> "genrule" | Gradle -> "gradle" | Java -> "java" | Javac -> "javac" @@ -69,6 +71,7 @@ let string_of_build_mode = function | Ndk -> "ndk-build" | Xcode -> "xcodebuild" + let remove_results_dir () = rmtree Config.results_dir @@ -174,6 +177,10 @@ let capture build_cmd = function check_xcpretty (); let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_xcodebuild () in capture_with_compilation_database json_cdb + | Genrule -> + L.stdout "Capturing for Buck genrule compatibility...@\n"; + let infer_java = Config.bin_dir // "InferJava" in + run_command [infer_java] (function _ -> ()) | build_mode -> L.stdout "Capturing in %s mode...@." (string_of_build_mode build_mode); let in_buck_mode = build_mode = Buck in @@ -279,7 +286,7 @@ let analyze = function | Java | Javac -> (* In Java and Javac modes, analysis is invoked from capture. *) () - | Analyze | Ant | Buck | ClangCompilationDatabase | Gradle | Make | Mvn | Ndk | Xcode -> + | Analyze | Ant | Buck | ClangCompilationDatabase | Genrule | Gradle | Make | Mvn | Ndk | Xcode -> if not (Sys.file_exists Config.(results_dir // captured_dir_name)) then ( L.stderr "There was nothing to analyze, exiting" ; Config.print_usage_exit () diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 3d079b013..65942b31c 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -595,6 +595,11 @@ and blacklist = ~meta:"regex" "Skip analysis of files matched by the specified regular expression (Buck \ flavors only)" +and bootclasspath = + CLOpt.mk_string_opt ~long:"bootclasspath" + ~exes:CLOpt.[Toplevel; Java] + "Specify the Java bootclasspath" + (** Automatically set when running from within Buck *) and buck = CLOpt.mk_bool ~long:"buck" @@ -703,6 +708,11 @@ and clang_include_to_override = location of internal compiler headers. This option should specify the path to those headers \ so that infer can use its own clang internal headers instead." +and classpath = + CLOpt.mk_string_opt ~long:"classpath" + ~exes:CLOpt.[Java] + "Specify the Java classpath" + and cluster = CLOpt.mk_path_opt ~deprecated:["cluster"] ~long:"cluster" ~meta:"file" "Specify a .cluster file to be analyzed" @@ -931,6 +941,11 @@ and frontend_tests = ~exes:CLOpt.frontend_exes "Save filename.ext.test.dot with the cfg in dotty format for frontend tests" +and generated_classes = + CLOpt.mk_path_opt ~long:"generated-classes" + ~exes:CLOpt.[Toplevel; Java] + "Specify where to load the generated class files" + and headers = CLOpt.mk_bool ~deprecated:["headers"] ~deprecated_no:["no_headers"] ~long:"headers" ~short:"hd" ~exes:CLOpt.[Clang] @@ -1156,6 +1171,16 @@ and skip_translation_headers = ~exes:CLOpt.[Clang] ~meta:"path prefix" "Ignore headers whose path matches the given prefix" +and sources = + CLOpt.mk_string_list ~long:"sources" + ~exes:CLOpt.[Java] + "Specify the list of source files" + +and sourcepath = + CLOpt.mk_string_opt ~long:"sourcepath" + ~exes:CLOpt.[Java] + "Specify the sourcepath" + and spec_abs_level = CLOpt.mk_int ~deprecated:["spec_abs_level"] ~long:"spec-abs-level" ~default:1 ~meta:"int" "Set the level of abstracting the postconditions of discovered specs:\n\ @@ -1418,12 +1443,14 @@ and angelic_execution = !angelic_execution and array_level = !array_level and ast_file = !ast_file and blacklist = !blacklist +and bootclasspath = !bootclasspath and buck = !buck and buck_build_args = !buck_build_args and buck_out = !buck_out and bugs_csv = !bugs_csv and bugs_json = !bugs_json and frontend_tests = !frontend_tests +and generated_classes = !generated_classes and bugs_tests = !bugs_tests and bugs_txt = !bugs_txt and bugs_xml = !bugs_xml @@ -1434,6 +1461,7 @@ and checkers = !checkers and checkers_repeated_calls = !checkers_repeated_calls and clang_biniou_file = !clang_biniou_file and clang_include_to_override = !clang_include_to_override +and classpath = !classpath and cluster_cmdline = !cluster and compute_analytics = !compute_analytics and continue_capture = !continue @@ -1525,6 +1553,8 @@ and show_progress_bar = !progress_bar and skip_analysis_in_path = !skip_analysis_in_path and skip_clang_analysis_in_path = !skip_clang_analysis_in_path and skip_translation_headers = !skip_translation_headers +and sources = !sources +and sourcepath = !sourcepath and spec_abs_level = !spec_abs_level and stacktrace = !stacktrace and stacktraces_dir = !stacktraces_dir @@ -1602,9 +1632,10 @@ let patterns_suppress_warnings = | `Null -> [] | json -> patterns_of_json_with_key json_key json) | Error msg -> error ("Could not read or parse the supplied " ^ path ^ ":\n" ^ msg)) + | None when CLOpt.(current_exe <> Java) -> [] + | None when Option.is_some generated_classes -> [] | None -> - if CLOpt.(current_exe <> Java) then [] - else error ("Error: The option " ^ suppress_warnings_annotations_long ^ " was not provided") + error ("Error: The option " ^ suppress_warnings_annotations_long ^ " was not provided") let specs_library = match infer_cache with diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 07441a002..7a32809ed 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -61,6 +61,7 @@ val buck_infer_deps_file_name : string val captured_dir_name : string val checks_disabled_by_default : string list val clang_initializer_prefix : string +val classpath : string option val cpp_extra_include_dir : string val relative_cpp_models_dir : string val csl_analysis : bool @@ -113,6 +114,8 @@ val save_compact_summaries : bool val save_time_in_summaries : bool val smt_output : bool val source_file_extentions : string list +val sources : string list +val sourcepath : string option val specs_dir_name : string val specs_files_suffix : string val start_filename : string @@ -143,6 +146,7 @@ val angelic_execution : bool val array_level : int val ast_file : string option val blacklist : string option +val bootclasspath : string option val buck : bool val buck_build_args : string list val buck_out : string option @@ -199,6 +203,7 @@ val from_json_report : string option val frontend_debug : bool val frontend_tests : bool val frontend_stats : bool +val generated_classes : string option val headers : bool val icfg_dotty_outfile : string option val infer_cache : string option diff --git a/infer/src/java/jClasspath.ml b/infer/src/java/jClasspath.ml index 60b67dc5f..93d698d5a 100644 --- a/infer/src/java/jClasspath.ml +++ b/infer/src/java/jClasspath.ml @@ -92,7 +92,6 @@ type file_entry = | Singleton of DB.source_file | Duplicate of (string * DB.source_file) list - (* Open the source file and search for the package declaration. Only the case where the package is declared in a single line is supported *) let read_package_declaration source_file = @@ -146,7 +145,7 @@ let add_source_file path map = StringMap.add basename entry map -let load_sources_and_classes () = +let load_from_verbose_output () = let file_in = open_in Config.javac_verbose_out in let class_filename_re = Str.regexp @@ -188,6 +187,94 @@ let load_sources_and_classes () = loop [] [] StringMap.empty JBasics.ClassSet.empty +let classname_of_class_filename class_filename = + let parts = Str.split (Str.regexp "/") class_filename in + let classname_str = + if IList.length parts > 1 then + IList.fold_left (fun s p -> s^"."^p) (IList.hd parts) (IList.tl parts) + else + IList.hd parts in + JBasics.make_cn classname_str + + +let extract_classnames classnames jar_filename = + let file_in = Zip.open_in jar_filename in + let collect classes entry = + let class_filename = entry.Zip.filename in + try + let () = ignore (Str.search_forward (Str.regexp "class") class_filename 0) in + (classname_of_class_filename (Filename.chop_extension class_filename):: classes) + with Not_found -> classes in + let classnames_after = IList.fold_left collect classnames (Zip.entries file_in) in + Zip.close_in file_in; + classnames_after + + +let collect_classnames start_classmap jar_filename = + let classpath = Javalib.class_path jar_filename in + let classmap = + IList.fold_left + (fun map cn -> JBasics.ClassSet.add cn map) + start_classmap + (extract_classnames [] jar_filename) in + Javalib.close_class_path classpath; + classmap + + +let search_classes path = + let add_class roots classes class_filename = + let cn, root_dir = + Javalib.extract_class_name_from_file class_filename in + let updated_roots = + if IList.exists (fun p -> p = root_dir) roots then roots + else root_dir:: roots in + (updated_roots, JBasics.ClassSet.add cn classes) in + directory_fold + (fun accu p -> + let paths, classes = accu in + if Filename.check_suffix p "class" then + add_class paths classes p + else if Filename.check_suffix p "jar" then + (p :: paths, collect_classnames classes p) + else accu) + ([], JBasics.ClassSet.empty) + path + + +let search_sources () = + let initial_map = + IList.fold_left + (fun map path -> add_source_file path map) + StringMap.empty + Config.sources in + match Config.sourcepath with + | None -> initial_map + | Some sourcepath -> + directory_fold + (fun map p -> + if Filename.check_suffix p "java" + then add_source_file p map + else map) + initial_map + sourcepath + + +let load_from_arguments classes_out_path = + let roots, classes = search_classes classes_out_path in + let sources = search_sources () in + let split cp_option = + Option.map_default split_classpath [] cp_option in + let paths = + (split Config.bootclasspath) @ roots @ (split Config.classpath) in + let classpath = IList.fold_left append_path "" paths in + (classpath, sources, classes) + + +let load_sources_and_classes () = + match Config.generated_classes with + | None -> load_from_verbose_output () + | Some path -> load_from_arguments path + type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t @@ -230,29 +317,6 @@ let lookup_node cn program = | Invalid_argument _ -> None -let classname_of_class_filename class_filename = - let parts = Str.split (Str.regexp "/") class_filename in - let classname_str = - if IList.length parts > 1 then - IList.fold_left (fun s p -> s^"."^p) (IList.hd parts) (IList.tl parts) - else - IList.hd parts in - JBasics.make_cn classname_str - - -let extract_classnames classnames jar_filename = - let file_in = Zip.open_in jar_filename in - let collect classes entry = - let class_filename = entry.Zip.filename in - try - let () = ignore (Str.search_forward (Str.regexp "class") class_filename 0) in - (classname_of_class_filename (Filename.chop_extension class_filename):: classes) - with Not_found -> classes in - let classnames_after = IList.fold_left collect classnames (Zip.entries file_in) in - Zip.close_in file_in; - classnames_after - - let collect_classes start_classmap jar_filename = let classpath = Javalib.class_path jar_filename in let collect classmap cn = diff --git a/infer/tests/codetoanalyze/java/infer/BUCK b/infer/tests/codetoanalyze/java/infer/BUCK index effb93ac0..c62ffaa0c 100644 --- a/infer/tests/codetoanalyze/java/infer/BUCK +++ b/infer/tests/codetoanalyze/java/infer/BUCK @@ -1,8 +1,10 @@ # TODO: this file exists only to support buck integration in infer/tests/build_systems/build_integration_tests.py +sources = glob(['**/*.java']) + java_library( name = 'compile', - srcs = glob(['**/*.java']), + srcs = sources, deps = [ '//dependencies/java/guava:guava', '//dependencies/java/jsr-305:jsr-305', @@ -15,3 +17,21 @@ java_library( 'PUBLIC' ] ) + +genrule( + name = 'run_infer', + srcs = sources, + out = 'infer-out', + bash = ' '.join([ + 'infer', + '--sourcepath', + '$SRCDIR', + '--classpath', + '$(classpath :compile)', + '--generated-classes', + '$(location :compile)', + '--out', + '$OUT', + '--', + 'genrule']), +)