зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1473278: Add infer to java-check, install, clear-cache, and print-checks subcommands. r=gps
MozReview-Commit-ID: 5ngZu6lh1wU --HG-- extra : amend_source : b89243dd57777746febd180db7acadb77d3d3a49
This commit is contained in:
Родитель
582e2ce27b
Коммит
24a33b307e
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"infer-blacklist-path-regex": [
|
|
||||||
// This is full of issues, and is a dependency we need to discard
|
|
||||||
// sooner rather than later anyway:
|
|
||||||
"mobile/android/thirdparty/ch/boye/httpclientandroidlib"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1630,7 +1630,7 @@ class StaticAnalysis(MachCommandBase):
|
||||||
"""Utilities for running C++ static analysis checks and format."""
|
"""Utilities for running C++ static analysis checks and format."""
|
||||||
|
|
||||||
# List of file extension to consider (should start with dot)
|
# List of file extension to consider (should start with dot)
|
||||||
_format_include_extensions = ('.cpp', '.c', '.h')
|
_format_include_extensions = ('.cpp', '.c', '.h', '.java')
|
||||||
# File contaning all paths to exclude from formatting
|
# File contaning all paths to exclude from formatting
|
||||||
_format_ignore_file = '.clang-format-ignore'
|
_format_ignore_file = '.clang-format-ignore'
|
||||||
|
|
||||||
|
@ -1712,7 +1712,141 @@ class StaticAnalysis(MachCommandBase):
|
||||||
self.log(logging.WARNING, 'warning_summary',
|
self.log(logging.WARNING, 'warning_summary',
|
||||||
{'count': len(monitor.warnings_db)},
|
{'count': len(monitor.warnings_db)},
|
||||||
'{count} warnings present.')
|
'{count} warnings present.')
|
||||||
|
if rc != 0:
|
||||||
return rc
|
return rc
|
||||||
|
# if we are building firefox for android it might be nice to
|
||||||
|
# also analyze the java code base
|
||||||
|
if self.substs['MOZ_BUILD_APP'] == 'mobile/android':
|
||||||
|
rc = self.check_java(source, jobs, strip, verbose, skip_export=True)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
@StaticAnalysisSubCommand('static-analysis', 'check-java',
|
||||||
|
'Run infer on the java codebase.')
|
||||||
|
@CommandArgument('source', nargs='*', default=['mobile'],
|
||||||
|
help='Source files to be analyzed. '
|
||||||
|
'Can be omitted, in which case the entire code base '
|
||||||
|
'is analyzed. The source argument is ignored if '
|
||||||
|
'there is anything fed through stdin, in which case '
|
||||||
|
'the analysis is only performed on the files changed '
|
||||||
|
'in the patch streamed through stdin. This is called '
|
||||||
|
'the diff mode.')
|
||||||
|
@CommandArgument('--checks', '-c', default=[], metavar='checks', nargs='*',
|
||||||
|
help='Static analysis checks to enable.')
|
||||||
|
@CommandArgument('--jobs', '-j', default='0', metavar='jobs', type=int,
|
||||||
|
help='Number of concurrent jobs to run.'
|
||||||
|
' Default is the number of CPUs.')
|
||||||
|
@CommandArgument('--task', '-t', type=str,
|
||||||
|
default='compileLocalWithGeckoBinariesNoMinApiDebugSources',
|
||||||
|
help='Which gradle tasks to use to compile the java codebase.')
|
||||||
|
def check_java(self, source=['mobile'], jobs=2, strip=1, verbose=False, checks=[],
|
||||||
|
task='compileLocalWithGeckoBinariesNoMinApiDebugSources',
|
||||||
|
skip_export=False):
|
||||||
|
self._set_log_level(verbose)
|
||||||
|
self.log_manager.enable_all_structured_loggers()
|
||||||
|
if self.substs['MOZ_BUILD_APP'] != 'mobile/android':
|
||||||
|
self.log(logging.WARNING, 'static-analysis', {},
|
||||||
|
'Cannot check java source code unless you are building for android!')
|
||||||
|
return 1
|
||||||
|
# if source contains the whole mobile folder, then we just have to
|
||||||
|
# analyze everything
|
||||||
|
check_all = any(i.rstrip(os.sep).split(os.sep)[-1] == 'mobile' for i in source)
|
||||||
|
# gather all java sources from the source variable
|
||||||
|
java_sources = []
|
||||||
|
if not check_all:
|
||||||
|
java_sources = self._get_java_files(source)
|
||||||
|
if not java_sources:
|
||||||
|
return 0
|
||||||
|
if not skip_export:
|
||||||
|
rc = self._build_export(jobs=jobs, verbose=verbose)
|
||||||
|
if rc != 0:
|
||||||
|
return rc
|
||||||
|
rc = self._get_infer(verbose=verbose)
|
||||||
|
if rc != 0:
|
||||||
|
self.log(logging.WARNING, 'static-analysis', {},
|
||||||
|
'This command is only available for linux64!')
|
||||||
|
return rc
|
||||||
|
# which checkers to use, and which folders to exclude
|
||||||
|
all_checkers, third_party_path = self._get_infer_config()
|
||||||
|
checkers, excludes = self._get_infer_args(
|
||||||
|
checks=checks or all_checkers,
|
||||||
|
third_party_path=third_party_path
|
||||||
|
)
|
||||||
|
gradlew = mozpath.join(self.topsrcdir, 'gradlew')
|
||||||
|
# infer capture command
|
||||||
|
capture_cmd = [self._infer_path, 'capture'] + excludes + \
|
||||||
|
['--', gradlew, task]
|
||||||
|
tmp_file, args = self._get_infer_source_args(java_sources)
|
||||||
|
# infer analyze command
|
||||||
|
analysis_cmd = [self._infer_path, 'analyze', '--keep-going'] + \
|
||||||
|
checkers + args
|
||||||
|
# capture, then analyze the sources
|
||||||
|
for args in [[gradlew, 'clean'], capture_cmd, analysis_cmd]:
|
||||||
|
rc = self.run_process(args=args, cwd=self.topsrcdir,
|
||||||
|
pass_thru=True)
|
||||||
|
# if a command fails, break and close the tmp file before returning
|
||||||
|
if rc != 0:
|
||||||
|
break
|
||||||
|
if tmp_file:
|
||||||
|
tmp_file.close()
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def _get_java_files(self, sources):
|
||||||
|
java_sources = []
|
||||||
|
for i in sources:
|
||||||
|
f = mozpath.join(self.topsrcdir, i)
|
||||||
|
if os.path.isdir(f):
|
||||||
|
for root, dirs, files in os.walk(f):
|
||||||
|
dirs.sort()
|
||||||
|
for file in sorted(files):
|
||||||
|
if file.endswith('.java'):
|
||||||
|
java_sources.append(mozpath.join(root, file))
|
||||||
|
elif f.endswith('.java'):
|
||||||
|
java_sources.append(f)
|
||||||
|
return java_sources
|
||||||
|
|
||||||
|
def _get_infer_source_args(self, sources):
|
||||||
|
'''Return the arguments to only analyze <sources>'''
|
||||||
|
if not sources:
|
||||||
|
return (None, [])
|
||||||
|
# create a temporary file in which we place all sources
|
||||||
|
# this is used by the analysis command to only analyze certain files
|
||||||
|
f = tempfile.NamedTemporaryFile()
|
||||||
|
for source in sources:
|
||||||
|
f.write(source+'\n')
|
||||||
|
f.flush()
|
||||||
|
return (f, ['--changed-files-index', f.name])
|
||||||
|
|
||||||
|
def _get_infer_config(self):
|
||||||
|
'''Load the infer config file.'''
|
||||||
|
import yaml
|
||||||
|
checkers = []
|
||||||
|
tp_path = ''
|
||||||
|
with open(mozpath.join(self.topsrcdir, 'tools',
|
||||||
|
'infer', 'config.yaml')) as f:
|
||||||
|
try:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
for item in config['infer_checkers']:
|
||||||
|
if item['publish']:
|
||||||
|
checkers.append(item['name'])
|
||||||
|
tp_path = mozpath.join(self.topsrcdir, config['third_party'])
|
||||||
|
except Exception as e:
|
||||||
|
print('Looks like config.yaml is not valid, so we are unable '
|
||||||
|
'to determine default checkers, and which folder to '
|
||||||
|
'exclude, using defaults provided by infer')
|
||||||
|
return checkers, tp_path
|
||||||
|
|
||||||
|
def _get_infer_args(self, checks, third_party_path):
|
||||||
|
'''Return the arguments which include the checkers <checks>, and
|
||||||
|
excludes all folder in <third_party_path>.'''
|
||||||
|
checkers = ['-a', 'checkers']
|
||||||
|
excludes = []
|
||||||
|
for checker in checks:
|
||||||
|
checkers.append('--' + checker)
|
||||||
|
with open(third_party_path) as f:
|
||||||
|
for line in f:
|
||||||
|
excludes.append('--skip-analysis-in-path')
|
||||||
|
excludes.append(line.strip('\n'))
|
||||||
|
return checkers, excludes
|
||||||
|
|
||||||
def _get_clang_tidy_command(self, checks, header_filter, sources, jobs, fix):
|
def _get_clang_tidy_command(self, checks, header_filter, sources, jobs, fix):
|
||||||
|
|
||||||
|
@ -1746,7 +1880,8 @@ class StaticAnalysis(MachCommandBase):
|
||||||
' this baseline we will test future results.')
|
' this baseline we will test future results.')
|
||||||
@CommandArgument('--intree-tool', '-i', default=False, action='store_true',
|
@CommandArgument('--intree-tool', '-i', default=False, action='store_true',
|
||||||
help='Use a pre-aquired in-tree clang-tidy package.')
|
help='Use a pre-aquired in-tree clang-tidy package.')
|
||||||
@CommandArgument('checker_names', nargs='*', default=[], help='Checkers that are going to be auto-tested.')
|
@CommandArgument('checker_names', nargs='*', default=[],
|
||||||
|
help='Checkers that are going to be auto-tested.')
|
||||||
def autotest(self, verbose=False, dump_results=False, intree_tool=False, checker_names=[]):
|
def autotest(self, verbose=False, dump_results=False, intree_tool=False, checker_names=[]):
|
||||||
# If 'dump_results' is True than we just want to generate the issues files for each
|
# If 'dump_results' is True than we just want to generate the issues files for each
|
||||||
# checker in particulat and thus 'force_download' becomes 'False' since we want to
|
# checker in particulat and thus 'force_download' becomes 'False' since we want to
|
||||||
|
@ -1793,7 +1928,8 @@ class StaticAnalysis(MachCommandBase):
|
||||||
rc = self._get_clang_tools(force=force_download, verbose=verbose)
|
rc = self._get_clang_tools(force=force_download, verbose=verbose)
|
||||||
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
self.log(logging.ERROR, 'ERROR: static-analysis', {}, 'clang-tidy unable to locate package.')
|
self.log(logging.ERROR, 'ERROR: static-analysis', {},
|
||||||
|
'clang-tidy unable to locate package.')
|
||||||
return self.TOOLS_FAILED_DOWNLOAD
|
return self.TOOLS_FAILED_DOWNLOAD
|
||||||
|
|
||||||
self._clang_tidy_base_path = mozpath.join(self.topsrcdir, "tools", "clang-tidy")
|
self._clang_tidy_base_path = mozpath.join(self.topsrcdir, "tools", "clang-tidy")
|
||||||
|
@ -1805,7 +1941,8 @@ class StaticAnalysis(MachCommandBase):
|
||||||
platform, _ = self.platform
|
platform, _ = self.platform
|
||||||
|
|
||||||
if platform not in config['platforms']:
|
if platform not in config['platforms']:
|
||||||
self.log(logging.ERROR, 'static-analysis', {},"RUNNING: clang-tidy autotest for platform {} not supported.".format(platform))
|
self.log(logging.ERROR, 'static-analysis', {},
|
||||||
|
"RUNNING: clang-tidy autotest for platform {} not supported.".format(platform))
|
||||||
return TOOLS_UNSUPORTED_PLATFORM
|
return TOOLS_UNSUPORTED_PLATFORM
|
||||||
|
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
@ -1875,11 +2012,11 @@ class StaticAnalysis(MachCommandBase):
|
||||||
@StaticAnalysisSubCommand('static-analysis', 'install',
|
@StaticAnalysisSubCommand('static-analysis', 'install',
|
||||||
'Install the static analysis helper tool')
|
'Install the static analysis helper tool')
|
||||||
@CommandArgument('source', nargs='?', type=str,
|
@CommandArgument('source', nargs='?', type=str,
|
||||||
help='Where to fetch a local archive containing the static-analysis and format helper tool.'
|
help='Where to fetch a local archive containing the static-analysis and '
|
||||||
'It will be installed in ~/.mozbuild/clang-tools/.'
|
'format helper tool.'
|
||||||
'Can be omitted, in which case the latest clang-tools '
|
'It will be installed in ~/.mozbuild/clang-tools and ~/.mozbuild/infer.'
|
||||||
' helper for the platform would be automatically '
|
'Can be omitted, in which case the latest clang-tools and infer '
|
||||||
'detected and installed.')
|
'helper for the platform would be automatically detected and installed.')
|
||||||
@CommandArgument('--skip-cache', action='store_true',
|
@CommandArgument('--skip-cache', action='store_true',
|
||||||
help='Skip all local caches to force re-fetching the helper tool.',
|
help='Skip all local caches to force re-fetching the helper tool.',
|
||||||
default=False)
|
default=False)
|
||||||
|
@ -1887,6 +2024,10 @@ class StaticAnalysis(MachCommandBase):
|
||||||
self._set_log_level(verbose)
|
self._set_log_level(verbose)
|
||||||
rc = self._get_clang_tools(force=True, skip_cache=skip_cache,
|
rc = self._get_clang_tools(force=True, skip_cache=skip_cache,
|
||||||
source=source, verbose=verbose)
|
source=source, verbose=verbose)
|
||||||
|
if rc == 0:
|
||||||
|
# XXX ignore the return code because if it fails or not, infer is
|
||||||
|
# not mandatory, but clang-tidy is
|
||||||
|
self._get_infer(force=True, skip_cache=skip_cache, verbose=verbose)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
@StaticAnalysisSubCommand('static-analysis', 'clear-cache',
|
@StaticAnalysisSubCommand('static-analysis', 'clear-cache',
|
||||||
|
@ -1895,20 +2036,31 @@ class StaticAnalysis(MachCommandBase):
|
||||||
self._set_log_level(verbose)
|
self._set_log_level(verbose)
|
||||||
rc = self._get_clang_tools(force=True, download_if_needed=True, skip_cache=True,
|
rc = self._get_clang_tools(force=True, download_if_needed=True, skip_cache=True,
|
||||||
verbose=verbose)
|
verbose=verbose)
|
||||||
|
if rc == 0:
|
||||||
|
self._get_infer(force=True, download_if_needed=True, skip_cache=True,
|
||||||
|
verbose=verbose)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return rc
|
return rc
|
||||||
|
return self._artifact_manager.artifact_clear_cache()
|
||||||
self._artifact_manager.artifact_clear_cache()
|
|
||||||
|
|
||||||
@StaticAnalysisSubCommand('static-analysis', 'print-checks',
|
@StaticAnalysisSubCommand('static-analysis', 'print-checks',
|
||||||
'Print a list of the static analysis checks performed by default')
|
'Print a list of the static analysis checks performed by default')
|
||||||
def print_checks(self, verbose=False):
|
def print_checks(self, verbose=False):
|
||||||
self._set_log_level(verbose)
|
self._set_log_level(verbose)
|
||||||
rc = self._get_clang_tools(verbose=verbose)
|
rc = self._get_clang_tools(verbose=verbose)
|
||||||
|
if rc == 0:
|
||||||
|
rc = self._get_infer(verbose=verbose)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return rc
|
return rc
|
||||||
args = [self._clang_tidy_path, '-list-checks', '-checks=%s' % self._get_checks()]
|
args = [self._clang_tidy_path, '-list-checks', '-checks=%s' % self._get_checks()]
|
||||||
return self._run_command_in_objdir(args=args, pass_thru=True)
|
rc = self._run_command_in_objdir(args=args, pass_thru=True)
|
||||||
|
if rc != 0:
|
||||||
|
return rc
|
||||||
|
checkers, _ = self._get_infer_config()
|
||||||
|
print('Infer checks:')
|
||||||
|
for checker in checkers:
|
||||||
|
print(' '*4 + checker)
|
||||||
|
return 0
|
||||||
|
|
||||||
@Command('clang-format', category='misc', description='Run clang-format on current changes')
|
@Command('clang-format', category='misc', description='Run clang-format on current changes')
|
||||||
@CommandArgument('--show', '-s', action='store_true', default=False,
|
@CommandArgument('--show', '-s', action='store_true', default=False,
|
||||||
|
@ -1939,7 +2091,8 @@ class StaticAnalysis(MachCommandBase):
|
||||||
test_file_path_cpp = test_file_path + '.cpp'
|
test_file_path_cpp = test_file_path + '.cpp'
|
||||||
test_file_path_json = test_file_path + '.json'
|
test_file_path_json = test_file_path + '.json'
|
||||||
|
|
||||||
self.log(logging.INFO, 'static-analysis', {},"RUNNING: clang-tidy checker {}.".format(check))
|
self.log(logging.INFO, 'static-analysis', {},
|
||||||
|
"RUNNING: clang-tidy checker {}.".format(check))
|
||||||
|
|
||||||
# Verify if this checker actually exists
|
# Verify if this checker actually exists
|
||||||
if not check in self._clang_tidy_checks:
|
if not check in self._clang_tidy_checks:
|
||||||
|
@ -2231,6 +2384,47 @@ class StaticAnalysis(MachCommandBase):
|
||||||
args += [':({0}){1}'.format(','.join(magics), pattern)]
|
args += [':({0}){1}'.format(','.join(magics), pattern)]
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
def _get_infer(self, force=False, skip_cache=False,
|
||||||
|
download_if_needed=True, verbose=False):
|
||||||
|
rc, config, _ = self._get_config_environment()
|
||||||
|
if rc != 0:
|
||||||
|
return rc
|
||||||
|
infer_path = mozpath.join(self._mach_context.state_dir, 'infer')
|
||||||
|
self._infer_path = mozpath.join(infer_path, 'infer', 'bin',
|
||||||
|
'infer' +
|
||||||
|
config.substs.get('BIN_SUFFIX', ''))
|
||||||
|
if os.path.exists(self._infer_path) and not force:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
if os.path.isdir(infer_path) and download_if_needed:
|
||||||
|
# The directory exists, perhaps it's corrupted? Delete it
|
||||||
|
# and start from scratch.
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(infer_path)
|
||||||
|
return self._get_infer(force=force, skip_cache=skip_cache,
|
||||||
|
verbose=verbose,
|
||||||
|
download_if_needed=download_if_needed)
|
||||||
|
os.mkdir(infer_path)
|
||||||
|
self._artifact_manager = PackageFrontend(self._mach_context)
|
||||||
|
if not download_if_needed:
|
||||||
|
return 0
|
||||||
|
job, _ = self.platform
|
||||||
|
if job != 'linux64':
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
job += '-infer'
|
||||||
|
# We want to unpack data in the infer mozbuild folder
|
||||||
|
currentWorkingDir = os.getcwd()
|
||||||
|
os.chdir(infer_path)
|
||||||
|
rc = self._artifact_manager.artifact_toolchain(verbose=verbose,
|
||||||
|
skip_cache=skip_cache,
|
||||||
|
from_build=[job],
|
||||||
|
no_unpack=False,
|
||||||
|
retry=0)
|
||||||
|
# Change back the cwd
|
||||||
|
os.chdir(currentWorkingDir)
|
||||||
|
return rc
|
||||||
|
|
||||||
def _run_clang_format_diff(self, clang_format_diff, clang_format, show):
|
def _run_clang_format_diff(self, clang_format_diff, clang_format, show):
|
||||||
# Run clang-format on the diff
|
# Run clang-format on the diff
|
||||||
# Note that this will potentially miss a lot things
|
# Note that this will potentially miss a lot things
|
||||||
|
@ -2532,4 +2726,3 @@ class Analyze(MachCommandBase):
|
||||||
res = 'Please make sure you have a local tup db *or* specify the location with --path.'
|
res = 'Please make sure you have a local tup db *or* specify the location with --path.'
|
||||||
print ('Could not find a valid tup db in ' + path, res, sep='\n')
|
print ('Could not find a valid tup db in ' + path, res, sep='\n')
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
target: obj-x86_64-pc-linux-gnu
|
||||||
|
# It is used by 'mach static-analysis' and 'mozreview static-analysis bot'
|
||||||
|
# in order to have consistency across the used checkers.
|
||||||
|
platforms:
|
||||||
|
- linux64
|
||||||
|
infer_checkers:
|
||||||
|
- name: check-nullable
|
||||||
|
publish: !!bool yes
|
||||||
|
- name: eradicate
|
||||||
|
publish: !!bool no
|
||||||
|
- name: quandary
|
||||||
|
publish: !!bool yes
|
||||||
|
- name: starvation
|
||||||
|
publish: !!bool yes
|
||||||
|
- name: litho
|
||||||
|
publish: !!bool yes
|
||||||
|
- name: racerd
|
||||||
|
publish: !!bool yes
|
||||||
|
- name: liveness
|
||||||
|
publish: !!bool yes
|
||||||
|
# Third party files from mozilla-central
|
||||||
|
third_party: tools/rewriting/ThirdPartyPaths.txt
|
Загрузка…
Ссылка в новой задаче