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/InferAnalyze
/infer/bin/InferClang /infer/bin/InferClang
/infer/bin/InferClang++ /infer/bin/InferClang++
/infer/bin/InferJava
/infer/bin/InferPrint /infer/bin/InferPrint
/infer/bin/InferUnit /infer/bin/InferUnit

@ -26,7 +26,6 @@ def java_library(
'--classpath', '$(classpath :{})'.format(compile_name), '--classpath', '$(classpath :{})'.format(compile_name),
'--sourcepath', '$(location :{})'.format(export_srcs_name), '--sourcepath', '$(location :{})'.format(export_srcs_name),
'--generated-classes', '$(location :{})'.format(compile_name), '--generated-classes', '$(location :{})'.format(compile_name),
'--', 'genrule'
]), ]),
out = 'infer_out', 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. 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. *InferClang* : Binary containing the clang frontend.
*InferAnalyze* : Binary containing the backend of Infer that performs the analysis. *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 \ @for i in infer/lib/java/*.jar; do \
$(INSTALL_DATA) -C $$i $(DESTDIR)$(libdir)/infer/$$i; \ $(INSTALL_DATA) -C $$i $(DESTDIR)$(libdir)/infer/$$i; \
done done
$(INSTALL_PROGRAM) -C $(INFERJAVA_BIN) $(DESTDIR)$(libdir)/infer/infer/bin/
$(INSTALL_PROGRAM) -C $(LIB_DIR)/wrappers/javac \ $(INSTALL_PROGRAM) -C $(LIB_DIR)/wrappers/javac \
$(DESTDIR)$(libdir)/infer/infer/lib/wrappers/ $(DESTDIR)$(libdir)/infer/infer/lib/wrappers/
endif endif

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

@ -35,11 +35,8 @@ CMD_MARKER = '--'
# All supported commands should be listed here # All supported commands should be listed here
MODULE_TO_COMMAND = { MODULE_TO_COMMAND = {
'ant': ['ant'], 'ant': ['ant'],
'analyze': ['analyze'],
'buck': ['buck'], 'buck': ['buck'],
'gradle': ['gradle', 'gradlew'], 'gradle': ['gradle', 'gradlew'],
'javac': ['javac'],
'java': ['java'],
'make': make.SUPPORTED_COMMANDS, 'make': make.SUPPORTED_COMMANDS,
'xcodebuild': ['xcodebuild'], 'xcodebuild': ['xcodebuild'],
'mvn': ['mvn'], 'mvn': ['mvn'],
@ -143,18 +140,7 @@ def main():
args = global_argparser.parse_args(to_parse) 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: 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) utils.configure_logging(args)
try: try:
logging.info('output of locale.getdefaultlocale(): %s', 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 # Increase the limit of the CSV parser to sys.maxlimit
csv.field_size_limit(sys.maxsize) csv.field_size_limit(sys.maxsize)
INFER_ANALYZE_BINARY = 'InferAnalyze'
base_parser = argparse.ArgumentParser(add_help=False) base_parser = argparse.ArgumentParser(add_help=False)
base_group = base_parser.add_argument_group('global arguments') 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', base_group.add_argument('-r', '--reactive', action='store_true',
help='''Analyze in reactive propagation mode help='''Analyze in reactive propagation mode
starting from changed files.''') 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', base_group.add_argument('--debug-exceptions', action='store_true',
help='''Generate lightweight debugging information: help='''Generate lightweight debugging information:
just print the internal exceptions during analysis''') 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', infer_group.add_argument('--java-jar-compiler',
metavar='<file>') 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: if argument_start_pattern in line:
collect = True collect = True
if javac_arguments != []: if javac_arguments != []:
capture = jwlib.create_infer_command(self.args, capture = jwlib.create_infer_command(javac_arguments)
javac_arguments)
calls.append(capture) calls.append(capture)
javac_arguments = [] javac_arguments = []
if collect: if collect:
@ -72,7 +71,7 @@ class AntCapture:
arg = self.remove_quotes(content) arg = self.remove_quotes(content)
javac_arguments.append(arg) javac_arguments.append(arg)
if javac_arguments != []: if javac_arguments != []:
capture = jwlib.create_infer_command(self.args, javac_arguments) capture = jwlib.create_infer_command(javac_arguments)
calls.append(capture) calls.append(capture)
javac_arguments = [] javac_arguments = []
return calls return calls

@ -76,7 +76,7 @@ class GradleCapture:
sources.write('\n'.join(map(utils.encode, java_files))) sources.write('\n'.join(map(utils.encode, java_files)))
sources.flush() sources.flush()
java_args.append('@' + sources.name) java_args.append('@' + sources.name)
capture = jwlib.create_infer_command(self.args, java_args) capture = jwlib.create_infer_command(java_args)
calls.append(capture) calls.append(capture)
return calls 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: if options_next:
# line has format [Debug] <space separated options> # line has format [Debug] <space separated options>
javac_args = line.split(' ')[1:] + files_to_compile 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) calls.append(capture)
options_next = False options_next = False
files_to_compile = [] files_to_compile = []

@ -11,186 +11,40 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import argparse import logging
import codecs
import os import os
import subprocess import subprocess
import tempfile from . import config
import time
from . import analyze, config, utils
# javac options class InferJavacCapture():
parser = argparse.ArgumentParser()
current_directory = utils.decode(os.getcwd()) def __init__(self, javac_args):
self.javac_args = javac_args
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 start(self): def start(self):
self._compile() infer = os.path.join(config.BIN_DIRECTORY, 'infer')
if self.args.analyzer == config.ANALYZER_COMPILE: # pass --continue to prevent removing the results-dir
return os.EX_OK cmd = [
infer,
self._run_infer_frontend() '--analyzer', 'capture',
if self.args.analyzer == config.ANALYZER_CAPTURE: '--continue',
return os.EX_OK '--', 'javac'
] + self.javac_args
self.analyze_and_report() try:
self._close() return subprocess.check_call(cmd)
except Exception as e:
return os.EX_OK print('Failed to execute:', ' '.join(cmd))
raise e
def _run_infer_frontend(self):
infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')]
def _get_javac_args(javac_args):
infer_cmd += [ # replace any -g:.* flag with -g to preserve debugging symbols
'-verbose_out', self.javac.verbose_out, args = map(lambda arg: '-g' if '-g:' in arg else arg, javac_args)
] # skip -Werror
args = filter(lambda arg: arg != '-Werror', args)
if self.args.debug: return args
infer_cmd.append('-debug')
if self.args.analyzer == config.ANALYZER_TRACING:
infer_cmd.append('-tracing') def create_infer_command(javac_args):
if self.args.android_harness: return InferJavacCapture(_get_javac_args(javac_args))
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)

@ -116,14 +116,16 @@ let get_overriden_method tenv pname_java => {
/** Serializer for type environments */ /** Serializer for type environments */
let tenv_serializer: Serialization.serializer t = Serialization.create_serializer Serialization.tenv_key; let tenv_serializer: Serialization.serializer t = Serialization.create_serializer Serialization.tenv_key;
let global_tenv: Lazy.t (option t) = let global_tenv: ref (option t) = ref None;
lazy (Serialization.from_file tenv_serializer DB.global_tenv_fname);
/** Load a type environment from a file */ /** Load a type environment from a file */
let load_from_file (filename: DB.filename) :option t => let load_from_file (filename: DB.filename) :option t =>
if (filename == DB.global_tenv_fname) { 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 { } else {
Serialization.from_file tenv_serializer filename 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 */ /** Save a type environment into a file */
let store_to_file (filename: DB.filename) (tenv: t) => { 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; Serialization.to_file tenv_serializer filename tenv;
if Config.debug_mode { if Config.debug_mode {
let debug_filename = DB.filename_to_string (DB.filename_add_suffix filename ".debug"); 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 JAVA_SOURCES = java
INFERJAVA_MAIN = $(JAVA_SOURCES)/jMain
#### Clang declarations #### #### Clang declarations ####
CLANG_SOURCES = clang CLANG_SOURCES = clang
@ -139,7 +137,6 @@ INFER_BASE_TARGETS = \
$(INFERUNIT_MAIN).native $(INFERUNIT_MAIN).native
INFER_ALL_TARGETS = $(INFER_BASE_TARGETS) \ INFER_ALL_TARGETS = $(INFER_BASE_TARGETS) \
$(INFERJAVA_MAIN).native \
$(INFERCLANG_MAIN).native \ $(INFERCLANG_MAIN).native \
# configure-aware ocamlbuild commands and targets # configure-aware ocamlbuild commands and targets
@ -148,8 +145,9 @@ INFER_CONFIG_TARGETS = $(INFER_BASE_TARGETS)
ifeq ($(BUILD_JAVA_ANALYZERS),yes) ifeq ($(BUILD_JAVA_ANALYZERS),yes)
OCAMLBUILD_CONFIG += $(JAVA_OCAMLBUILD_OPTIONS) OCAMLBUILD_CONFIG += $(JAVA_OCAMLBUILD_OPTIONS)
INFER_CONFIG_TARGETS += $(INFERJAVA_MAIN).native
DEPENDENCIES += java DEPENDENCIES += java
else
DEPENDENCIES += java_stubs
endif endif
ifeq ($(BUILD_C_ANALYZERS),yes) ifeq ($(BUILD_C_ANALYZERS),yes)
INFER_CONFIG_TARGETS += $(INFERCLANG_MAIN).native 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)/$(INFERANALYZE_MAIN).native $(INFERANALYZE_BIN)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERPRINT_MAIN).native $(INFERPRINT_BIN) $(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERPRINT_MAIN).native $(INFERPRINT_BIN)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERUNIT_MAIN).native $(INFERUNIT_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) ifeq ($(BUILD_C_ANALYZERS),yes)
$(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERCLANG_MAIN).native $(INFERCLANG_BIN) $(INSTALL_PROGRAM) $(INFER_BUILD_DIR)/$(INFERCLANG_MAIN).native $(INFERCLANG_BIN)
endif endif
@ -384,7 +379,6 @@ clean:
$(REMOVE) backend/jsonbug_{j,t}.ml{,i} $(REMOVE) backend/jsonbug_{j,t}.ml{,i}
$(REMOVE) checkers/stacktree_{j,t}.ml{,i} $(REMOVE) checkers/stacktree_{j,t}.ml{,i}
$(REMOVE) $(INFER_BIN) $(INFERANALYZE_BIN) $(INFERPRINT_BIN) $(REMOVE) $(INFER_BIN) $(INFERANALYZE_BIN) $(INFERPRINT_BIN)
$(REMOVE) $(INFERJAVA_BIN) $(INFERCLANG_BIN)
$(REMOVE) $(INFERUNIT_BIN) $(CHECKCOPYRIGHT_BIN) $(REMOVE) $(INFERUNIT_BIN) $(CHECKCOPYRIGHT_BIN)
$(REMOVE) $(CLANG_ATDGEN_STUBS) $(REMOVE) $(CLANG_ATDGEN_STUBS)
$(REMOVE) $(INFER_CLANG_FCP_MIRRORED_FILES) $(REMOVE) $(INFER_CLANG_FCP_MIRRORED_FILES)

@ -14,6 +14,7 @@ open! IStd
module CLOpt = CommandLineOption module CLOpt = CommandLineOption
module L = Logging module L = Logging
module F = Format
let rec rmtree name = let rec rmtree name =
@ -38,15 +39,13 @@ let rec rmtree name =
type build_mode = type build_mode =
| Analyze | Ant | Buck | Gradle | Java | Javac | Make | Mvn | Ndk | Analyze | Ant | Buck | Gradle | Java | Javac | Make | Mvn | Ndk | Xcode
| Xcode | Genrule
let build_mode_of_string path = let build_mode_of_string path =
match Filename.basename path with match Filename.basename path with
| "analyze" -> Analyze | "analyze" -> Analyze
| "ant" -> Ant | "ant" -> Ant
| "buck" -> Buck | "buck" -> Buck
| "genrule" -> Genrule
| "gradle" | "gradlew" -> Gradle | "gradle" | "gradlew" -> Gradle
| "java" -> Java | "java" -> Java
| "javac" -> Javac | "javac" -> Javac
@ -60,7 +59,6 @@ let string_of_build_mode = function
| Analyze -> "analyze" | Analyze -> "analyze"
| Ant -> "ant" | Ant -> "ant"
| Buck -> "buck" | Buck -> "buck"
| Genrule -> "genrule"
| Gradle -> "gradle" | Gradle -> "gradle"
| Java -> "java" | Java -> "java"
| Javac -> "javac" | Javac -> "javac"
@ -74,6 +72,7 @@ let remove_results_dir () =
rmtree Config.results_dir rmtree Config.results_dir
let create_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.captured_dir_name) ;
Unix.mkdir_p (Config.results_dir ^/ Config.specs_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, _, _) -> () with Unix.Unix_error (Unix.EEXIST, _, _) -> ()
let run_command ~prog ~args after_wait = let run_command ~prog ~args cleanup =
let status = Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ()) in Unix.waitpid (Unix.fork_exec ~prog ~args:(prog :: args) ())
after_wait status ; |> fun status
match status with -> cleanup status
| Ok () -> () ; ok_exn (Unix.Exit_or_signal.or_error status)
| 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_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 () = let check_xcpretty () =
match Unix.system "xcpretty --version" with 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 let compilation_database = CompilationDatabase.from_json_files db_files in
CaptureCompilationDatabase.capture_files_in_database compilation_database CaptureCompilationDatabase.capture_files_in_database compilation_database
let capture build_cmd = function let capture build_cmd build_mode =
| Analyze when not (List.is_empty !Config.clang_compilation_db_files) -> 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 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"; L.stdout "Capturing using Buck's compilation database...@\n";
let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck () in let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_buck () in
capture_with_compilation_database json_cdb capture_with_compilation_database json_cdb
| Genrule -> | (Java | Javac), _ ->
L.stdout "Capturing for Buck genrule compatibility...@\n"; let verbose_out_file = run_javac build_mode build_cmd in
let infer_java = Config.bin_dir ^/ "InferJava" in if Config.analyzer <> Config.Compile then
run_command ~prog:infer_java ~args:[] (fun _ -> ()) JMain.main (lazy (JClasspath.load_from_verbose_output verbose_out_file)) ;
| Xcode when Config.xcpretty -> Unix.unlink verbose_out_file
| Xcode, _ when Config.xcpretty ->
L.stdout "Capturing using xcpretty...@\n"; L.stdout "Capturing using xcpretty...@\n";
check_xcpretty (); check_xcpretty ();
let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_xcodebuild () in let json_cdb = CaptureCompilationDatabase.get_compilation_database_files_xcodebuild () in
capture_with_compilation_database json_cdb capture_with_compilation_database json_cdb
| build_mode -> | build_mode, _ ->
L.stdout "Capturing in %s mode...@." (string_of_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 in_buck_mode = build_mode = Buck in
let infer_py = Config.lib_dir ^/ "python" ^/ "infer.py" 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] @ ["--project-root"; Config.project_root] @
(if not Config.reactive_mode then [] else (if not Config.reactive_mode then [] else
["--reactive"]) @ ["--reactive"]) @
(if not Config.continue_capture then [] else
["--continue"]) @
"--out" :: Config.results_dir :: "--out" :: Config.results_dir ::
(match Config.xcode_developer_dir with None -> [] | Some d -> (match Config.xcode_developer_dir with None -> [] | Some d ->
["--xcode-developer-dir"; d]) @ ["--xcode-developer-dir"; d]) @
@ -237,7 +279,7 @@ let run_parallel_analysis () =
) (fun _ -> ()) ) (fun _ -> ())
let execute_analyze () = let execute_analyze () =
if Config.jobs = 1 then if Config.jobs = 1 || Config.cluster_cmdline <> None then
InferAnalyze.main "" InferAnalyze.main ""
else else
run_parallel_analysis () 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 (* 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. *) 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 ( if (Sys.file_exists Config.(results_dir ^/ captured_dir_name)) <> `Yes then (
L.stderr "There was nothing to analyze, exiting" ; L.stderr "There was nothing to analyze, exiting" ;
Config.print_usage_exit () Config.print_usage_exit ()
@ -299,7 +338,7 @@ let fail_on_issue_epilogue () =
let () = let () =
let build_cmd = IList.rev Config.rest in let build_cmd = IList.rev Config.rest in
let build_mode = match build_cmd with path :: _ -> build_mode_of_string path | [] -> Analyze 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 () ; 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 *)

@ -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 (** 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. *) 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. *) (** Association list of executable (base)names to their [exe]s. *)
let exes = [ let exes = [
("InferAnalyze", Analyze); ("InferAnalyze", Analyze);
("InferClang", Clang); ("InferClang", Clang);
("InferJava", Java);
("InferPrint", Print); ("InferPrint", Print);
("infer", Toplevel); ("infer", Toplevel);
("interactive", Interactive); ("interactive", Interactive);
@ -35,7 +34,7 @@ let exe_name =
fun exe -> IList.assoc (=) exe exe_to_name fun exe -> IList.assoc (=) exe exe_to_name
let frontend_exes = [Clang; Java] let frontend_exes = [Clang]
type desc = { type desc = {
long: string; short: string; meta: string; doc: string; spec: Arg.spec; 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 ( if current_exe = Toplevel then (
add_to_curr_speclist ~header:"Analysis (backend) options" Analyze; add_to_curr_speclist ~header:"Analysis (backend) options" Analyze;
add_to_curr_speclist ~header:"Clang frontend options" Clang; add_to_curr_speclist ~header:"Clang frontend options" Clang;
add_to_curr_speclist ~header:"Java frontend options" Java;
) )
; ;
assert( check_no_duplicates !curr_speclist ) 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 exe_name = Sys.executable_name in
let should_parse_cl_args = match current_exe with let should_parse_cl_args = match current_exe with
| Clang | Interactive -> false | Clang | Interactive -> false
| Analyze | Java | Print | Toplevel -> true in | Analyze | Print | Toplevel -> 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

@ -11,7 +11,7 @@
open! IStd 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. *) (** Association list of executable (base)names to their [exe]s. *)
val exes : (string * exe) list val exes : (string * exe) list

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

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

@ -9,6 +9,39 @@
include Core.Std 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 let ( @ ) = Caml.List.append
(* Use Caml.Set since they are serialized using Marshal, and Core.Std.Set includes the comparison (* 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" | Analyze -> "analyze"
| Clang -> "clang" | Clang -> "clang"
| Interactive -> "interactive" | Interactive -> "interactive"
| Java -> "java"
| Print -> "print" | Print -> "print"
| Toplevel -> "toplevel" | Toplevel -> "toplevel"

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

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

@ -14,17 +14,6 @@ open Javalib_pack
module L = Logging 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 register_perf_stats_report source_file =
let stats_dir = Filename.concat Config.results_dir Config.frontend_stats_dir_name in 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 do_source_file linereader classes program tenv basename package_opt source_file in
String.Map.iteri String.Map.iteri
~f:(fun ~key:basename ~data:file_entry -> ~f:(fun ~key:basename ~data:file_entry ->
match file_entry with match file_entry with
| JClasspath.Singleton source_file -> | JClasspath.Singleton source_file ->
translate_source_file basename (None, source_file) source_file translate_source_file basename (None, source_file) source_file
| JClasspath.Duplicate source_files -> | JClasspath.Duplicate source_files ->
IList.iter IList.iter
(fun (package, source_file) -> (fun (package, source_file) ->
translate_source_file basename (Some package, source_file) source_file) translate_source_file basename (Some package, source_file) source_file)
source_files) source_files)
sources; sources;
if Config.dependency_mode then if Config.dependency_mode then
capture_libs linereader program tenv; capture_libs linereader program tenv;
@ -151,9 +140,19 @@ let do_all_files classpath sources classes =
(* loads the source files and translates them *) (* 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; 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 if String.Map.is_empty sources then
failwith "Failed to load any Java source code" failwith "Failed to load any Java source code"
else 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