From 2e96caac42cf8cef7e666729f7a2903c13c1ef2b Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Thu, 19 Jan 2017 04:06:39 -0800 Subject: [PATCH] [mvn] new integration Summary: This replaces the previous integration written in Python, which consisted in 1) run the mvn command and parse its output to locate "directories containing source files", 2) run on files named "*.java" in these directories. This meant we had to run javac twice on each source file, and more importantly this mechanism of finding source files was very fragile. In fact, I could not make it work on several mvn projects I tried. The new integration is based on parsing "pom.xml" to add an "infer-capture" profile which instructs mvn to run `/path/to/infer` instead of `javac`. We also add this profile to each maven submodule. Users can specify an "infer-capture" profile themselves if the default one doesn't work; in that case we don't inject our own "infer-capture" profile. Reviewed By: jeremydubreil Differential Revision: D4409613 fbshipit-source-id: d664274 --- .gitignore | 1 + Makefile | 3 + Makefile.autoconf.in | 1 + configure.ac | 4 + infer/lib/python/infer.py | 1 - infer/lib/python/inferlib/capture/mvn.py | 87 ---------- infer/src/backend/infer.ml | 24 ++- infer/src/base/CommandLineOption.ml | 6 +- infer/src/base/CommandLineOption.mli | 5 +- infer/src/base/Config.ml | 16 +- infer/src/base/Config.mli | 3 + infer/src/integration/Maven.ml | 158 ++++++++++++++++++ infer/src/integration/Maven.mli | 9 + .../mvn/app_with_infer_profile/pom.xml | 28 ++++ .../mvn/app_with_infer_profile/src | 1 + .../mvn/app_with_profiles/pom.xml | 22 +++ .../codetoanalyze/mvn/app_with_profiles/src | 1 + .../mvn/app_with_submodules/module1/pom.xml | 9 + .../mvn/app_with_submodules/module1/src | 1 + .../mvn/app_with_submodules/module2/pom.xml | 16 ++ .../src/main/java/com/mycompany/Hello2.java | 62 +++++++ .../mvn/app_with_submodules/pom.xml | 14 ++ .../codetoanalyze/mvn/simple_app/pom.xml | 9 + .../src/main/java/com/mycompany/Hello.java | 59 +++++++ .../src/main/java/com/mycompany/Pointers.java | 26 +++ .../main/java/com/mycompany/Resources.java | 27 +++ infer/tests/build_systems/mvn/Makefile | 55 ++++++ infer/tests/build_systems/mvn/issues.exp | 19 +++ scripts/toplevel_init | 1 + 29 files changed, 570 insertions(+), 98 deletions(-) delete mode 100644 infer/lib/python/inferlib/capture/mvn.py create mode 100644 infer/src/integration/Maven.ml create mode 100644 infer/src/integration/Maven.mli create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/pom.xml create mode 120000 infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/src create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/pom.xml create mode 120000 infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/src create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/pom.xml create mode 120000 infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/src create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/pom.xml create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/pom.xml create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/simple_app/pom.xml create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Pointers.java create mode 100644 infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Resources.java create mode 100644 infer/tests/build_systems/mvn/Makefile create mode 100644 infer/tests/build_systems/mvn/issues.exp diff --git a/.gitignore b/.gitignore index 8b02f3514..5ee9cb408 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ duplicates.txt /infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/libs/ /infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/obj/ /infer/tests/build_systems/codetoanalyze/utf8_*n_pwd +/infer/tests/build_systems/codetoanalyze/mvn/**/target/ # generated by oUnit /oUnit-all.cache diff --git a/Makefile b/Makefile index 7fd81dce4..ed72416ac 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,9 @@ endif ifneq ($(CMAKE),no) BUILD_SYSTEMS_TESTS += clang_compilation_db cmake endif +ifneq ($(MVN),no) +BUILD_SYSTEMS_TESTS += mvn +endif ifneq ($(NDKBUILD),no) BUILD_SYSTEMS_TESTS += ndk_build endif diff --git a/Makefile.autoconf.in b/Makefile.autoconf.in index 53a57aa88..78bd5e8aa 100644 --- a/Makefile.autoconf.in +++ b/Makefile.autoconf.in @@ -40,6 +40,7 @@ MKDIR_P_CMD = case "@MKDIR_P@" in \ *) printf "@MKDIR_P@\n";; \ esac MKDIR_P = $(shell $(MKDIR_P_CMD)) +MVN = @MVN@ NCPU = @NCPU@ NDKBUILD = @NDKBUILD@ prefix = @prefix@ diff --git a/configure.ac b/configure.ac index 5900fd603..4862f80cc 100644 --- a/configure.ac +++ b/configure.ac @@ -273,6 +273,10 @@ if test ! -z "$SANDCASTLE"; then else AC_CHECK_TOOL([BUCK], [buck], [no]) fi +AC_ARG_VAR([MVN], [command to execute Maven when running tests]) +AS_IF([test "x$MVN" = "x"], [ + AC_CHECK_TOOL([MVN], [mvn], [no]) +]) AC_CHECK_TOOL([NDKBUILD], [ndk-build], [no]) if test x"$NDKBUILD" = x"no"; then # ndk-build not in $PATH, look into potential android NDK install paths and record the absolute path diff --git a/infer/lib/python/infer.py b/infer/lib/python/infer.py index 841cf4196..175154b90 100755 --- a/infer/lib/python/infer.py +++ b/infer/lib/python/infer.py @@ -39,7 +39,6 @@ MODULE_TO_COMMAND = { 'gradle': ['gradle', 'gradlew'], 'make': make.SUPPORTED_COMMANDS, 'xcodebuild': ['xcodebuild'], - 'mvn': ['mvn'], 'ndk-build': ['ndk-build'], } diff --git a/infer/lib/python/inferlib/capture/mvn.py b/infer/lib/python/inferlib/capture/mvn.py deleted file mode 100644 index e56e04a80..000000000 --- a/infer/lib/python/inferlib/capture/mvn.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) 2015 - 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. - -import os -import logging -import re -import util - -from inferlib import jwlib - -MODULE_NAME = __name__ -MODULE_DESCRIPTION = '''Run analysis of code built with a command like: -mvn [options] [task] - -Analysis examples: -infer -- mvn build''' -LANG = ['java'] - - -def gen_instance(*args): - return MavenCapture(*args) - - -# This creates an empty argparser for the module, which provides only -# description/usage information and no arguments. -create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) - - -class MavenCapture: - def __init__(self, args, cmd): - self.args = args - logging.info(util.run_cmd_ignore_fail(['mvn', '-version'])) - # TODO: make the extraction of targets smarter - self.build_cmd = ['mvn', '-X'] + cmd[1:] - - def get_infer_commands(self, verbose_output): - file_pattern = r'\[DEBUG\] Stale source detected: ([^ ]*\.java)' - options_pattern = '[DEBUG] Command line options:' - source_roots_pattern = '[DEBUG] Source roots:' - - files_to_compile = [] - calls = [] - options_next = False - source_roots_next = False - for line in verbose_output: - if options_next: - # line has format [Debug] - javac_args = line.split(' ')[1:] + files_to_compile - capture = jwlib.create_infer_command(javac_args) - calls.append(capture) - options_next = False - files_to_compile = [] - - elif source_roots_next: - # line has format [Debug] - src_roots = line.split(' ')[1:] - for src_root in src_roots: - for root, dirs, files in os.walk(src_root): - for name in files: - if name.endswith(".java"): - path = os.path.join(root, name) - files_to_compile.append(path) - source_roots_next = False - - elif options_pattern in line: - # Next line will have javac options to run - options_next = True - - elif source_roots_pattern in line: - # Next line will have directory containing files to compile - source_roots_next = True - - else: - found = re.match(file_pattern, line) - if found: - files_to_compile.append(found.group(1)) - - return calls - - def capture(self): - cmds = self.get_infer_commands(util.get_build_output(self.build_cmd)) - clean_cmd = '%s clean' % self.build_cmd[0] - return util.run_compilation_commands(cmds, clean_cmd) diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index bce13ccb4..b07cfba16 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -51,7 +51,7 @@ let build_system_exe_assoc = [ printing *) BMake, "make/cc"; BMake, "cc"; BMake, "clang"; BMake, "clang++"; BMake, "cmake"; BMake, "configure"; BMake, "g++"; BMake, "gcc"; BMake, "make"; BMake, "waf"; - BMvn, "mvn"; BNdk, "ndk-build"; BXcode, "xcodebuild"; + BMvn, "mvn"; BMvn, "mvnw"; BNdk, "ndk-build"; BXcode, "xcodebuild"; ] let build_system_of_exe_name name = @@ -70,6 +70,7 @@ type driver_mode = | BuckCompilationDB | ClangCompilationDB of string list | Javac of Javac.compiler * string * string list + | Maven of string * string list | PythonCapture of build_system * string list | XcodeXcpretty @@ -98,6 +99,9 @@ let pp_driver_mode fmt driver_mode = 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 let remove_results_dir () = rmtree Config.results_dir @@ -190,6 +194,9 @@ let capture = function | Javac (compiler, prog, args) -> L.stdout "Capturing in javac mode...@."; Javac.capture compiler ~prog ~args + | Maven (prog, args) -> + L.stdout "Capturing in maven mode...@."; + Maven.capture ~prog ~args | PythonCapture (build_system, build_cmd) -> L.stdout "Capturing in %s mode...@." (string_of_build_system build_system); let in_buck_mode = build_system = BBuck in @@ -296,6 +303,9 @@ let analyze driver_mode = (* 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 | _, (Capture | Compile) -> false, false | _, (Infer | Eradicate | Checkers | Tracing | Crashcontext | Quandary | Threadsafety) -> @@ -344,13 +354,21 @@ let driver_mode_of_build_cmd build_cmd = Javac (Javac.Java, prog, args) | BJavac -> Javac (Javac.Javac, prog, args) + | BMvn -> + Maven (prog, args) | BXcode when Config.xcpretty -> XcodeXcpretty - | BAnt | BBuck | BGradle | BMake | BMvn | BNdk | BXcode as build_system -> + | BAnt | BBuck | BGradle | BMake | 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 -> BuckGenrule path | None -> @@ -358,7 +376,7 @@ let get_driver_mode () = let () = let driver_mode = get_driver_mode () in - if not (driver_mode = Analyze || Config.(buck || continue_capture || reactive_mode)) then + if not (driver_mode = Analyze || Config.(buck || continue_capture || maven || reactive_mode)) then remove_results_dir () ; create_results_dir () ; (* re-set log files, as default files were in results_dir removed above *) diff --git a/infer/src/base/CommandLineOption.ml b/infer/src/base/CommandLineOption.ml index d38bd024c..6f42ee50d 100644 --- a/infer/src/base/CommandLineOption.ml +++ b/infer/src/base/CommandLineOption.ml @@ -576,7 +576,8 @@ let extra_env_args = ref [] let extend_env_args args = extra_env_args := List.rev_append args !extra_env_args -let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file current_exe exe_usage = +let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file current_exe exe_usage + ~should_parse_cl_args = let full_speclist = ref [] in let usage_msg = exe_usage current_exe @@ -678,9 +679,6 @@ let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file current_exe e ; let env_args = decode_env_to_argv (Option.value (Sys.getenv args_env_var) ~default:"") in let exe_name = Sys.executable_name in - let should_parse_cl_args = match current_exe with - | Clang | Interactive -> false - | Analyze | Print | Driver -> true in let env_cl_args = if should_parse_cl_args then prepend_to_argv env_args else env_args in diff --git a/infer/src/base/CommandLineOption.mli b/infer/src/base/CommandLineOption.mli index 6d9140032..76e363de3 100644 --- a/infer/src/base/CommandLineOption.mli +++ b/infer/src/base/CommandLineOption.mli @@ -152,4 +152,7 @@ val extend_env_args : string list -> unit and [env_var] is not set. If [accept_unknown] is set, unknown options are treated the same as anonymous arguments. *) val parse : ?incomplete:bool -> ?accept_unknown:bool -> ?config_file:string -> - exe -> (exe -> Arg.usage_msg) -> (int -> 'a) + exe -> (exe -> Arg.usage_msg) -> should_parse_cl_args:bool -> (int -> 'a) + +(** [is_env_var_set var] is true if $[var]=1 *) +val is_env_var_set : string -> bool diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 57348d97e..7038e3d37 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -307,9 +307,21 @@ let init_work_dir, is_originator = directory of the initial invocation of infer. *) let resolve = Utils.filename_to_absolute ~root:init_work_dir +let infer_inside_maven_env_var = "INFER_INSIDE_MAVEN" + +let maven = CLOpt.is_env_var_set infer_inside_maven_env_var + +let env_inside_maven = `Extend [infer_inside_maven_env_var, "1"] + (** Command Line options *) +let should_parse_cl_args = + (match current_exe with + | Clang | Interactive -> false + | Analyze | Driver | Print -> true) && + not maven + (* Declare the phase 1 options *) let inferconfig_home = @@ -325,7 +337,7 @@ and project_root = (* Parse the phase 1 options, ignoring the rest *) -let _ = CLOpt.parse ~incomplete:true current_exe (fun _ -> "") +let _ : int -> 'a = CLOpt.parse ~incomplete:true current_exe (fun _ -> "") ~should_parse_cl_args (* Define the values that depend on phase 1 options *) @@ -1346,7 +1358,7 @@ let post_parsing_initialization () = let parse_args_and_return_usage_exit = let usage_exit = - CLOpt.parse ~config_file:inferconfig_path current_exe exe_usage in + CLOpt.parse ~config_file:inferconfig_path current_exe exe_usage ~should_parse_cl_args in post_parsing_initialization () ; usage_exit diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 772e2ac62..3f06caea9 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -42,6 +42,8 @@ type dynamic_dispatch_policy = [ | `Lazy ] +val env_inside_maven : Unix.env + (** Constant configuration values *) val allow_missing_index_in_proc_call : bool @@ -215,6 +217,7 @@ val latex : string option val linters_def_file : string list val load_analysis_results : string option val makefile_cmdline : string +val maven : bool val merge : bool val ml_buckets : [ `MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown ] list diff --git a/infer/src/integration/Maven.ml b/infer/src/integration/Maven.ml new file mode 100644 index 000000000..6230606c0 --- /dev/null +++ b/infer/src/integration/Maven.ml @@ -0,0 +1,158 @@ +(* + * 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 + +module CLOpt = CommandLineOption +module L = Logging + +let infer_profile_name = "infer-capture" + +let infer_profile = lazy + (* indented so that users may copy it into their projects if they want to *) + (Printf.sprintf "\ +\n \ +\n %s\ +\n \ +\n \ +\n \ +\n org.apache.maven.plugins\ +\n maven-compiler-plugin\ +\n \ +\n javac\ +\n true\ +\n true\ +\n %s\ +\n \ +\n \ +\n \ +\n \ +\n \ + " infer_profile_name (Config.(bin_dir ^/ string_of_analyzer Infer))) + +let pom_worklist = ref [Config.init_work_dir] + +let add_infer_profile_to_xml maven_xml infer_xml = + let copy xml_in xml_out = Xmlm.output xml_out (Xmlm.input xml_in) in + (* whether we ever found a tag *) + let found_profiles_tag = ref false in + (* whether there already is an infer profile --this will always be true at the end *) + let found_infer_profile = ref false in + (* Process an xml document from the root. Assume the Dtd has already been handled. *) + let rec process_root xml_in xml_out = + process xml_in xml_out [] + and insert_infer_profile xml_out = + let infer_xml = Xmlm.make_input ~strip:false (`String (0, Lazy.force infer_profile)) in + Xmlm.input infer_xml |> ignore; (* skip dummy DTD *) + process_root infer_xml xml_out + and process xml_in xml_out tag_stack = + let elt_in = Xmlm.input xml_in in + match elt_in with + | `El_start tag -> + Xmlm.output xml_out elt_in; + let tag_name = snd (fst tag) in + if tag_name = "profiles" then ( + found_profiles_tag := true + ); + process xml_in xml_out (tag_name::tag_stack) + | `El_end -> + (match tag_stack with + | "profiles"::_ when not !found_infer_profile -> + (* found the tag but no infer profile found, add one *) + insert_infer_profile xml_out + | _::[] when not !found_profiles_tag-> + (* closing the root tag but no tag found, add + [infer profile] *) + Xmlm.output xml_out (`El_start (("", "profiles"), [])); + found_profiles_tag := true; (* do not add again *) + insert_infer_profile xml_out; + Xmlm.output xml_out `El_end + | _ -> () + ); + Xmlm.output xml_out elt_in; + (match tag_stack with + | _::parent::tl -> + process xml_in xml_out (parent::tl) + | _::[] -> + (* closing the first tag, we're done *) + () + | [] -> + invalid_arg "ill-formed xml") + | `Data data -> + Xmlm.output xml_out elt_in; + (match tag_stack with + | "id"::"profile"::"profiles"::_ when data = infer_profile_name -> + L.do_out "Found infer profile, not adding one@."; + found_infer_profile := true + | "module"::"modules"::_ -> + let abs_data = Config.init_work_dir ^/ data in + L.do_out "Adding maven module %s@." abs_data; + pom_worklist := abs_data::!pom_worklist + | _ -> () + ); + process xml_in xml_out tag_stack + | `Dtd _ -> + (* already processed the Dtd node *) + assert false in + let process_document () = + (* process `Dtd; if present, it is always the first node *) + (match Xmlm.peek maven_xml with + | `Dtd _ -> + copy maven_xml infer_xml + | _ -> + Xmlm.output infer_xml (`Dtd None) + ); + process_root maven_xml infer_xml; + Xmlm.eoi maven_xml |> ignore; + if not (Xmlm.eoi maven_xml) then invalid_arg "More than one document" in + process_document () + +let add_infer_profile mvn_pom infer_pom = + let ic = In_channel.create mvn_pom in + let with_oc out_chan = + let with_ic () = + let xml_in = Xmlm.make_input ~strip:false (`Channel ic) in + let xml_out = Xmlm.make_output ~nl:true (`Channel out_chan) in + add_infer_profile_to_xml xml_in xml_out in + protect ~f:with_ic ~finally:(fun () -> In_channel.close ic) in + Utils.with_file infer_pom ~f:with_oc + +let add_profile_to_pom_in_directory dir = + (* Even though there is a "-f" command-line arguments to change the config file Maven reads from, + this is unreliable and Maven pretty much always reads from "pom.xml" anyway. So, we replace + "pom.xml" with a version holding a special profile for infer capture, then put the original + back in place. *) + let maven_pom_path = dir ^/ "pom.xml" in + let saved_pom_path = dir ^/ "pom.xml.infer-orig" in + let infer_pom_path = dir ^/ "pom.xml.infer" in + add_infer_profile maven_pom_path infer_pom_path; + Unix.rename ~src:maven_pom_path ~dst:saved_pom_path; + Pervasives.at_exit (fun () -> Unix.rename ~src:saved_pom_path ~dst:maven_pom_path); + Unix.rename ~src:infer_pom_path ~dst:maven_pom_path; + if Config.debug_mode || Config.stats_mode then + Pervasives.at_exit (fun () -> Unix.rename ~src:maven_pom_path ~dst:infer_pom_path) + +let capture ~prog ~args = + while not (List.is_empty !pom_worklist); do + let pom = List.hd_exn !pom_worklist in + pom_worklist := List.tl_exn !pom_worklist; + add_profile_to_pom_in_directory pom + done; + let extra_args = "-P"::infer_profile_name::[] in + let capture_args = args @ extra_args in + L.do_out "Running maven capture:@\n%s %s@." prog + (String.concat ~sep:" " (List.map ~f:(Printf.sprintf "'%s'") capture_args)); + (* let children infer processes know that they are spawned by Maven *) + Unix.fork_exec ~prog ~args:(prog::capture_args) ~env:Config.env_inside_maven () + |> Unix.waitpid + |> function + | Ok () -> () + | Error _ as status -> + failwithf "*** ERROR: Maven command failed:@\n*** %s@\n*** %s@\n" + (String.concat ~sep:" " (prog::capture_args)) + (Unix.Exit_or_signal.to_string_hum status) diff --git a/infer/src/integration/Maven.mli b/infer/src/integration/Maven.mli new file mode 100644 index 000000000..720607aaf --- /dev/null +++ b/infer/src/integration/Maven.mli @@ -0,0 +1,9 @@ +(* + * 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. + *) +val capture : prog:string -> args:string list -> unit diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/pom.xml b/infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/pom.xml new file mode 100644 index 000000000..0709b1e90 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/pom.xml @@ -0,0 +1,28 @@ + + 4.0.0 + com.mycompany + app-with-infer-profile + jar + 1.0-SNAPSHOT + app-with-infer-profile + + + infer-capture + + + + org.apache.maven.plugins + maven-compiler-plugin + + javac + true + true + ${project.basedir}/../../../../../bin/infer + + + + + + + diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/src b/infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/src new file mode 120000 index 000000000..50aeb27aa --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_infer_profile/src @@ -0,0 +1 @@ +../simple_app/src \ No newline at end of file diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/pom.xml b/infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/pom.xml new file mode 100644 index 000000000..6f6f3c218 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + com.mycompany + app-with-profiles + jar + 1.0-SNAPSHOT + app-with-profiles + + + some-profile + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + + diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/src b/infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/src new file mode 120000 index 000000000..50aeb27aa --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_profiles/src @@ -0,0 +1 @@ +../simple_app/src \ No newline at end of file diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/pom.xml b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/pom.xml new file mode 100644 index 000000000..e50abdd26 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/pom.xml @@ -0,0 +1,9 @@ + + + 4.0.0 + com.mycompany + module1 + jar + 1.0-SNAPSHOT + module1 + diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/src b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/src new file mode 120000 index 000000000..4d02679cb --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module1/src @@ -0,0 +1 @@ +../../simple_app/src \ No newline at end of file diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/pom.xml b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/pom.xml new file mode 100644 index 000000000..63bca1b55 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + com.mycompany + module2 + jar + 1.0-SNAPSHOT + module2 + + + com.mycompany + module1 + 1.0-SNAPSHOT + + + diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java new file mode 100644 index 000000000..9f47e9468 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 - 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. + */ + +package hello2; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Random; + +import hello.Pointers; +import hello.Resources; + +class Hello2 { + + void doesNotCauseNPE2() { + Pointers.A a = Pointers.mayReturnNull(10); + a.method(); + } + + void mayCauseNPE2() { + Random rng = new Random(); + Pointers.A a = Pointers.mayReturnNull(rng.nextInt()); + // NPE + a.method(); + } + + void mayLeakResource2() throws IOException { + OutputStream stream = Resources.allocateResource(); + if (stream == null) { + return; + } + + try { + stream.write(12); + } finally { + // Resource leak + } + } + + void twoResources2() throws IOException { + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(new File("whatever.txt")); + fos = new FileOutputStream(new File("everwhat.txt")); + fos.write(fis.read()); + } finally { + if (fis != null) { fis.close(); } // Resource leak + if (fos != null) { fos.close(); } + } + } + +} diff --git a/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/pom.xml b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/pom.xml new file mode 100644 index 000000000..23d82e283 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/app_with_submodules/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + com.some.company.name + app-with-submodules + pom + 1.0-SNAPSHOT + my app with submodules + http://maven.apache.org + + module1 + module2 + + diff --git a/infer/tests/build_systems/codetoanalyze/mvn/simple_app/pom.xml b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/pom.xml new file mode 100644 index 000000000..0c096291c --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/pom.xml @@ -0,0 +1,9 @@ + + + 4.0.0 + com.mycompany + simple-app + jar + 1.0-SNAPSHOT + simple-app + diff --git a/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java new file mode 100644 index 000000000..0ae51fa3d --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 - 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. + */ + +package hello; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Random; + +class Hello { + + void doesNotCauseNPE() { + Pointers.A a = Pointers.mayReturnNull(10); + a.method(); + } + + void mayCauseNPE() { + Random rng = new Random(); + Pointers.A a = Pointers.mayReturnNull(rng.nextInt()); + // NPE + a.method(); + } + + void mayLeakResource() throws IOException { + OutputStream stream = Resources.allocateResource(); + if (stream == null) { + return; + } + + try { + stream.write(12); + } finally { + // Resource leak + } + } + + void twoResources() throws IOException { + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(new File("whatever.txt")); + fos = new FileOutputStream(new File("everwhat.txt")); + fos.write(fis.read()); + } finally { + if (fis != null) { fis.close(); } // Resource leak + if (fos != null) { fos.close(); } + } + } + +} diff --git a/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Pointers.java b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Pointers.java new file mode 100644 index 000000000..3d8efa90a --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Pointers.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 - 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. + */ + +package hello; + +public class Pointers { + + public static class A { + public void method() { + } + } + + public static A mayReturnNull(int i) { + if (i > 0) { + return new A(); + } + return null; + } + +} diff --git a/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Resources.java b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Resources.java new file mode 100644 index 000000000..c5f0b5234 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Resources.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015 - 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. + */ + +package hello; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class Resources { + + public static FileOutputStream allocateResource() { + try { + File file = new File("foo.txt"); + return new FileOutputStream(file); + } catch (IOException e) { + return null; + } + } + +} diff --git a/infer/tests/build_systems/mvn/Makefile b/infer/tests/build_systems/mvn/Makefile new file mode 100644 index 000000000..57d50047a --- /dev/null +++ b/infer/tests/build_systems/mvn/Makefile @@ -0,0 +1,55 @@ +# Copyright (c) 2016 - 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. + +TESTS_DIR = ../.. + +ANALYZER = infer +INFERPRINT_OPTIONS = --issues-tests + +MVN_DIRS = app_with_submodules simple_app app_with_infer_profile app_with_profiles + +CLEAN_EXTRA = \ + $(MVN_DIRS:%=../codetoanalyze/mvn/%/target) \ + $(MVN_DIRS:%=../codetoanalyze/mvn/%/com) \ + $(MVN_DIRS:%=infer-out-%) \ + $(MVN_DIRS:%=issues-%.exp.test) + +include $(TESTS_DIR)/java.make +include $(TESTS_DIR)/base.make + +infer-out/report.json: + $(MKDIR_P) $(@D) + touch $@ + +infer-out-%/report.json: $(JAVA_DEPS) $(SOURCES) + cd ../codetoanalyze/mvn/$* && \ + $(call silent_on_success,\ + $(INFER_BIN) -a $(ANALYZER) --results-dir $(CURDIR)/$(@D) \ + --project-root $(CURDIR)/$(TESTS_DIR) -- \ + $(MVN) clean compile) + +infer-out-app_with_submodules/report.json: infer-out-simple_app/report.json + +infer-out-simple_app/report.json: infer-out-app_with_infer_profile/report.json + +infer-out-app_with_infer_profile/report.json: infer-out-app_with_profiles/report.json + +issues-%.exp.test: infer-out-%/report.json + $(INFERPRINT_BIN) -q -a $(ANALYZER) $(INFERPRINT_OPTIONS) $@ --from-json-report $< + +issues.exp.test: $(foreach mvndir,$(MVN_DIRS),issues-$(mvndir).exp.test) +# erase the contents of the file + : > $@ +# remember the file name so it's easier to know which bug is from where + for mvndir in $(MVN_DIRS); do \ + echo "-- $$mvndir" >> $@; \ + cat issues-$$mvndir.exp.test >> $@; \ + done + +prout: + @echo "prout: $(MVN_DIRS)" + @echo "clean extra: $(CLEAN_EXTRA)" diff --git a/infer/tests/build_systems/mvn/issues.exp b/infer/tests/build_systems/mvn/issues.exp new file mode 100644 index 000000000..7fe2dc926 --- /dev/null +++ b/infer/tests/build_systems/mvn/issues.exp @@ -0,0 +1,19 @@ +-- app_with_submodules +build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java, void Hello2.mayCauseNPE2(), 4, NULL_DEREFERENCE, [start of procedure mayCauseNPE2(),start of procedure mayReturnNull(...),Taking false branch,return from a call to Pointers$A Pointers.mayReturnNull(int)] +build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java, void Hello2.mayLeakResource2(), 7, RESOURCE_LEAK, [start of procedure mayLeakResource2(),start of procedure allocateResource(),return from a call to FileOutputStream Resources.allocateResource(),Taking false branch] +build_systems/codetoanalyze/mvn/app_with_submodules/module2/src/main/java/com/mycompany/Hello2.java, void Hello2.twoResources2(), 11, RESOURCE_LEAK, [start of procedure twoResources2(),Taking true branch,exception java.io.IOException] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayCauseNPE(), 4, NULL_DEREFERENCE, [start of procedure mayCauseNPE(),start of procedure mayReturnNull(...),Taking false branch,return from a call to Pointers$A Pointers.mayReturnNull(int)] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayLeakResource(), 7, RESOURCE_LEAK, [start of procedure mayLeakResource(),start of procedure allocateResource(),return from a call to FileOutputStream Resources.allocateResource(),Taking false branch] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.twoResources(), 11, RESOURCE_LEAK, [start of procedure twoResources(),Taking true branch,exception java.io.IOException] +-- simple_app +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayCauseNPE(), 4, NULL_DEREFERENCE, [start of procedure mayCauseNPE(),start of procedure mayReturnNull(...),Taking false branch,return from a call to Pointers$A Pointers.mayReturnNull(int)] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayLeakResource(), 7, RESOURCE_LEAK, [start of procedure mayLeakResource(),start of procedure allocateResource(),return from a call to FileOutputStream Resources.allocateResource(),Taking false branch] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.twoResources(), 11, RESOURCE_LEAK, [start of procedure twoResources(),Taking true branch,exception java.io.IOException] +-- app_with_infer_profile +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayCauseNPE(), 4, NULL_DEREFERENCE, [start of procedure mayCauseNPE(),start of procedure mayReturnNull(...),Taking false branch,return from a call to Pointers$A Pointers.mayReturnNull(int)] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayLeakResource(), 7, RESOURCE_LEAK, [start of procedure mayLeakResource(),start of procedure allocateResource(),return from a call to FileOutputStream Resources.allocateResource(),Taking false branch] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.twoResources(), 11, RESOURCE_LEAK, [start of procedure twoResources(),Taking true branch,exception java.io.IOException] +-- app_with_profiles +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayCauseNPE(), 4, NULL_DEREFERENCE, [start of procedure mayCauseNPE(),start of procedure mayReturnNull(...),Taking false branch,return from a call to Pointers$A Pointers.mayReturnNull(int)] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.mayLeakResource(), 7, RESOURCE_LEAK, [start of procedure mayLeakResource(),start of procedure allocateResource(),return from a call to FileOutputStream Resources.allocateResource(),Taking false branch] +build_systems/codetoanalyze/mvn/simple_app/src/main/java/com/mycompany/Hello.java, void Hello.twoResources(), 11, RESOURCE_LEAK, [start of procedure twoResources(),Taking true branch,exception java.io.IOException] diff --git a/scripts/toplevel_init b/scripts/toplevel_init index 1309c7ad7..dc9a9f507 100644 --- a/scripts/toplevel_init +++ b/scripts/toplevel_init @@ -6,6 +6,7 @@ #require "ctypes.foreign";; #require "sawja";; #require "atdgen";; +#require "xmlm";; (* load infer code *) #load_rec "toplevel.cmo";;