Skip to content

Commit

Permalink
[feat] Support report annotations and add dynamic analyzer related an…
Browse files Browse the repository at this point in the history
…notations

A plist file may contains a section for report annotations:

```.xml
<key>diagnostics</key>
<array>
  <dict>
    <key>category</key>
    <string>Memory error</string>
    ...
    <dict>
      <key>timestamp</key>
      <string>2000-01-01 10:00</string>
      <key>testsuite</key>
      <string>TS-1</key>
      ...
    </dict>
  </dict>
</array>
```

At the time of creation of this commit there is no analyzer that would
emit a .plist file with such content. But the plan is that sanitizers and
some other dynamic analyzers will provide this information, at least the
report converter could add the timestamp to the .plist files.

The goal of this new feature is to enable storing arbitrary annotations to
the reports. We could have extended the report table with further columns
for each annotation, or we could have joined them in separate tables. Finally
we decided to create a generic "report_annotations" table where the
annotations are collected as (report_id; key; value) tuples. This adds some
complexity when selecting annotations for each report, but this tradeoff is
accepted.

Annotations are untyped labels, but "timestamp" and "testsuite" will be
handled in a specific way in the sense that users can order reports by their
timestamps and can filter reports based on which testsuite resulted them.
This can be useful because in case of dynamic analyzers it is possible that
a report is a consequence of another one. Ordering them by their timestamps
can help in detecting these relationships.

This commit targets the addition of "timestamp" and "testsuite" annotations,
but the implementation is more general than supporting only these two.
This is the reason why the database schema and Thrift API interfaces don't
have the concept of dynamic analysis.
  • Loading branch information
