Call java frontend directly instead of using fork

Reviewed By: jeremydubreil

Differential Revision: D4114523

fbshipit-source-id: a822fc8
master
Josh Berdine 8 years ago committed by Facebook Github Bot
parent 0095e9c635
commit 4ec3af4a7f

1
.gitignore vendored

@ -78,7 +78,6 @@ buck-out/
/infer/bin/InferAnalyze
/infer/bin/InferClang
/infer/bin/InferClang++
/infer/bin/InferJava
/infer/bin/InferPrint
/infer/bin/InferUnit

@ -26,7 +26,6 @@ def java_library(
'--classpath', '$(classpath :{})'.format(compile_name),
'--sourcepath', '$(location :{})'.format(export_srcs_name),
'--generated-classes', '$(location :{})'.format(compile_name),
'--', 'genrule'
]),
out = 'infer_out',
)

@ -10,8 +10,6 @@
The rest of the commands in infer/bin/ are not meant to be called directly, but are used by the top-level commands above.
*InferJava* : Binary containing the Java frontend.
*InferClang* : Binary containing the clang frontend.
*InferAnalyze* : Binary containing the backend of Infer that performs the analysis.

@ -317,7 +317,6 @@ ifeq ($(BUILD_JAVA_ANALYZERS),yes)
@for i in infer/lib/java/*.jar; do \
$(INSTALL_DATA) -C $$i $(DESTDIR)$(libdir)/infer/$$i; \
done
$(INSTALL_PROGRAM) -C $(INFERJAVA_BIN) $(DESTDIR)$(libdir)/infer/infer/bin/
$(INSTALL_PROGRAM) -C $(LIB_DIR)/wrappers/javac \
$(DESTDIR)$(libdir)/infer/infer/lib/wrappers/
endif

@ -94,7 +94,6 @@ CAPTURE_LIB_DIR = $(PYTHON_LIB_DIR)/capture
INFERANALYZE_BIN = $(BIN_DIR)/InferAnalyze
INFERCLANG_BIN = $(BIN_DIR)/InferClang
INFERJAVA_BIN = $(BIN_DIR)/InferJava
INFERPRINT_BIN = $(BIN_DIR)/InferPrint
INFERUNIT_BIN = $(BIN_DIR)/InferUnit
INFER_BIN = $(BIN_DIR)/infer
@ -107,10 +106,9 @@ endif
JAVA_DEPS = $(addprefix $(PYTHON_LIB_DIR)/, \
analyze.py bucklib.py config.py issues.py jwlib.py source.py utils.py) \
$(addprefix $(CAPTURE_LIB_DIR)/, javac.py util.py) \
$(addprefix $(CAPTURE_LIB_DIR)/, util.py) \
$(INFER_BIN) \
$(INFERANALYZE_BIN) \
$(INFERJAVA_BIN) \
$(INFERPRINT_BIN)
CLANG_DEPS = $(addprefix $(PYTHON_LIB_DIR)/, \

@ -35,11 +35,8 @@ CMD_MARKER = '--'
# All supported commands should be listed here
MODULE_TO_COMMAND = {
'ant': ['ant'],
'analyze': ['analyze'],
'buck': ['buck'],
'gradle': ['gradle', 'gradlew'],
'javac': ['javac'],
'java': ['java'],
'make': make.SUPPORTED_COMMANDS,
'xcodebuild': ['xcodebuild'],
'mvn': ['mvn'],
@ -143,18 +140,7 @@ def main():
args = global_argparser.parse_args(to_parse)
remove_infer_out = (imported_module is not None and
not args.reactive and
capture_module_name != 'analyze' and
not args.buck)
if remove_infer_out:
analyze.remove_infer_out(args.infer_out)
if imported_module is not None:
analyze.create_results_dir(args.infer_out)
analyze.reset_start_file(args.infer_out,
touch_if_present=not args.continue_capture)
utils.configure_logging(args)
try:
logging.info('output of locale.getdefaultlocale(): %s',

@ -26,8 +26,6 @@ from . import config, issues, utils
# Increase the limit of the CSV parser to sys.maxlimit
csv.field_size_limit(sys.maxsize)
INFER_ANALYZE_BINARY = 'InferAnalyze'
base_parser = argparse.ArgumentParser(add_help=False)
base_group = base_parser.add_argument_group('global arguments')
@ -40,10 +38,6 @@ base_group.add_argument('-o', '--out', metavar='<directory>',
base_group.add_argument('-r', '--reactive', action='store_true',
help='''Analyze in reactive propagation mode
starting from changed files.''')
base_group.add_argument('-c', '--continue', action='store_true',
dest='continue_capture',
help='''Continue the capture for the reactive
analysis, increasing the changed files/procedures.''')
base_group.add_argument('--debug-exceptions', action='store_true',
help='''Generate lightweight debugging information:
just print the internal exceptions during analysis''')
@ -87,194 +81,3 @@ infer_group.add_argument('--buck', action='store_true', dest='buck',
infer_group.add_argument('--java-jar-compiler',
metavar='<file>')
def remove_infer_out(infer_out):
# it is safe to ignore errors here because recreating the infer_out
# directory will fail later
shutil.rmtree(infer_out, True)
def create_results_dir(results_dir):
utils.mkdir_if_not_exists(results_dir)
utils.mkdir_if_not_exists(os.path.join(results_dir, 'specs'))
utils.mkdir_if_not_exists(os.path.join(results_dir, 'captured'))
utils.mkdir_if_not_exists(os.path.join(results_dir, 'sources'))
def reset_start_file(results_dir, touch_if_present=False):
start_path = os.path.join(results_dir, '.start')
if (not os.path.exists(start_path)) or touch_if_present:
# create new empty file - this will update modified timestamp
open(start_path, 'w').close()
def clean(infer_out):
directories = [
'multicore', 'classnames', 'sources',
config.JAVAC_FILELISTS_FILENAME,
]
extensions = ['.cfg', '.cg']
for root, dirs, files in os.walk(infer_out):
for d in dirs:
if d in directories:
path = os.path.join(root, d)
shutil.rmtree(path)
for f in files:
for ext in extensions:
if f.endswith(ext):
path = os.path.join(root, f)
os.remove(path)
def help_exit(message):
utils.stdout(message)
infer_parser.print_usage()
exit(1)
def run_command(cmd, debug_mode, javac_arguments, step, analyzer):
if debug_mode:
utils.stdout('\n{0}\n'.format(' '.join(cmd)))
try:
return subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
error_msg = 'Failure during {0}, original command was\n\n{1}\n\n'
infer_cmd = ['infer', '-g', '-a', analyzer]
failing_cmd = infer_cmd + ['--', 'javac'] + javac_arguments
logging.error(error_msg.format(
step,
failing_cmd
))
raise e
class AnalyzerWrapper(object):
javac = None
def __init__(self, args):
self.args = args
if self.args.analyzer not in config.ANALYZERS:
help_exit('Unknown analysis mode \"{0}\"'
.format(self.args.analyzer))
if self.args.infer_out is None:
help_exit('Expect Infer results directory')
try:
os.mkdir(self.args.infer_out)
except OSError as e:
if not os.path.isdir(self.args.infer_out):
raise e
def clean_exit(self):
if os.path.isdir(self.args.infer_out):
utils.stdout('removing {}'.format(self.args.infer_out))
shutil.rmtree(self.args.infer_out)
exit(os.EX_OK)
def analyze(self):
logging.info('Starting analysis')
infer_analyze = [
utils.get_cmd_in_bin_dir(INFER_ANALYZE_BINARY),
'-results_dir',
self.args.infer_out
]
exit_status = os.EX_OK
javac_original_arguments = \
self.javac.original_arguments if self.javac is not None else []
if self.args.multicore == 1:
analyze_cmd = infer_analyze
exit_status = run_command(
analyze_cmd,
self.args.debug,
javac_original_arguments,
'analysis',
self.args.analyzer
)
else:
multicore_dir = os.path.join(self.args.infer_out, 'multicore')
pwd = os.getcwd()
if os.path.isdir(multicore_dir):
shutil.rmtree(multicore_dir)
os.mkdir(multicore_dir)
os.chdir(multicore_dir)
analyze_cmd = infer_analyze + ['-makefile', 'Makefile']
makefile_status = run_command(
analyze_cmd,
self.args.debug,
javac_original_arguments,
'create_makefile',
self.args.analyzer
)
exit_status += makefile_status
if makefile_status == os.EX_OK:
make_cmd = ['make', '-k']
make_cmd += ['-j', str(self.args.multicore)]
if self.args.load_average is not None:
make_cmd += ['-l', str(self.args.load_average)]
if not self.args.debug:
make_cmd += ['-s']
make_status = run_command(
make_cmd,
self.args.debug,
javac_original_arguments,
'run_makefile',
self.args.analyzer
)
os.chdir(pwd)
exit_status += make_status
if self.args.buck and exit_status == os.EX_OK:
clean(self.args.infer_out)
return exit_status
def create_report(self):
"""Report statistics about the computation and create a CSV file
containing the list or errors found during the analysis"""
out_dir = self.args.infer_out
json_report = os.path.join(out_dir, config.JSON_REPORT_FILENAME)
procs_report = os.path.join(self.args.infer_out, 'procs.csv')
infer_print_cmd = [utils.get_cmd_in_bin_dir('InferPrint')]
infer_print_options = [
'-q',
'-results_dir', self.args.infer_out,
'-bugs_json', json_report,
'-procs', procs_report,
'-analyzer', self.args.analyzer
]
if self.args.debug or self.args.debug_exceptions:
infer_print_options.append('-with_infer_src_loc')
exit_status = subprocess.check_call(
infer_print_cmd + infer_print_options
)
if exit_status != os.EX_OK:
logging.error(
'Error with InferPrint with the command: {}'.format(
infer_print_cmd))
return exit_status
def report(self):
report_status = self.create_report()
if report_status == os.EX_OK and not self.args.buck:
infer_out = self.args.infer_out
json_report = os.path.join(infer_out, config.JSON_REPORT_FILENAME)
bugs_out = os.path.join(infer_out, config.BUGS_FILENAME)
issues.print_and_save_errors(infer_out, self.args.project_root,
json_report, bugs_out,
self.args.pmd_xml)
def analyze_and_report(self):
if self.args.analyzer not in [config.ANALYZER_COMPILE,
config.ANALYZER_CAPTURE]:
if self.args.analyzer == config.ANALYZER_LINTERS:
self.report()
elif self.analyze() == os.EX_OK:
self.report()

@ -1,32 +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 util
MODULE_NAME = __name__
MODULE_DESCRIPTION = '''Run analysis of what has already been captured:
Usage:
infer -- analyze
infer --out <capture_folder> -- analyze'''
LANG = ['clang', 'java']
def gen_instance(*args):
return NoCapture(*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 NoCapture:
def __init__(self, args, cmd):
self.args = args
def capture(self):
return os.EX_OK

@ -61,8 +61,7 @@ class AntCapture:
if argument_start_pattern in line:
collect = True
if javac_arguments != []:
capture = jwlib.create_infer_command(self.args,
javac_arguments)
capture = jwlib.create_infer_command(javac_arguments)
calls.append(capture)
javac_arguments = []
if collect:
@ -72,7 +71,7 @@ class AntCapture:
arg = self.remove_quotes(content)
javac_arguments.append(arg)
if javac_arguments != []:
capture = jwlib.create_infer_command(self.args, javac_arguments)
capture = jwlib.create_infer_command(javac_arguments)
calls.append(capture)
javac_arguments = []
return calls

@ -76,7 +76,7 @@ class GradleCapture:
sources.write('\n'.join(map(utils.encode, java_files)))
sources.flush()
java_args.append('@' + sources.name)
capture = jwlib.create_infer_command(self.args, java_args)
capture = jwlib.create_infer_command(java_args)
calls.append(capture)
return calls

@ -1,63 +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 argparse
import os
import subprocess
import traceback
import util
from inferlib import jwlib, utils
MODULE_NAME = __name__
MODULE_DESCRIPTION = '''Run analysis of code built with a command like:
java -jar compiler.jar <options> <source files>
Analysis examples:
infer -- java -jar compiler.jar srcfile.java
infer -- /path/to/java -jar compiler.jar srcfile.java'''
LANG = ['java']
def gen_instance(*args):
return JavaJarCapture(*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)
def parse_command_line(cmd):
cmd_parser = argparse.ArgumentParser()
cmd_parser.add_argument('-jar', type=utils.decode, metavar='Compiler jar')
java_jar, other_args = cmd_parser.parse_known_args(cmd[1:])
if java_jar.jar is None:
utils.stderr('Expects a javac command or jar file for the compiler')
utils.stderr('Example: infer -- java -jar compiler.jar ...\n')
exit(1)
return cmd[0], java_jar.jar, other_args
class JavaJarCapture:
def __init__(self, args, cmd):
java_binary, java_jar, other_args = parse_command_line(cmd)
if args.java_jar_compiler is not None:
java_jar = args.java_jar_compiler
self.analysis = jwlib.AnalyzerWithJavaJar(
args,
java_binary,
java_jar,
other_args)
def capture(self):
try:
self.analysis.start()
return os.EX_OK
except subprocess.CalledProcessError as exc:
if self.analysis.args.debug:
traceback.print_exc()
return exc.returncode

@ -1,54 +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 subprocess
import traceback
import util
from inferlib import jwlib
MODULE_NAME = __name__
MODULE_DESCRIPTION = '''Run analysis of code built with a command like:
javac <options> <source files>
Analysis examples:
infer -- javac srcfile.java
infer -- /path/to/javac srcfile.java'''
LANG = ['java']
def gen_instance(*args):
return JavacCapture(*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 JavacCapture:
def __init__(self, args, cmd):
if args.java_jar_compiler is not None:
self.analysis = jwlib.AnalyzerWithJavaJar(
args,
'java',
args.java_jar_compiler,
cmd[1:])
else:
self.analysis = jwlib.AnalyzerWithJavac(
args,
cmd[0],
cmd[1:])
def capture(self):
try:
self.analysis.start()
return os.EX_OK
except subprocess.CalledProcessError as exc:
if self.analysis.args.debug:
traceback.print_exc()
return exc.returncode

@ -49,7 +49,7 @@ class MavenCapture:
if options_next:
# line has format [Debug] <space separated options>
javac_args = line.split(' ')[1:] + files_to_compile
capture = jwlib.create_infer_command(self.args, javac_args)
capture = jwlib.create_infer_command(javac_args)
calls.append(capture)
options_next = False
files_to_compile = []

@ -11,186 +11,40 @@ from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import codecs
import logging
import os
import subprocess
import tempfile
import time
from . import config
from . import analyze, config, utils
# javac options
parser = argparse.ArgumentParser()
class InferJavacCapture():
current_directory = utils.decode(os.getcwd())
parser.add_argument('-d', dest='classes_out', default=current_directory)
def _get_javac_args(args):
try:
javac_pos = args.index('javac')
except ValueError:
return None
javac_args = args[javac_pos + 1:]
if len(javac_args) == 0:
return None
else:
# replace any -g:.* flag with -g to preserve debugging symbols
args = map(lambda arg: '-g' if '-g:' in arg else arg, javac_args)
# skip -Werror
args = filter(lambda arg: arg != '-Werror', args)
return args
def create_infer_command(args, javac_arguments):
infer_args = ['-o', args.infer_out]
if args.debug:
infer_args.append('--debug')
infer_args += ['--analyzer', 'capture']
return AnalyzerWithJavac(
analyze.infer_parser.parse_args(infer_args),
'javac',
_get_javac_args(['javac'] + javac_arguments)
)
class CompilerCall(object):
def __init__(self, javac_cmd, arguments):
assert javac_cmd is not None and arguments is not None
self.javac_cmd = javac_cmd
self.original_arguments = arguments
self.args, self.remaining_args = parser.parse_known_args(arguments)
self.verbose_out = None
def run(self):
javac_args = ['-verbose', '-g']
if self.args.classes_out is not None:
javac_args += ['-d', self.args.classes_out]
javac_args += self.remaining_args
def arg_must_go_on_cli(arg):
# as mandated by javac, argument files must not contain
# arguments
return arg.startswith('-J') or arg.startswith('@')
file_args = filter(lambda x: not arg_must_go_on_cli(x), javac_args)
cli_args = filter(arg_must_go_on_cli, javac_args)
# pass non-special args via a file to avoid blowing up the
# command line size limit
with tempfile.NamedTemporaryFile(
mode='w',
prefix='javac_args_',
delete=False) as command_line:
escaped_args = map(lambda x: '"%s"' % (x.replace('"', '\\"')),
file_args)
command_line.write(utils.encode('\n'.join(escaped_args)))
command_line.write('\n')
self.command_line_file = command_line.name
with tempfile.NamedTemporaryFile(
mode='w',
suffix='.out',
prefix='javac_',
delete=False) as file_out:
self.verbose_out = file_out.name
command = self.javac_cmd + cli_args + \
['@' + str(self.command_line_file)]
try:
subprocess.check_call(command, stderr=file_out)
except subprocess.CalledProcessError:
try:
fallback_command = ['javac'] + cli_args + \
['@' + str(self.command_line_file)]
subprocess.check_call(
fallback_command, stderr=file_out)
except subprocess.CalledProcessError:
error_msg = 'ERROR: failure during compilation ' \
+ 'command.\nYou can run the failing ' \
+ 'compilation command again by ' \
+ 'copy-pasting the\nlines below in ' \
+ 'your terminal:\n\n"""\n' \
+ 'python <<EOF\n' \
+ 'import subprocess\n' \
+ 'cmd = {}\n' \
+ 'subprocess.check_call(cmd)\n' \
+ 'EOF\n"""\n'
failing_cmd = filter(lambda arg: arg != '-verbose',
command)
utils.stderr(error_msg.format(failing_cmd))
subprocess.check_call(failing_cmd)
return os.EX_OK
class AnalyzerWithFrontendWrapper(analyze.AnalyzerWrapper):
def __init__(self, infer_args, compiler_call):
analyze.AnalyzerWrapper.__init__(self, infer_args)
self.javac = compiler_call
if self.javac.original_arguments is None:
raise Exception('No javac command detected')
def __init__(self, javac_args):
self.javac_args = javac_args
def start(self):
self._compile()
if self.args.analyzer == config.ANALYZER_COMPILE:
return os.EX_OK
self._run_infer_frontend()
if self.args.analyzer == config.ANALYZER_CAPTURE:
return os.EX_OK
self.analyze_and_report()
self._close()
return os.EX_OK
def _run_infer_frontend(self):
infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')]
infer_cmd += [
'-verbose_out', self.javac.verbose_out,
]
if self.args.debug:
infer_cmd.append('-debug')
if self.args.analyzer == config.ANALYZER_TRACING:
infer_cmd.append('-tracing')
if self.args.android_harness:
infer_cmd.append('-harness')
return analyze.run_command(
infer_cmd,
self.args.debug,
self.javac.original_arguments,
'frontend',
self.args.analyzer
)
def _compile(self):
return self.javac.run()
def _close(self):
os.remove(self.javac.verbose_out)
class AnalyzerWithJavac(AnalyzerWithFrontendWrapper):
def __init__(self, infer_args, javac_executable, javac_args):
javac_cmd = [javac_executable, '-J-Duser.language=en']
compiler_call = CompilerCall(javac_cmd, javac_args)
AnalyzerWithFrontendWrapper.__init__(self, infer_args, compiler_call)
class AnalyzerWithJavaJar(AnalyzerWithFrontendWrapper):
def __init__(self, infer_args, java_executable, jar_path, compiler_args):
javac_cmd = [java_executable, '-jar', jar_path]
compiler_call = CompilerCall(javac_cmd, compiler_args)
AnalyzerWithFrontendWrapper.__init__(self, infer_args, compiler_call)
infer = os.path.join(config.BIN_DIRECTORY, 'infer')
# pass --continue to prevent removing the results-dir
cmd = [
infer,
'--analyzer', 'capture',
'--continue',
'--', 'javac'
] + self.javac_args
try:
return subprocess.check_call(cmd)
except Exception as e:
print('Failed to execute:', ' '.join(cmd))
raise e
def _get_javac_args(javac_args):
# replace any -g:.* flag with -g to preserve debugging symbols
args = map(lambda arg: '-g' if '-g:' in arg else arg, javac_args)
# skip -Werror
args = filter(lambda arg: arg != '-Werror', args)
return args
def create_infer_command(javac_args):
return InferJavacCapture(_get_javac_args(javac_args))

@ -116,14 +116,16 @@ let get_overriden_method tenv pname_java => {
/** Serializer for type environments */
let tenv_serializer: Serialization.serializer t = Serialization.create_serializer Serialization.tenv_key;
let global_tenv: Lazy.t (option t) =
lazy (Serialization.from_file tenv_serializer DB.global_tenv_fname);
let global_tenv: ref (option t) = ref None;
/** Load a type environment from a file */
let load_from_file (filename: DB.filename) :option t =>
if (filename == DB.global_tenv_fname) {
Lazy.force global_tenv
if (is_none !global_tenv) {
global_tenv := Serialization.from_file tenv_serializer DB.global_tenv_fname
};
!global_tenv
} else {
Serialization.from_file tenv_serializer filename
};
@ -131,6 +133,11 @@ let load_from_file (filename: DB.filename) :option t =>
/** Save a type environment into a file */
let store_to_file (filename: DB.filename) (tenv: t) => {
/* update in-memory global tenv for later uses by this process, e.g. in single-core mode the
frontend and backend run in the same process */
if (filename == DB.global_tenv_fname) {
global_tenv := Some tenv
};
Serialization.to_file tenv_serializer filename tenv;
if Config.debug_mode {
let debug_filename = DB.filename_to_string (DB.filename_add_suffix filename ".debug");

@ -81,8 +81,6 @@ JAVA_OCAMLBUILD_OPTIONS = -pkgs javalib,ptrees,sawja
JAVA_SOURCES = java
INFERJAVA_MAIN = $(JAVA_SOURCES)/jMain
#### Clang declarations ####
CLANG_SOURCES = clang
@ -139,7 +137,6 @@ INFER_BASE_TARGETS = \
$(INFERUNIT_MAIN).native
INFER_ALL_TARGETS = $(INFER_BASE_TARGETS) \
$(INFERJAVA_MAIN).native \
$(INFERCLANG_MAIN).native \
# configure-aware ocamlbuild commands and targets
@ -148,8 +145,9 @@ INFER_CONFIG_TARGETS = $(INFER_BASE_TARGETS)
ifeq ($(BUILD_JAVA_ANALYZERS),yes)
OCAMLBUILD_CONFIG += $(JAVA_OCAMLBUILD_OPTIONS)
INFER_CONFIG_TARGETS += $(INFERJAVA_MAIN).native
DEPENDENCIES += java
else
DEPENDENCIES += java_stubs
endif
ifeq ($(BUILD_C_ANALYZERS),yes)
INFER_CONFIG_TARGETS += $(INFERCLANG_MAIN).native
@ -187,9 +185,6 @@ $(INFER_BIN): $(INFER_BUILD_DIR)/$(INFER_MAIN).native $(BIN_DIR)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERANALYZE_MAIN).native $(INFERANALYZE_BIN)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERPRINT_MAIN).native $(INFERPRINT_BIN)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERUNIT_MAIN).native $(INFERUNIT_BIN)
ifeq ($(BUILD_JAVA_ANALYZERS),yes)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERJAVA_MAIN).native $(INFERJAVA_BIN)
endif
ifeq ($(BUILD_C_ANALYZERS),yes)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERCLANG_MAIN).native $(INFERCLANG_BIN)
endif
@ -384,7 +379,6 @@ clean:
$(REMOVE) backend/jsonbug_{j,t}.ml{,i}
$(REMOVE) checkers/stacktree_{j,t}.ml{,i}
$(REMOVE) $(INFER_BIN) $(INFERANALYZE_BIN) $(INFERPRINT_BIN)
$(REMOVE) $(INFERJAVA_BIN) $(INFERCLANG_BIN)
$(REMOVE) $(INFERUNIT_BIN) $(CHECKCOPYRIGHT_BIN)
$(REMOVE) $(CLANG_ATDGEN_STUBS)
$(REMOVE) $(INFER_CLANG_FCP_MIRRORED_FILES)

