[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
master
Jules Villard 8 years ago committed by Facebook Github Bot
parent 99e2038560
commit 2e96caac42

1
.gitignore vendored

@ -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/libs/
/infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/obj/ /infer/tests/build_systems/codetoanalyze/ndk-build/hello_app/obj/
/infer/tests/build_systems/codetoanalyze/utf8_*n_pwd /infer/tests/build_systems/codetoanalyze/utf8_*n_pwd
/infer/tests/build_systems/codetoanalyze/mvn/**/target/
# generated by oUnit # generated by oUnit
/oUnit-all.cache /oUnit-all.cache

@ -26,6 +26,9 @@ endif
ifneq ($(CMAKE),no) ifneq ($(CMAKE),no)
BUILD_SYSTEMS_TESTS += clang_compilation_db cmake BUILD_SYSTEMS_TESTS += clang_compilation_db cmake
endif endif
ifneq ($(MVN),no)
BUILD_SYSTEMS_TESTS += mvn
endif
ifneq ($(NDKBUILD),no) ifneq ($(NDKBUILD),no)
BUILD_SYSTEMS_TESTS += ndk_build BUILD_SYSTEMS_TESTS += ndk_build
endif endif

@ -40,6 +40,7 @@ MKDIR_P_CMD = case "@MKDIR_P@" in \
*) printf "@MKDIR_P@\n";; \ *) printf "@MKDIR_P@\n";; \
esac esac
MKDIR_P = $(shell $(MKDIR_P_CMD)) MKDIR_P = $(shell $(MKDIR_P_CMD))
MVN = @MVN@
NCPU = @NCPU@ NCPU = @NCPU@
NDKBUILD = @NDKBUILD@ NDKBUILD = @NDKBUILD@
prefix = @prefix@ prefix = @prefix@

@ -273,6 +273,10 @@ if test ! -z "$SANDCASTLE"; then
else else
AC_CHECK_TOOL([BUCK], [buck], [no]) AC_CHECK_TOOL([BUCK], [buck], [no])
fi 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]) AC_CHECK_TOOL([NDKBUILD], [ndk-build], [no])
if test x"$NDKBUILD" = x"no"; then if test x"$NDKBUILD" = x"no"; then
# ndk-build not in $PATH, look into potential android NDK install paths and record the absolute path # ndk-build not in $PATH, look into potential android NDK install paths and record the absolute path

@ -39,7 +39,6 @@ MODULE_TO_COMMAND = {
'gradle': ['gradle', 'gradlew'], 'gradle': ['gradle', 'gradlew'],
'make': make.SUPPORTED_COMMANDS, 'make': make.SUPPORTED_COMMANDS,
'xcodebuild': ['xcodebuild'], 'xcodebuild': ['xcodebuild'],
'mvn': ['mvn'],
'ndk-build': ['ndk-build'], 'ndk-build': ['ndk-build'],
} }

@ -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] <space separated options>
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] <space separated directories>
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)

@ -51,7 +51,7 @@ let build_system_exe_assoc = [
printing *) printing *)
BMake, "make/cc"; BMake, "cc"; BMake, "clang"; BMake, "clang++"; BMake, "cmake"; BMake, "make/cc"; BMake, "cc"; BMake, "clang"; BMake, "clang++"; BMake, "cmake";
BMake, "configure"; BMake, "g++"; BMake, "gcc"; BMake, "make"; BMake, "waf"; 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 = let build_system_of_exe_name name =
@ -70,6 +70,7 @@ type driver_mode =
| BuckCompilationDB | BuckCompilationDB
| ClangCompilationDB of string list | ClangCompilationDB of string list
| Javac of Javac.compiler * string * string list | Javac of Javac.compiler * string * string list
| Maven of string * string list
| PythonCapture of build_system * string list | PythonCapture of build_system * string list
| XcodeXcpretty | XcodeXcpretty
@ -98,6 +99,9 @@ let pp_driver_mode fmt driver_mode =
the fact, so log them now. *) the fact, so log them now. *)
Option.iter ~f:log_argfile_arg in Option.iter ~f:log_argfile_arg in
List.iter ~f:log_arg args 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 () = let remove_results_dir () =
rmtree Config.results_dir rmtree Config.results_dir
@ -190,6 +194,9 @@ let capture = function
| Javac (compiler, prog, args) -> | Javac (compiler, prog, args) ->
L.stdout "Capturing in javac mode...@."; L.stdout "Capturing in javac mode...@.";
Javac.capture compiler ~prog ~args Javac.capture compiler ~prog ~args
| Maven (prog, args) ->
L.stdout "Capturing in maven mode...@.";
Maven.capture ~prog ~args
| PythonCapture (build_system, build_cmd) -> | PythonCapture (build_system, build_cmd) ->
L.stdout "Capturing in %s mode...@." (string_of_build_system build_system); L.stdout "Capturing in %s mode...@." (string_of_build_system build_system);
let in_buck_mode = build_system = BBuck in 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 (* 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. *) a separate Analyze invocation is necessary, depending on the buck flavor used. *)
false, false false, false
| _ when Config.maven ->
(* Called from Maven, only do capture. *)
false, false
| _, (Capture | Compile) -> | _, (Capture | Compile) ->
false, false false, false
| _, (Infer | Eradicate | Checkers | Tracing | Crashcontext | Quandary | Threadsafety) -> | _, (Infer | Eradicate | Checkers | Tracing | Crashcontext | Quandary | Threadsafety) ->
@ -344,13 +354,21 @@ let driver_mode_of_build_cmd build_cmd =
Javac (Javac.Java, prog, args) Javac (Javac.Java, prog, args)
| BJavac -> | BJavac ->
Javac (Javac.Javac, prog, args) Javac (Javac.Javac, prog, args)
| BMvn ->
Maven (prog, args)
| BXcode when Config.xcpretty -> | BXcode when Config.xcpretty ->
XcodeXcpretty 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) PythonCapture (build_system, build_cmd)
let get_driver_mode () = let get_driver_mode () =
match Config.generated_classes with 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 -> | Some path ->
BuckGenrule path BuckGenrule path
| None -> | None ->
@ -358,7 +376,7 @@ let get_driver_mode () =
let () = let () =
let driver_mode = get_driver_mode () in 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 () ; remove_results_dir () ;
create_results_dir () ; create_results_dir () ;
(* re-set log files, as default files were in results_dir removed above *) (* re-set log files, as default files were in results_dir removed above *)