bruntib committed Mar 21, 2023
1 parent 089290f commit 7b0dc2e
Show file tree
Hide file tree
Showing 26 changed files with 830 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ def __init__(
bug_path_events: Optional[List[BugPathEvent]] = None,
bug_path_positions: Optional[List[BugPathPosition]] = None,
notes: Optional[List[BugPathEvent]] = None,
macro_expansions: Optional[List[MacroExpansion]] = None
macro_expansions: Optional[List[MacroExpansion]] = None,
annotations: Optional[Dict[str, str]] = None
):
self.analyzer_result_file_path = analyzer_result_file_path
self.file = file
Expand All @@ -304,6 +305,7 @@ def __init__(
self.analyzer_name = analyzer_name
self.category = category
self.type = type
self.annotations = annotations

self.bug_path_events = bug_path_events \
if bug_path_events is not None else \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ def __create_report(
analyzer_name = self.__get_analyzer_name(checker_name, metadata)
severity = self.get_severity(checker_name)

report_annotation = diag["report-annotation"] \
if "report-annotation" in diag else None

return Report(
analyzer_result_file_path=analyzer_result_file_path,
file=files[location['file']],
Expand All @@ -259,7 +262,8 @@ def __create_report(
bug_path_events=self.__get_bug_path_events(diag, files),
bug_path_positions=self.__get_bug_path_positions(diag, files),
notes=self.__get_notes(diag, files),
macro_expansions=self.__get_macro_expansions(diag, files))
macro_expansions=self.__get_macro_expansions(diag, files),
annotations=report_annotation)

def __get_analyzer_name(
self,
Expand Down
1 change: 1 addition & 0 deletions web/api/codechecker_api_shared.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum ErrorCode {
UNAUTHORIZED, // Authorization denied. User does not have right to perform an action.
API_MISMATCH, // The client attempted to query an API version that is not supported by the server.
SOURCE_FILE, // The client sent a source code which contains errors (e.g.: source code comment errors).
REPORT_FORMAT, // The client sent a report with wrong format (e.g. report annotation has bad type in a .plist)
}

exception RequestFailed {
Expand Down
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion web/api/js/codechecker-api-node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codechecker-api",
"version": "6.52.0",
"version": "6.53.0",
"description": "Generated node.js compatible API stubs for CodeChecker server.",
"main": "lib",
"homepage": "https://github.com/Ericsson/codechecker",
Expand Down
Binary file modified web/api/py/codechecker_api/dist/codechecker_api.tar.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion web/api/py/codechecker_api/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
with open('README.md', encoding='utf-8', errors="ignore") as f:
long_description = f.read()

api_version = '6.52.0'
api_version = '6.53.0'

setup(
name='codechecker_api',
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion web/api/py/codechecker_api_shared/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
with open('README.md', encoding='utf-8', errors="ignore") as f:
long_description = f.read()

api_version = '6.52.0'
api_version = '6.53.0'

setup(
name='codechecker_api_shared',
Expand Down
18 changes: 16 additions & 2 deletions web/api/report_server.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ enum SortType {
REVIEW_STATUS,
DETECTION_STATUS,
BUG_PATH_LENGTH,
TIMESTAMP,
}

enum RunSortType {
Expand All @@ -98,6 +99,11 @@ enum CommentKind {
SYSTEM // System events.
}

struct Pair {
1: string first,
2: string second
}

struct SourceFileData {
1: i64 fileId,
2: string filePath,
Expand Down Expand Up @@ -315,7 +321,10 @@ struct ReportData {
15: i64 bugPathLength, // Length of the bug path.
16: optional ReportDetails details, // Details of the report.
17: optional string analyzerName, // Analyzer name.
18: optional string timeStamp, // Timestamp for dynamic analyzers.
// Report annotations are key-value pairs attached to a report. This is a set
// of custom labels that describe some properties of a report. For example the
// timestamp in case of dynamic analyzers when the report was actually emitted.
18: optional map<string, string> annotations,
}
typedef list<ReportData> ReportDataList

Expand Down Expand Up @@ -364,6 +373,12 @@ struct ReportFilter {
20: optional bool fileMatchesAnyPoint,
// Similar to fileMatchesAnyPoint but for component filtering.
21: optional bool componentMatchesAnyPoint,
// Filter on reports that match some annotations. Annotations are key-value
// pairs, however, as a filter field a list of pairs is required. This way
// several possible values of a key can be provided. For example:
// [(key1, value1), (key1, value2), (key2, value3)] returns reports which
// have "value1" OR "value2" for "key1" AND have "value3" for "key2".
22: optional list<Pair> annotations,
}

struct RunReportCount {
Expand Down Expand Up @@ -575,7 +590,6 @@ service codeCheckerDBAccess {
7: optional bool getDetails)
throws (1: codechecker_api_shared.RequestFailed requestError),


// Count the results separately for multiple runs.
// If an empty run id list is provided the report
// counts will be calculated for all of the available runs.
Expand Down
2 changes: 1 addition & 1 deletion web/codechecker_web/shared/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# The newest supported minor version (value) for each supported major version
# (key) in this particular build.
SUPPORTED_VERSIONS = {
6: 52
6: 53
}

# Used by the client to automatically identify the latest major and minor
Expand Down
56 changes: 54 additions & 2 deletions web/server/codechecker_server/api/mass_store_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@
from ..database.config_db_model import Product
from ..database.database import DBSession
from ..database.run_db_model import AnalysisInfo, AnalyzerStatistic, \
BugPathEvent, BugReportPoint, ExtendedReportData, File, FileContent, \
Report as DBReport, ReviewStatus, Run, RunHistory, RunLock
BugPathEvent, BugReportPoint, ReportAnnotations, ExtendedReportData, \
File, FileContent, Report as DBReport, ReviewStatus, Run, RunHistory, \
RunLock
from ..metadata import checker_is_unavailable, MetadataInfoParser

from .report_server import ThriftRequestHandler
Expand Down Expand Up @@ -941,13 +942,64 @@ def get_missing_file_ids(report: Report) -> List[str]:
review_status, scc, detection_status, detected_at,
run_history_time, analysis_info, analyzer_name, fixed_at)

if report.annotations:
self.__validate_and_add_report_annotations(
session, report_id, report.annotations)

self.__new_report_hashes.add(report.report_hash)
self.__already_added_report_hashes.add(report_path_hash)

LOG.debug("Storing report done. ID=%d", report_id)

return True

def __validate_and_add_report_annotations(
self,
session: DBSession,
report_id: int,
report_annotation: Dict
):
"""
This function checks the format of the annotations. For example a
"timestamp" annotation must be in datetime format. If the format
doesn't match then an exception is thrown. In case of proper format the
annotation is added to the database.
"""
ALLOWED_TYPES = {
"datetime": {
"func": datetime.fromisoformat,
"display": "date-time in ISO format"
},
"string": {
"func": str,
"display": "string"
}
}

ALLOWED_ANNOTATIONS = {
"timestamp": ALLOWED_TYPES["datetime"],
"testsuite": ALLOWED_TYPES["string"],
"testcase": ALLOWED_TYPES["string"]
}

for key, value in report_annotation.items():
try:
ALLOWED_ANNOTATIONS[key]["func"](value)
session.add(ReportAnnotations(report_id, key, value))
except KeyError:
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.REPORT_FORMAT,
f"'{key}' is not an allowed report annotation.",
ALLOWED_ANNOTATIONS.keys())
except ValueError:
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.REPORT_FORMAT,
f"'{value}' has wrong format. '{key}' annotations must be "
f"'{ALLOWED_ANNOTATIONS[key]['display']}'."
)

session.flush()

def __store_reports(
self,
session: DBSession,
Expand Down
Loading

0 comments on commit 7b0dc2e

Please sign in to comment.