forked from Ericsson/codechecker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
report.py
189 lines (147 loc) · 5.79 KB
/
report.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
"""
Parsers for the analyzer output formats (plist ...) should create this
type of Report which will be printed or stored.
"""
from typing import Dict, List
import json
import os
from codechecker_common.logger import get_logger
from codechecker_common.source_code_comment_handler import \
SourceCodeCommentHandler, SpellException
from codechecker_common import util
LOG = get_logger('report')
class Report(object):
"""Represents an analyzer report.
The main section is where the analyzer reported the issue.
The bugpath contains additional locations (and messages) which lead to
the main section.
"""
def __init__(self,
main: Dict,
bugpath: Dict,
files: Dict[int, str],
metadata: Dict[str, str]):
# Dictionary containing checker name, report hash,
# main report position, report message ...
self.__main = main
# Dictionary containing bug path related data
# with control, event ... sections.
self.__bug_path = bugpath
# Dictionary fileid to filepath that bugpath events refer to
self.__files = files
# Can contain the source line where the main section was reported.
self.__source_line = ""
# Dictionary containing metadata information (analyzer name, version).
self.__metadata = metadata
self.__source_code_comments = None
self.__sc_handler = SourceCodeCommentHandler()
@property
def line(self) -> int:
return self.__main['location']['line']
@property
def col(self) -> int:
return self.__main['location']['col']
@property
def description(self) -> str:
return self.__main['description']
@property
def main(self) -> Dict:
return self.__main
@property
def report_hash(self) -> str:
return self.__main['issue_hash_content_of_line_in_context']
@property
def check_name(self) -> str:
return self.__main['check_name']
@property
def bug_path(self) -> Dict:
return self.__bug_path
@property
def notes(self) -> List[str]:
return self.__main.get('notes', [])
@property
def macro_expansions(self) -> List[str]:
return self.__main.get('macro_expansions', [])
@property
def files(self) -> Dict[int, str]:
return self.__files
@property
def file_path(self) -> str:
""" Get the filepath for the main report location. """
return self.files[self.__main['location']['file']]
@property
def source_line(self) -> str:
"""Get the source line for the main location.
If the source line is already set returns that
if not tries to read it from the disk.
"""
if not self.__source_line:
self.__source_line = util.get_line(self.file_path, self.line)
return self.__source_line
@source_line.setter
def source_line(self, sl):
self.__source_line = sl
@property
def metadata(self) -> Dict:
return self.__metadata
@property
def source_code_comments(self):
"""
Get source code comments for the report.
It will read the source file only once.
"""
if self.__source_code_comments is not None:
return self.__source_code_comments
self.__source_code_comments = []
if not os.path.exists(self.file_path):
return self.__source_code_comments
with open(self.file_path, encoding='utf-8', errors='ignore') as sf:
try:
self.__source_code_comments = \
self.__sc_handler.filter_source_line_comments(
sf, self.line, self.check_name)
except SpellException as ex:
LOG.warning("%s contains %s", os.path.basename(self.file_path),
str(ex))
if len(self.__source_code_comments) == 1:
LOG.debug("Report %s is suppressed in code. file: %s Line %s",
self.report_hash, self.file_path, self.line)
elif len(self.__source_code_comments) > 1:
LOG.warning(
"Multiple source code comment can be found "
"for '%s' checker in '%s' at line %s. "
"This bug will not be suppressed!",
self.check_name, self.file_path, self.line)
return self.__source_code_comments
def check_source_code_comments(self, comment_types: List[str]):
"""
True if it doesn't have a source code comment or if every comments have
specified comment types.
"""
if not self.source_code_comments:
return True
return all(c['status'] in comment_types
for c in self.source_code_comments)
def __str__(self):
msg = json.dumps(self.__main, sort_keys=True, indent=2)
msg += str(self.__files)
return msg
def trim_path_prefixes(self, path_prefixes=None):
""" Removes the longest matching leading path from the file paths. """
self.__files = {i: util.trim_path_prefixes(file_path, path_prefixes)
for i, file_path in self.__files.items()}
def to_json(self):
"""Converts to a special json format.
This format is used by the parse command when the reports are printed
to the stdout in json format."""
ret = self.__main
ret["path"] = self.bug_path
ret["files"] = self.files.values()
return ret