Summary: @public This adds a script `inferTraceBugs` to `infer/bin/` that 1. shows the list of bugs found by Infer to the user 2. asks which one to display 3. asks what max level of nested procedure calls to display 4. shows the error trace of that bug with some lines of context in the source code Also has some options to script more easily, for instance when calling it from inside an editor to navigate the sources. Test Plan: infer -o out -- gcc -c hello.c inferTraceBugs -o out also tested on OpenSSL. In emacs, run `M-x compile` from the directory where `infer-out` is, then enter custom compilation command: inferTraceBugs --select 0 --max-level max --no-source Then navigate the trace with `M-g n`.master
parent
c3a1e501bc
commit
044df14616
@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2013-present Facebook. All rights reserved.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Infer imports
|
||||
import utils
|
||||
|
||||
BASE_INDENT = 2
|
||||
JSON_REPORT = 'report.json'
|
||||
# how many lines of context around each report
|
||||
SOURCE_CONTEXT = 2
|
||||
|
||||
|
||||
base_parser = argparse.ArgumentParser(
|
||||
description='Explore the error traces in Infer reports.')
|
||||
base_parser.add_argument('-o', '--out', metavar='<directory>',
|
||||
default=utils.DEFAULT_INFER_OUT, dest='infer_out',
|
||||
action=utils.AbsolutePathAction,
|
||||
help='Set the Infer results directory')
|
||||
base_parser.add_argument('--only-show',
|
||||
action='store_true',
|
||||
help='Show the list of reports and exit')
|
||||
base_parser.add_argument('--no-source',
|
||||
action='store_true',
|
||||
help='Do not print code excerpts')
|
||||
base_parser.add_argument('--select',
|
||||
metavar='N',
|
||||
nargs=1,
|
||||
help='Select bug number N. '
|
||||
'If omitted, prompts you for input.')
|
||||
base_parser.add_argument('--max-level',
|
||||
metavar='N',
|
||||
nargs=1,
|
||||
help='Level of nested procedure calls to show. '
|
||||
'Can be "max", in which case all levels are shown. '
|
||||
'If omitted, prompts you for input.')
|
||||
|
||||
|
||||
def describe_report(report, indent=0):
|
||||
filename = report['file']
|
||||
kind = report['kind']
|
||||
line = report['line']
|
||||
error_type = report['type']
|
||||
msg = utils.remove_bucket(report['qualifier'])
|
||||
return '{0}:{1}: {2}: {3}\n {4}{5}\n'.format(
|
||||
filename,
|
||||
line,
|
||||
kind.lower(),
|
||||
error_type,
|
||||
' ' * indent,
|
||||
msg,
|
||||
)
|
||||
|
||||
|
||||
def show_error_and_exit(err, show_help):
|
||||
print(err)
|
||||
if show_help:
|
||||
print('')
|
||||
base_parser.print_help()
|
||||
exit(1)
|
||||
|
||||
|
||||
class Tracer(object):
|
||||
def __init__(self, args, level=sys.maxsize):
|
||||
self.args = args
|
||||
self.max_level = level
|
||||
|
||||
self.text = ''
|
||||
self.indent = []
|
||||
|
||||
def indent_get(self):
|
||||
indent = ''
|
||||
for i in self.indent:
|
||||
indent += i
|
||||
return indent
|
||||
|
||||
def indent_push(self, n=1):
|
||||
self.indent.append(n * BASE_INDENT * ' ')
|
||||
|
||||
def indent_pop(self):
|
||||
return self.indent.pop()
|
||||
|
||||
def add(self, s):
|
||||
self.text += self.indent_get() + s
|
||||
|
||||
def newline(self):
|
||||
self.text += '\n'
|
||||
|
||||
def build_node_tags(self, node):
|
||||
pass
|
||||
|
||||
def build_source_context(self, source_name, report_line):
|
||||
start_line = max(1, report_line - SOURCE_CONTEXT)
|
||||
# could go beyond last line, checked in the loop
|
||||
end_line = report_line + SOURCE_CONTEXT
|
||||
|
||||
n_length = len(str(end_line))
|
||||
line_number = 1
|
||||
with open(source_name) as source_file:
|
||||
for line in source_file:
|
||||
if start_line <= line_number <= end_line:
|
||||
num = str(line_number).zfill(n_length)
|
||||
caret = ' '
|
||||
if line_number == report_line:
|
||||
caret = '> '
|
||||
self.add(num + ' ' + caret + line)
|
||||
line_number += 1
|
||||
|
||||
def build_node(self, node):
|
||||
if node['level'] > self.max_level:
|
||||
return
|
||||
|
||||
report_line = node['line_number']
|
||||
fname = node['filename']
|
||||
|
||||
self.add('%s:%d: %s\n' % (fname,
|
||||
report_line,
|
||||
node['description']))
|
||||
|
||||
if not self.args.no_source:
|
||||
self.indent_push(node['level'])
|
||||
self.build_source_context(fname, report_line)
|
||||
self.indent_pop()
|
||||
self.newline()
|
||||
|
||||
def build_trace(self, trace):
|
||||
total_nodes = len(trace)
|
||||
hidden_nodes = len([None for n in trace if n['level'] > self.max_level])
|
||||
shown_nodes = total_nodes - hidden_nodes
|
||||
hidden_str = ''
|
||||
all_str = 'all '
|
||||
if hidden_nodes > 0:
|
||||
hidden_str = ' (%d steps too deeply nested)' % hidden_nodes
|
||||
all_str = ''
|
||||
self.add('Showing %s%d steps of the trace%s\n\n'
|
||||
% (all_str, shown_nodes, hidden_str))
|
||||
|
||||
for node in trace:
|
||||
self.build_node(node)
|
||||
|
||||
def build_report(self, report):
|
||||
self.add(describe_report(report))
|
||||
self.newline()
|
||||
|
||||
traces = json.loads(report['trace'])
|
||||
self.build_trace(traces['trace'])
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
class Selector(object):
|
||||
def __init__(self, args, reports):
|
||||
self.args = args
|
||||
|
||||
def has_trace(report):
|
||||
trace = json.loads(report['trace'])
|
||||
return len(trace['trace']) > 0
|
||||
self.reports = [report for report in reports if has_trace(report)]
|
||||
|
||||
def show_choices(self):
|
||||
n = 0
|
||||
n_length = len(str(len(self)))
|
||||
for report in self.reports:
|
||||
print(str(n).rjust(n_length) + '. ' +
|
||||
describe_report(report, n_length + 2))
|
||||
n += 1
|
||||
|
||||
def prompt_number(self):
|
||||
if self.args.select is not None:
|
||||
return self.parse_report_number(self.args.select[0], True)
|
||||
|
||||
self.show_choices()
|
||||
|
||||
report_number = 0
|
||||
if len(self) > 1:
|
||||
report_number_str = raw_input(
|
||||
'Choose report to display (default=0): ')
|
||||
if report_number_str == '':
|
||||
report_number = 0
|
||||
else:
|
||||
report_number = self.parse_report_number(report_number_str)
|
||||
elif len(self) == 1:
|
||||
print('Auto-selecting the only report.')
|
||||
report_number = 0
|
||||
|
||||
return report_number
|
||||
|
||||
def prompt_level(self):
|
||||
if self.args.max_level is not None:
|
||||
return self.parse_max_level(self.args.max_level[0], True)
|
||||
|
||||
max_level_str = raw_input(
|
||||
'Choose maximum level of nested procedures calls (default=max): ')
|
||||
if max_level_str == '':
|
||||
max_level = sys.maxsize
|
||||
else:
|
||||
max_level = self.parse_max_level(max_level_str)
|
||||
|
||||
print('')
|
||||
|
||||
return max_level
|
||||
|
||||
def parse_report_number(self, s, show_help=False):
|
||||
try:
|
||||
n = int(s)
|
||||
except ValueError:
|
||||
show_error_and_exit(
|
||||
'ERROR: integer report number expected',
|
||||
show_help)
|
||||
|
||||
if n >= len(self) or n < 0:
|
||||
show_error_and_exit('ERROR: invalid report number.', show_help)
|
||||
|
||||
return n
|
||||
|
||||
def parse_max_level(self, s, show_help=False):
|
||||
if s == 'max':
|
||||
return sys.maxsize
|
||||
|
||||
try:
|
||||
n = int(s)
|
||||
except ValueError:
|
||||
show_error_and_exit(
|
||||
'ERROR: integer max level or "max" expected',
|
||||
show_help)
|
||||
|
||||
if n < 0:
|
||||
show_error_and_exit('ERROR: invalid max level.', show_help)
|
||||
|
||||
return n
|
||||
|
||||
def __len__(self):
|
||||
return len(self.reports)
|
||||
|
||||
|
||||
def main():
|
||||
args = base_parser.parse_args()
|
||||
|
||||
with open(os.path.join(args.infer_out, JSON_REPORT)) as report_file:
|
||||
reports = json.load(report_file)
|
||||
|
||||
sel = Selector(args, reports)
|
||||
|
||||
if len(sel) == 0:
|
||||
print('No issues found')
|
||||
exit(0)
|
||||
|
||||
if args.only_show:
|
||||
sel.show_choices()
|
||||
exit(0)
|
||||
|
||||
report_number = sel.prompt_number()
|
||||
max_level = sel.prompt_level()
|
||||
|
||||
tracer = Tracer(args, max_level)
|
||||
tracer.build_report(reports[report_number])
|
||||
print(tracer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in new issue