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";;