@ -14,6 +14,7 @@ open! IStd
module CLOpt = CommandLineOption
module L = Logging
module F = Format
let rec rmtree name =
@ -38,15 +39,13 @@ let rec rmtree name =
type build_mode =
| Analyze | Ant | Buck | Gradle | Java | Javac | Make | Mvn | Ndk
| Xcode | Genrule
| Analyze | Ant | Buck | Gradle | Java | Javac | Make | Mvn | Ndk | Xcode
let build_mode_of_string path =
match Filename.basename path with
| "analyze" -> Analyze
| "ant" -> Ant
| "buck" -> Buck
| "genrule" -> Genrule
| "gradle" | "gradlew" -> Gradle
| "java" -> Java
| "javac" -> Javac
@ -60,7 +59,6 @@ let string_of_build_mode = function
| Analyze -> "analyze"
| Ant -> "ant"
| Buck -> "buck"
| Genrule -> "genrule"
| Gradle -> "gradle"
| Java -> "java"
| Javac -> "javac"
@ -74,6 +72,7 @@ let remove_results_dir () =
rmtree Config.results_dir
let create_results_dir () =
Unix.mkdir_p (Config.results_dir ^/ Config.attributes_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.captured_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.specs_dir_name)
@ -122,16 +121,56 @@ let touch_start_file () =
with Unix.Unix_error (Unix.EEXIST, _, _) -> ()
let run_command ~prog ~args after_wait =
let status = Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ()) in
after_wait status ;
match status with
| Ok () -> ()
| Error _ ->
L.do_err "%s@\n%s@\n"
(String.concat ~sep:" " (prog :: args)) (Unix.Exit_or_signal.to_string_hum status) ;
exit 1
let run_command ~prog ~args cleanup =
Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())
|> fun status
-> cleanup status
; ok_exn (Unix.Exit_or_signal.or_error status)
let run_javac build_mode build_cmd =
let build_prog, build_args =
match build_cmd with
| prog :: args -> (prog, args)
| [] -> invalid_arg "run_java: build command cannot be empty" in
let prog, prog_args =
match build_mode, Config.java_jar_compiler with
| _, None -> (build_prog, ["-J-Duser.language=en"])
| Java, Some jar -> (build_prog, ["-jar"; jar])
| _, Some jar -> (* fall back to java in PATH to avoid passing -jar to javac *)
("java", ["-jar"; jar]) in
let cli_args, file_args =
let args =
"-verbose" :: "-g" ::
if List.exists build_args ~f:(function "-d" | "-classes_out" -> true | _ -> false)
then build_args
else "-d" :: Config.javac_classes_out :: build_args in
List.partition_tf args ~f:(fun arg ->
(* As mandated by javac, argument files must not contain certain arguments. *)
String.is_prefix ~prefix:"-J" arg || String.is_prefix ~prefix:"@" arg) in
(* Pass non-special args via a file to avoid exceeding the command line size limit. *)
let args_file =
let file = Filename.temp_file "args_" "" in
let quoted_file_args =
List.map file_args ~f:(fun arg ->
if String.contains arg '\'' then arg else F.sprintf "'%s'" arg) in
Out_channel.with_file file ~f:(fun oc -> Out_channel.output_lines oc quoted_file_args) ;
file in
let cli_file_args = cli_args @ ["@" ^ args_file] in
let args = prog_args @ cli_file_args in
let verbose_out_file = Filename.temp_file "javac_" ".out" in
Unix.with_file verbose_out_file ~mode:[Unix.O_WRONLY] ~f:(
fun verbose_out_fd ->
try
Unix_.fork_redirect_exec_wait ~prog ~args ~stderr:verbose_out_fd ()
with exn ->
try
Unix_.fork_redirect_exec_wait ~prog:"javac" ~args:cli_file_args ~stderr:verbose_out_fd ()
with _ ->
L.stderr "Failed to execute: %s %s@\nSee contents of %s.@\n"
prog (String.concat ~sep:" " args) verbose_out_file ;
raise exn
);
verbose_out_file
let check_xcpretty () =
match Unix.system "xcpretty --version" with
@ -149,25 +188,30 @@ let capture_with_compilation_database db_files =
let compilation_database = CompilationDatabase.from_json_files db_files in
CaptureCompilationDatabase.capture_files_in_database compilation_database
let capture build_cmd = function
| Analyze when not (List.is_empty !Config.clang_compilation_db_files) ->
let capture build_cmd build_mode =
match build_mode, Config.generated_classes with
| _, Some path ->
L.stdout "Capturing for Buck genrule compatibility...@\n";
JMain.main (lazy (JClasspath.load_from_arguments path))
| Analyze, _ when not (List.is_empty !Config.clang_compilation_db_files) ->
capture_with_compilation_database !Config.clang_compilation_db_files
| Analyze ->
| Analyze, _ ->
()
| Buck when Config.use_compilation_database <> None ->
| Buck, _ when Config.use_compilation_database <> None ->
L.stdout "Capturing using Buck's compilation database...@\n";
let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck () in
capture_with_compilation_database json_cdb
| Genrule ->
L.stdout "Capturing for Buck genrule compatibility...@\n";
let infer_java = Config.bin_dir ^/ "InferJava" in
run_command ~prog:infer_java ~args:[] (fun _ -> ())
| Xcode when Config.xcpretty ->
| (Java | Javac), _ ->
let verbose_out_file = run_javac build_mode build_cmd in
if Config.analyzer <> Config.Compile then
JMain.main (lazy (JClasspath.load_from_verbose_output verbose_out_file)) ;
Unix.unlink verbose_out_file
| Xcode, _ when Config.xcpretty ->
L.stdout "Capturing using xcpretty...@\n";
check_xcpretty ();
let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_xcodebuild () in
capture_with_compilation_database json_cdb
| build_mode ->
| build_mode, _ ->
L.stdout "Capturing in %s mode...@." (string_of_build_mode build_mode);
let in_buck_mode = build_mode = Buck in
let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" in
@ -205,8 +249,6 @@ let capture build_cmd = function
["--project-root"; Config.project_root] @
(if not Config.reactive_mode then [] else
["--reactive"]) @
(if not Config.continue_capture then [] else
["--continue"]) @
"--out" :: Config.results_dir ::
(match Config.xcode_developer_dir with None -> [] | Some d ->
["--xcode-developer-dir"; d]) @
@ -237,7 +279,7 @@ let run_parallel_analysis () =
) (fun _ -> ())
let execute_analyze () =
if Config.jobs = 1 then
if Config.jobs = 1 || Config.cluster_cmdline <> None then
InferAnalyze.main ""
else
run_parallel_analysis ()
@ -269,10 +311,7 @@ let analyze = function
(* 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. *)
()
| Java | Javac ->
(* In Java and Javac modes, analysis is invoked from capture. *)
()
| Analyze | Ant | Buck | Gradle | Genrule | Make | Mvn | Ndk | Xcode ->
| _ ->
if (Sys.file_exists Config.(results_dir ^/ captured_dir_name)) <> `Yes then (
L.stderr "There was nothing to analyze, exiting" ;
Config.print_usage_exit ()
@ -299,7 +338,7 @@ let fail_on_issue_epilogue () =
let () =
let build_cmd = IList.rev Config.rest in
let build_mode = match build_cmd with path :: _ -> build_mode_of_string path | [] -> Analyze in
if build_mode <> Analyze && not Config.buck && not Config.reactive_mode then
if not (build_mode = Analyze || Config.(buck || continue_capture || reactive_mode)) then
remove_results_dir () ;
create_results_dir () ;
(* re-set log files, as default files were in results_dir removed above *)

@ -17,14 +17,13 @@ module YBU = Yojson.Basic.Util
(** Each command line option may appear in the --help list of any executable, these tags are used to
specify which executables for which an option will be documented. *)
type exe = Analyze | Clang | Interactive | Java | Print | Toplevel
type exe = Analyze | Clang | Interactive | Print | Toplevel
(** Association list of executable (base)names to their [exe]s. *)
let exes = [
("InferAnalyze", Analyze);
("InferClang", Clang);
("InferJava", Java);
("InferPrint", Print);
("infer", Toplevel);
("interactive", Interactive);
@ -35,7 +34,7 @@ let exe_name =
fun exe -> IList.assoc (=) exe exe_to_name
let frontend_exes = [Clang; Java]
let frontend_exes = [Clang]
type desc = {
long: string; short: string; meta: string; doc: string; spec: Arg.spec;
@ -633,7 +632,6 @@ let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file current_exe e
if current_exe = Toplevel then (
add_to_curr_speclist ~header:"Analysis (backend) options" Analyze;
add_to_curr_speclist ~header:"Clang frontend options" Clang;
add_to_curr_speclist ~header:"Java frontend options" Java;
)
;
assert( check_no_duplicates !curr_speclist )
@ -650,7 +648,7 @@ let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file current_exe e
let exe_name = Sys.executable_name in
let should_parse_cl_args = match current_exe with
| Clang | Interactive -> false
| Analyze | Java | Print | Toplevel -> true in
| Analyze | Print | Toplevel -> true in
let env_cl_args =
if should_parse_cl_args then prepend_to_argv env_args
else env_args in

@ -11,7 +11,7 @@
open! IStd
type exe = Analyze | Clang | Interactive | Java | Print | Toplevel
type exe = Analyze | Clang | Interactive | Print | Toplevel
(** Association list of executable (base)names to their [exe]s. *)
val exes : (string * exe) list

@ -320,7 +320,7 @@ let inferconfig_home =
and project_root =
CLOpt.mk_path ~deprecated:["project_root"; "-project_root"] ~long:"project-root" ~short:"pr"
~default:init_work_dir
~exes:CLOpt.[Analyze;Clang;Java;Print;Toplevel]
~exes:CLOpt.[Analyze;Clang;Print;Toplevel]
~meta:"dir" "Specify the root directory of the project"
(* Parse the phase 1 options, ignoring the rest *)
@ -456,7 +456,6 @@ and analyzer =
and android_harness =
CLOpt.mk_bool ~deprecated:["harness"] ~long:"android-harness"
~exes:CLOpt.[Java]
"(Experimental) Create harness to detect issues involving the Android lifecycle"
and angelic_execution =
@ -482,7 +481,7 @@ and blacklist =
and bootclasspath =
CLOpt.mk_string_opt ~long:"bootclasspath"
~exes:CLOpt.[Toplevel; Java]
~exes:CLOpt.[Toplevel]
"Specify the Java bootclasspath"
(** Automatically set when running from within Buck *)
@ -596,7 +595,6 @@ and clang_include_to_override =
and classpath =
CLOpt.mk_string_opt ~long:"classpath"
~exes:CLOpt.[Java]
"Specify the Java classpath"
and cluster =
@ -713,7 +711,6 @@ and (
)
and dependencies =
CLOpt.mk_bool ~deprecated:["dependencies"] ~long:"dependencies"
~exes:CLOpt.[Java]
"Translate all the dependencies during the capture. The classes in the given jar file will be \
translated. No sources needed."
@ -830,7 +827,7 @@ and frontend_tests =
and generated_classes =
CLOpt.mk_path_opt ~long:"generated-classes"
~exes:CLOpt.[Toplevel; Java]
~exes:CLOpt.[Toplevel]
"Specify where to load the generated class files"
and headers =
@ -860,7 +857,6 @@ and iterations =
and java_jar_compiler =
CLOpt.mk_path_opt
~long:"java-jar-compiler"
~exes:CLOpt.[Java]
~meta:"path" "Specifify the Java compiler jar used to generate the bytecode"
and jobs =
@ -951,7 +947,6 @@ and patterns_modeled_expensive =
let long = "modeled-expensive" in
(long,
CLOpt.mk_json ~deprecated:["modeled_expensive"] ~long
~exes:CLOpt.[Java]
"Matcher or list of matchers for methods that should be considered expensive by the \
performance critical checker.")
@ -959,14 +954,12 @@ and patterns_never_returning_null =
let long = "never-returning-null" in
(long,
CLOpt.mk_json ~deprecated:["never_returning_null"] ~long
~exes:CLOpt.[Java]
"Matcher or list of matchers for functions that never return `null`.")
and patterns_skip_translation =
let long = "skip-translation" in
(long,
CLOpt.mk_json ~deprecated:["skip_translation"] ~long
~exes:CLOpt.[Java]
"Matcher or list of matchers for names of files that should not be analyzed at all.")
and pmd_xml =
@ -1042,7 +1035,7 @@ and report_hook =
and results_dir =
CLOpt.mk_path ~deprecated:["results_dir"; "-out"] ~long:"results-dir" ~short:"o"
~default:(init_work_dir ^/ "infer-out")
~exes:CLOpt.[Toplevel;Analyze;Clang;Java;Print]
~exes:CLOpt.[Toplevel;Analyze;Clang;Print]
~meta:"dir" "Write results and internal files in the specified directory"
and save_results =
@ -1055,7 +1048,7 @@ and seconds_per_iteration =
and skip_analysis_in_path =
CLOpt.mk_string_list ~long:"skip-analysis-in-path"
~exes:CLOpt.[Clang;Java]
~exes:CLOpt.[Clang]
~meta:"path prefix" "Ignore files whose path matches the given prefix"
and skip_clang_analysis_in_path =
@ -1070,12 +1063,10 @@ and skip_translation_headers =
and sources =
CLOpt.mk_string_list ~long:"sources"
~exes:CLOpt.[Java]
"Specify the list of source files"
and sourcepath =
CLOpt.mk_string_opt ~long:"sourcepath"
~exes:CLOpt.[Java]
"Specify the sourcepath"
and spec_abs_level =
@ -1185,13 +1176,13 @@ and verbose_out =
and version =
let var = ref `None in
CLOpt.mk_set var `Full ~deprecated:["version"] ~long:"version"
~exes:CLOpt.[Toplevel;Analyze;Clang;Java;Print]
~exes:CLOpt.[Toplevel;Analyze;Clang;Print]
"Print version information and exit" ;
CLOpt.mk_set var `Json ~deprecated:["version_json"] ~long:"version-json"
~exes:CLOpt.[Analyze;Clang;Java;Print]
~exes:CLOpt.[Analyze;Clang;Print]
"Print version information in json format and exit" ;
CLOpt.mk_set var `Vcs ~long:"version-vcs"
~exes:CLOpt.[Analyze;Clang;Java;Print]
~exes:CLOpt.[Analyze;Clang;Print]
"Print version control system commit and exit" ;
var
@ -1229,14 +1220,14 @@ and xml_specs =
CLOpt.mk_bool ~deprecated:["xml"] ~long:"xml-specs"
"Export specs into XML files file1.xml ... filen.xml"
let javac_classes_out = ref None
let javac_classes_out = ref init_work_dir
(* The "rest" args must appear after "--" on the command line, and hence after other args, so they
are allowed to refer to the other arg variables. *)
let rest =
let classes_out_spec =
Arg.String (fun classes_out ->
javac_classes_out := Some classes_out ;
javac_classes_out := classes_out ;
if !buck then (
let classes_out_infer = resolve classes_out ^/ buck_results_dir_name in
(* extend env var args to pass args to children that do not receive the rest args *)
@ -1282,9 +1273,6 @@ let exe_usage (exe : CLOpt.exe) =
You shouldn't need to call this directly."
| Interactive ->
"Usage: interactive ocaml toplevel. To pass infer config options use env variable"
| Java ->
"Usage: InferJava [options]\n\
Translate the given files using javac into infer internal representation for later analysis."
| Print ->
"Usage: InferPrint [options] name1.specs ... namen.specs\n\
Read, convert, and print .specs files. \
@ -1447,6 +1435,7 @@ and infer_cache = !infer_cache
and iphoneos_target_sdk_version = !iphoneos_target_sdk_version
and iterations = !iterations
and java_jar_compiler = !java_jar_compiler
and javac_classes_out = !javac_classes_out
and javac_verbose_out = !verbose_out
and jobs = !jobs
and join_cond = !join_cond

@ -204,10 +204,12 @@ val generated_classes : string option
val headers : bool
val icfg_dotty_outfile : string option
val infer_cache : string option
val init_work_dir : string
val iphoneos_target_sdk_version : string option
val is_originator : bool
val iterations : int
val java_jar_compiler : string option
val javac_classes_out : string
val javac_verbose_out : string
val jobs : int
val join_cond : int

@ -9,6 +9,39 @@
include Core.Std
module Unix_ = struct
let improve f make_arg_sexps =
try f () with
| Unix.Unix_error (e, s, _) ->
let buf = Buffer.create 100 in
let fmt = Format.formatter_of_buffer buf in
Format.pp_set_margin fmt 10000;
Sexp.pp_hum fmt (
Sexp.List (
List.map (make_arg_sexps ())
~f:(fun (name, value) -> Sexp.List [Sexp.Atom name; value])));
Format.pp_print_flush fmt ();
let arg_str = Buffer.contents buf in
raise (Unix.Unix_error (e, s, arg_str))
let create_process_redirect
~prog ~args ?(stdin = Unix.stdin) ?(stdout = Unix.stdout) ?(stderr = Unix.stderr) () =
improve
(fun () ->
let prog_args = Array.of_list (prog :: args) in
Caml.UnixLabels.create_process ~prog ~args:prog_args ~stdin ~stdout ~stderr
|> Pid.of_int)
(fun () ->
[("prog", Sexp.Atom prog);
("args", Sexplib.Conv.sexp_of_list (fun a -> Sexp.Atom a) args)])
let fork_redirect_exec_wait ~prog ~args ?stdin ?stdout ?stderr () =
Unix.waitpid (create_process_redirect ~prog ~args ?stdin ?stdout ?stderr ())
|> Unix.Exit_or_signal.or_error |> ok_exn
end
let ( @ ) = Caml.List.append
(* Use Caml.Set since they are serialized using Marshal, and Core.Std.Set includes the comparison

@ -34,7 +34,6 @@ let log_dir_of_exe (exe : CLOpt.exe) =
| Analyze -> "analyze"
| Clang -> "clang"
| Interactive -> "interactive"
| Java -> "java"
| Print -> "print"
| Toplevel -> "toplevel"

@ -91,6 +91,9 @@ type file_entry =
| Singleton of SourceFile.t
| Duplicate of (string * SourceFile.t) list
type t = string * file_entry String.Map.t * JBasics.ClassSet.t
(* Open the source file and search for the package declaration.
Only the case where the package is declared in a single line is supported *)
let read_package_declaration source_file =
@ -148,8 +151,8 @@ let add_root_path path roots =
String.Set.add roots path
let load_from_verbose_output () =
let file_in = open_in Config.javac_verbose_out in
let load_from_verbose_output javac_verbose_out =
let file_in = open_in javac_verbose_out in
let class_filename_re =
Str.regexp
"\\[wrote RegularFileObject\\[\\(.*\\)\\]\\]" in
@ -263,11 +266,6 @@ let load_from_arguments classes_out_path =
(classpath, search_sources (), classes)
let load_sources_and_classes () =
match Config.generated_classes with
| None -> load_from_verbose_output ()
| Some path -> load_from_arguments path
type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t

@ -32,9 +32,13 @@ type file_entry =
| Singleton of SourceFile.t
| Duplicate of (string * SourceFile.t) list
type t = string * file_entry String.Map.t * JBasics.ClassSet.t
(** load the list of source files and the list of classes from the javac verbose file *)
val load_sources_and_classes : unit ->
string * file_entry String.Map.t * JBasics.ClassSet.t
val load_from_verbose_output : string -> t
(** load the list of source files and the list of classes from Config.generated_classes *)
val load_from_arguments : string -> t
type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t

@ -14,17 +14,6 @@ open Javalib_pack
module L = Logging
let () =
match Config.models_mode, Sys.file_exists Config.models_jar = `Yes with
| true, false ->
()
| false, false ->
failwith "Java model file is required"
| true, true ->
failwith "Not expecting model file when analyzing the models"
| false, true ->
JClasspath.add_models Config.models_jar
let register_perf_stats_report source_file =
let stats_dir = Filename.concat Config.results_dir Config.frontend_stats_dir_name in
@ -134,14 +123,14 @@ let do_all_files classpath sources classes =
do_source_file linereader classes program tenv basename package_opt source_file in
String.Map.iteri
~f:(fun ~key:basename ~data:file_entry ->
match file_entry with
| JClasspath.Singleton source_file ->
translate_source_file basename (None, source_file) source_file
| JClasspath.Duplicate source_files ->
IList.iter
(fun (package, source_file) ->
translate_source_file basename (Some package, source_file) source_file)
source_files)
match file_entry with
| JClasspath.Singleton source_file ->
translate_source_file basename (None, source_file) source_file
| JClasspath.Duplicate source_files ->
IList.iter
(fun (package, source_file) ->
translate_source_file basename (Some package, source_file) source_file)
source_files)
sources;
if Config.dependency_mode then
capture_libs linereader program tenv;
@ -151,9 +140,19 @@ let do_all_files classpath sources classes =
(* loads the source files and translates them *)
let () =
let main load_sources_and_classes =
(match Config.models_mode, Sys.file_exists Config.models_jar = `Yes with
| true, false ->
()
| false, false ->
failwith "Java model file is required"
| true, true ->
failwith "Not expecting model file when analyzing the models"
| false, true ->
JClasspath.add_models Config.models_jar
);
JBasics.set_permissive true;
let classpath, sources, classes = JClasspath.load_sources_and_classes () in
let classpath, sources, classes = Lazy.force load_sources_and_classes in
if String.Map.is_empty sources then
failwith "Failed to load any Java source code"
else

@ -0,0 +1,13 @@
(*
* Copyright (c) 2009 - 2013 Monoidics ltd.
* Copyright (c) 2013 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
val main: JClasspath.t Lazy.t -> unit

@ -0,0 +1,10 @@
(*
* 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.
*)
let main _ = ()

@ -0,0 +1,13 @@
(*
* Copyright (c) 2009 - 2013 Monoidics ltd.
* Copyright (c) 2013 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
val main: 'a -> unit
Loading…
Cancel
Save