You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
7.5 KiB

#!/usr/bin/env python2.7
#
# 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 = 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 = self.parse_report_number(report_number_str)
elif len(self) == 1:
print('Auto-selecting the only report.')
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()