@ -576,7 +576,8 @@ let extra_env_args = ref []
let extend_env_args args = let extend_env_args args =
extra_env_args := List.rev_append args !extra_env_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 [] let full_speclist = ref []
in in
let usage_msg = exe_usage current_exe 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 env_args = decode_env_to_argv (Option.value (Sys.getenv args_env_var) ~default:"") in
let exe_name = Sys.executable_name 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 = let env_cl_args =
if should_parse_cl_args then prepend_to_argv env_args if should_parse_cl_args then prepend_to_argv env_args
else env_args in else env_args in

@ -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 and [env_var] is not set. If [accept_unknown] is set, unknown options are treated the same as
anonymous arguments. *) anonymous arguments. *)
val parse : ?incomplete:bool -> ?accept_unknown:bool -> ?config_file:string -> 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

@ -307,9 +307,21 @@ let init_work_dir, is_originator =
directory of the initial invocation of infer. *) directory of the initial invocation of infer. *)
let resolve = Utils.filename_to_absolute ~root:init_work_dir 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 *) (** 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 *) (* Declare the phase 1 options *)
let inferconfig_home = let inferconfig_home =
@ -325,7 +337,7 @@ and project_root =
(* Parse the phase 1 options, ignoring the rest *) (* 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 *) (* 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 parse_args_and_return_usage_exit =
let 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 () ; post_parsing_initialization () ;
usage_exit usage_exit

@ -42,6 +42,8 @@ type dynamic_dispatch_policy = [
| `Lazy | `Lazy
] ]
val env_inside_maven : Unix.env
(** Constant configuration values *) (** Constant configuration values *)
val allow_missing_index_in_proc_call : bool val allow_missing_index_in_proc_call : bool
@ -215,6 +217,7 @@ val latex : string option
val linters_def_file : string list val linters_def_file : string list
val load_analysis_results : string option val load_analysis_results : string option
val makefile_cmdline : string val makefile_cmdline : string
val maven : bool
val merge : bool val merge : bool
val ml_buckets : val ml_buckets :
[ `MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown ] list [ `MLeak_all | `MLeak_arc | `MLeak_cf | `MLeak_cpp | `MLeak_no_arc | `MLeak_unknown ] list

@ -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 <profile>\
\n <id>%s</id>\
\n <build>\
\n <plugins>\
\n <plugin>\
\n <groupId>org.apache.maven.plugins</groupId>\
\n <artifactId>maven-compiler-plugin</artifactId>\
\n <configuration>\
\n <compilerId>javac</compilerId>\
\n <forceJavacCompilerUse>true</forceJavacCompilerUse>\
\n <fork>true</fork>\
\n <executable>%s</executable>\
\n </configuration>\
\n </plugin>\
\n </plugins>\
\n </build>\
\n </profile>\
" 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 <profiles> 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 </profiles> tag but no infer profile found, add one *)
insert_infer_profile xml_out
| _::[] when not !found_profiles_tag->
(* closing the root tag but no <profiles> tag found, add
<profiles>[infer profile]</profiles> *)
Xmlm.output xml_out (`El_start (("", "profiles"), []));
found_profiles_tag := true; (* do not add <profiles> 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)

@ -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

@ -0,0 +1,28 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>app-with-infer-profile</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>app-with-infer-profile</name>
<profiles>
<profile>
<id>infer-capture</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerId>javac</compilerId>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<fork>true</fork>
<executable>${project.basedir}/../../../../../bin/infer</executable>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>app-with-profiles</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>app-with-profiles</name>
<profiles>
<profile>
<id>some-profile</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>module1</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>module1</name>
</project>

@ -0,0 +1,16 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>module2</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>module2</name>
<dependencies>
<dependency>
<groupId>com.mycompany</groupId>
<artifactId>module1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

@ -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(); }
}
}
}

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.some.company.name</groupId>
<artifactId>app-with-submodules</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>my app with submodules</name>
<url>http://maven.apache.org</url>
<modules>
<module>module1</module>
<module>module2</module>
</modules>
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>simple-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>simple-app</name>
</project>

@ -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(); }
}
}
}

@ -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;
}
}

@ -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;
}
}
}

@ -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)"

@ -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]

@ -6,6 +6,7 @@
#require "ctypes.foreign";; #require "ctypes.foreign";;
#require "sawja";; #require "sawja";;
#require "atdgen";; #require "atdgen";;
#require "xmlm";;
(* load infer code *) (* load infer code *)
#load_rec "toplevel.cmo";; #load_rec "toplevel.cmo";;

Loading…
Cancel
Save