diff --git a/analyzer/codechecker_analyzer/cmd/check.py b/analyzer/codechecker_analyzer/cmd/check.py index ced027255e..e116e041e0 100644 --- a/analyzer/codechecker_analyzer/cmd/check.py +++ b/analyzer/codechecker_analyzer/cmd/check.py @@ -24,7 +24,7 @@ analyzer_config, checker_config from codechecker_common import arg, cmd_config, logger -from codechecker_report_converter.source_code_comment_handler import \ +from codechecker_common.source_code_comment_handler import \ REVIEW_STATUS_VALUES from codechecker_analyzer.cmd.analyze import \ diff --git a/analyzer/codechecker_analyzer/cmd/parse.py b/analyzer/codechecker_analyzer/cmd/parse.py index 3574e95f41..458be06e2b 100644 --- a/analyzer/codechecker_analyzer/cmd/parse.py +++ b/analyzer/codechecker_analyzer/cmd/parse.py @@ -25,15 +25,16 @@ from codechecker_report_converter.report.output.html import \ html as report_to_html from codechecker_report_converter.report.statistics import Statistics -from codechecker_report_converter.source_code_comment_handler import \ - REVIEW_STATUS_VALUES from codechecker_analyzer import analyzer_context, suppress_handler from codechecker_common import arg, logger, cmd_config +from codechecker_common.review_status_handler import ReviewStatusHandler from codechecker_common.skiplist_handler import SkipListHandler, \ SkipListHandlers +from codechecker_common.source_code_comment_handler import \ + REVIEW_STATUS_VALUES from codechecker_common.util import load_json @@ -384,6 +385,7 @@ def get_output_file_path(default_file_name: str) -> Optional[str]: processed_path_hashes = set() processed_file_paths = set() print_steps = 'print_steps' in args + review_status_handler = ReviewStatusHandler() html_builder: Optional[report_to_html.HtmlBuilder] = None if export == 'html': @@ -413,6 +415,20 @@ def get_output_file_path(default_file_name: str) -> Optional[str]: reports = report_file.get_reports( file_path, context.checker_labels, file_cache) + for report in reports: + try: + # TODO: skip_handler is used later in reports_helper.skip() + # too. However, skipped reports shouldn't check source code + # comments because they potentially raise an exception. + # Skipped files shouldn't raise an exception, also, "skip" + # shouldn't be checked twice. + if not report.skip(skip_handlers): + report.review_status = \ + review_status_handler.get_review_status(report) + except ValueError as err: + LOG.error(err) + sys.exit(1) + reports = reports_helper.skip( reports, processed_path_hashes, skip_handlers, suppr_handler, src_comment_status_filter) @@ -434,6 +450,7 @@ def get_output_file_path(default_file_name: str) -> Optional[str]: file_report_map = plaintext.get_file_report_map( reports, file_path, metadata) plaintext.convert( + review_status_handler, file_report_map, processed_file_paths, print_steps) elif export == 'html': print(f"Parsing input file '{file_path}'.") @@ -441,6 +458,9 @@ def get_output_file_path(default_file_name: str) -> Optional[str]: file_path, reports, output_dir_path, html_builder) + for warning in review_status_handler.source_comment_warnings(): + LOG.warning(warning) + if export is None: # Plain text output statistics.write() elif export == 'html': diff --git a/analyzer/codechecker_analyzer/suppress_file_handler.py b/analyzer/codechecker_analyzer/suppress_file_handler.py index 54fcbd09b5..1a8f606f68 100644 --- a/analyzer/codechecker_analyzer/suppress_file_handler.py +++ b/analyzer/codechecker_analyzer/suppress_file_handler.py @@ -24,7 +24,7 @@ import re from codechecker_common.logger import get_logger -from codechecker_report_converter.source_code_comment_handler import \ +from codechecker_common.source_code_comment_handler import \ SourceCodeCommentHandler LOG = get_logger('system') diff --git a/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py b/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py index 98fabd73ce..042695570c 100644 --- a/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py +++ b/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py @@ -232,13 +232,21 @@ def check_one_file(self, path, mode): # Replace full path only to file name on the following # formatted lines: + # 1. # [severity] /a/b/x.cpp:line:col: message [checker] # The replacement on this line will be the following: # [severity] x.cpp:line:col: message [checker] + # 2. + # [] - /a/b/x.cpp contains misspelled ... + # The replacement on this line will be the following: + # [] - x.cpp contains misspelled ... sep = re.escape(os.sep) line = re.sub(r'^(\[\w+\]\s)(?P.+{0})' r'(.+\:\d+\:\d+\:\s.*\s\[.*\])$'.format(sep), r'\1\3', line) + line = re.sub(r'^\[\] - (?P.+{0})' + r'(.+ contains misspelled.+)'.format(sep), + r'[] - \2', line) if not any([line.startswith(prefix) for prefix in skip_prefixes]): diff --git a/analyzer/tests/functional/analyze_and_parse/test_files/multi_error_suppress_typo.output b/analyzer/tests/functional/analyze_and_parse/test_files/multi_error_suppress_typo.output index 0578d528d1..8536e1bf4e 100644 --- a/analyzer/tests/functional/analyze_and_parse/test_files/multi_error_suppress_typo.output +++ b/analyzer/tests/functional/analyze_and_parse/test_files/multi_error_suppress_typo.output @@ -22,13 +22,13 @@ CHECK#CodeChecker check --build "make multi_error_suppress_typo" --output $OUTPU return x/0; ^ -[] - multi_error_suppress_typo.cpp contains misspelled review status comment @9: // codechecker_suppressssss [all] some comment [LOW] multi_error_suppress_typo.cpp:10:3: Value stored to 'y' is never read [deadcode.DeadStores] y = 7; ^ Found 2 defect(s) in multi_error_suppress_typo.cpp +[] - multi_error_suppress_typo.cpp contains misspelled review status comment @9: // codechecker_suppressssss [all] some comment ----==== Severity Statistics ====---- ---------------------------- diff --git a/tools/report-converter/tests/unit/source_code_comment/source_code_comment_test_files/test_file_1 b/analyzer/tests/unit/source_code_comment_test_files/test_file_1 similarity index 100% rename from tools/report-converter/tests/unit/source_code_comment/source_code_comment_test_files/test_file_1 rename to analyzer/tests/unit/source_code_comment_test_files/test_file_1 diff --git a/tools/report-converter/tests/unit/source_code_comment/source_code_comment_test_files/test_file_2 b/analyzer/tests/unit/source_code_comment_test_files/test_file_2 similarity index 100% rename from tools/report-converter/tests/unit/source_code_comment/source_code_comment_test_files/test_file_2 rename to analyzer/tests/unit/source_code_comment_test_files/test_file_2 diff --git a/tools/report-converter/tests/unit/source_code_comment/source_code_comment_test_files/test_file_3 b/analyzer/tests/unit/source_code_comment_test_files/test_file_3 similarity index 100% rename from tools/report-converter/tests/unit/source_code_comment/source_code_comment_test_files/test_file_3 rename to analyzer/tests/unit/source_code_comment_test_files/test_file_3 diff --git a/tools/report-converter/tests/unit/source_code_comment/test_source_code_comment.py b/analyzer/tests/unit/test_source_code_comment.py similarity index 99% rename from tools/report-converter/tests/unit/source_code_comment/test_source_code_comment.py rename to analyzer/tests/unit/test_source_code_comment.py index 7609e810a8..a225fbf856 100644 --- a/tools/report-converter/tests/unit/source_code_comment/test_source_code_comment.py +++ b/analyzer/tests/unit/test_source_code_comment.py @@ -14,7 +14,7 @@ import os import unittest -from codechecker_report_converter.source_code_comment_handler import \ +from codechecker_common.source_code_comment_handler import \ SourceCodeComment, SourceCodeCommentHandler diff --git a/codechecker_common/review_status_handler.py b/codechecker_common/review_status_handler.py index 66dbe57d68..2febf4e021 100644 --- a/codechecker_common/review_status_handler.py +++ b/codechecker_common/review_status_handler.py @@ -7,38 +7,19 @@ # ------------------------------------------------------------------------- -from dataclasses import dataclass -from datetime import datetime import os -from typing import Optional +from typing import List, Optional -from logger import get_logger -from codechecker_report_converter.report import Report -from codechecker_report_converter.source_code_comment_handler import \ +from codechecker_report_converter.report import Report, SourceReviewStatus +from codechecker_common.logger import get_logger +from codechecker_common.source_code_comment_handler import \ SourceCodeCommentHandler, SpellException, contains_codechecker_comment, \ - SourceCodeComments + SourceCodeComment, SourceCodeComments LOG = get_logger('system') -@dataclass -class SourceReviewStatus: - """ - Helper class for handling in source review statuses. - Collect the same info as a review status rule. - - TODO: rename this class, because this not only represents review statuses - in source code comments but in review status yaml too. - """ - status: str = "unreviewed" - message: bytes = b"" - bug_hash: str = "" - in_source: bool = False - author: Optional[str] = None - date: Optional[datetime] = None - - class ReviewStatusHandler: """ This class helps to determine the review status of a report. The review @@ -55,8 +36,10 @@ class ReviewStatusHandler: TODO: Option 2. will be handled in a future commit. TODO: Option 3. should also be covered by this handler class. """ - def __init__(self, source_root): + def __init__(self, source_root=''): self.__source_root = source_root + self.__source_comment_warnings = [] + self.__source_commets = {} def __parse_codechecker_review_comment( self, @@ -75,28 +58,56 @@ def __parse_codechecker_review_comment( src_comment_data = sc_handler.filter_source_line_comments( f, report_line, checker_name) except SpellException as ex: - LOG.warning("File %s contains %s", source_file_name, ex) + self.__source_comment_warnings.append( + f"{source_file_name} contains {ex}") return src_comment_data + def get_review_status(self, report: Report) -> SourceReviewStatus: + """ + Return the review status of the report based on source code comments. + + If the review status is not set in the source code then "unreviewed" + review status returns. This function throws ValueError if the review + status is ambiguous (see get_review_status_from_source()). + """ + rs_from_source = self.get_review_status_from_source(report) + + if rs_from_source: + return rs_from_source + + return SourceReviewStatus(bug_hash=report.report_hash) + def get_review_status_from_source( self, report: Report - ) -> SourceReviewStatus: + ) -> Optional[SourceReviewStatus]: """ - Return the review status set in the source code belonging to + Return the review status based on the source code comment belonging to the given report. - Return the review status if it is set in the source code. - If the review status is ambiguous (i.e. multiple source code comments belong to it) then a ValueError exception is raised which contains information about the problem in a string. - # TODO: If not found then return None or unreviewed by default? + - If the soure file changed (which means that the source comments may + have replaced, changed or removed) then None returns. + TODO: This function either returns a SourceReviewStatus, or raises an + exception or returns None. This is too many things that a caller needs + to handle. The reason is that according to the current behaviors, + ambiguous comment results a hard error, and changed files result a + warning with "unreviewed" status. In the future we could implement a + logic where we take the first comment into account in case of ambiguity + or return "unreviewed" with a warning, like changed files. """ # The original file path is needed here, not the trimmed, because # the source files are extracted as the original file path. + # TODO: Should original_path be strip('/') at store? source_file_name = os.path.realpath(os.path.join( - self.__source_root, report.file.original_path.strip("/"))) + self.__source_root, report.file.original_path)) + + if source_file_name in report.changed_files: + return None if os.path.isfile(source_file_name): src_comment_data = self.__parse_codechecker_review_comment( @@ -104,33 +115,36 @@ def get_review_status_from_source( if len(src_comment_data) == 1: data = src_comment_data[0] - status, message = data.status, bytes(data.message, 'utf-8') + status, message = data.status, data.message + self.__source_commets[report] = data - review_status = SourceReviewStatus( + return SourceReviewStatus( status=status, - message=message, + message=message.encode('utf-8'), bug_hash=report.report_hash, - in_source=True - ) - - return review_status + in_source=True) elif len(src_comment_data) > 1: - LOG.warning( - "Multiple source code comment can be found " - "for '%s' checker in '%s' at line %s. " - "This suppression will not be taken into account!", - report.checker_name, source_file_name, report.line) - raise ValueError( - f"{source_file_name}|{report.line}|{report.checker_name}") - - # A better way to handle reports where the review status is not - # set in the source is to return None, and set the reviews status info - # at report addition time. - return SourceReviewStatus( - status="unreviewed", - message=b'', - bug_hash=report.report_hash, - in_source=False - ) + f"Multiple source code comments can be found for " + f"'{report.checker_name}' checker in '{source_file_name}' " + f"at line {report.line}.") + + return None + def source_comment_warnings(self) -> List[str]: + """ + Sometimes it is not intuitive why the given review status is determined + for a report. For example, if an in-source suppression is misspelled, + then the report remains unreviewed: + // codechecker_suppresssss [] comment + This function returns a list of warning messages on these unintuitive + behaviors. + """ + return self.__source_comment_warnings + + def source_comment(self, report: Report) -> Optional[SourceCodeComment]: + """ + This ReviewStatusHandler class is caching source comments so they are + read and parsed only once for each report. + """ + return self.__source_commets.get(report) diff --git a/tools/report-converter/codechecker_report_converter/source_code_comment_handler.py b/codechecker_common/source_code_comment_handler.py similarity index 100% rename from tools/report-converter/codechecker_report_converter/source_code_comment_handler.py rename to codechecker_common/source_code_comment_handler.py diff --git a/codechecker_common/util.py b/codechecker_common/util.py index 3465681e72..a58631a2cf 100644 --- a/codechecker_common/util.py +++ b/codechecker_common/util.py @@ -12,6 +12,7 @@ import itertools import json +from typing import TextIO import portalocker from codechecker_common.logger import get_logger @@ -84,3 +85,15 @@ def load_json(path: str, default=None, lock=False, display_warning=True): LOG.warning(ex) return ret + +def get_linef(fp: TextIO, line_no: int) -> str: + """'fp' should be (readable) file object. + Return the line content at line_no or an empty line + if there is less lines than line_no. + """ + fp.seek(0) + for line in fp: + line_no -= 1 + if line_no == 0: + return line + return '' diff --git a/tools/report-converter/codechecker_report_converter/report/__init__.py b/tools/report-converter/codechecker_report_converter/report/__init__.py index 52cbf772e7..71f8cf8c34 100644 --- a/tools/report-converter/codechecker_report_converter/report/__init__.py +++ b/tools/report-converter/codechecker_report_converter/report/__init__.py @@ -7,16 +7,16 @@ # ------------------------------------------------------------------------- import builtins +from dataclasses import dataclass +from datetime import datetime import itertools import json import logging import os -from typing import Callable, Dict, Iterable, List, Optional, Set +from typing import Callable, Dict, List, Optional, Set from .. import util -from ..source_code_comment_handler import SourceCodeCommentHandler, \ - SourceCodeComments, SpellException LOG = logging.getLogger('report-converter') @@ -271,6 +271,26 @@ def __repr__(self): return json.dumps(self.to_json()) +@dataclass +class SourceReviewStatus: + """ + Helper class for handling in source review statuses. + Collect the same info as a review status rule. + + TODO: rename this class, because this not only represents review statuses + in source code comments but in review status yaml too. + """ + status: str = "unreviewed" + message: bytes = b"" + bug_hash: str = "" + in_source: bool = False + author: Optional[str] = None + date: Optional[datetime] = None + + def formatted_status(self): + return self.status.lower().replace('_', ' ').capitalize() + + class Report: """ Represents a report object. """ @@ -293,7 +313,8 @@ def __init__( notes: Optional[List[BugPathEvent]] = None, macro_expansions: Optional[List[MacroExpansion]] = None, annotations: Optional[Dict[str, str]] = None, - static_message: Optional[str] = None + static_message: Optional[str] = None, + review_status: Optional[SourceReviewStatus] = SourceReviewStatus() ): """ This constructor populates the members of the Report object. @@ -333,9 +354,7 @@ def __init__( self.macro_expansions = macro_expansions \ if macro_expansions is not None else [] - self.__source_code_comments: Optional[SourceCodeComments] = None - self.__source_code_comment_warnings: List[str] = [] - self.__sc_handler = SourceCodeCommentHandler() + self.review_status = review_status self.__source_line: Optional[str] = source_line self.__files: Optional[Set[File]] = None @@ -449,92 +468,6 @@ def changed_files(self, changed_files: Set[str]): if self.__changed_files is None: self.__changed_files = changed_files - def __init_source_code_comments(self): - """ - Initialize source code comments and warnings if it is not parsed yet. - """ - if self.__source_code_comments is not None: - return None - - self.__source_code_comments = [] - - if self.file.original_path in self.changed_files: - return None - - if not os.path.exists(self.file.original_path): - return None - - with open(self.file.original_path, - encoding='utf-8', errors='ignore') as f: - try: - self.__source_code_comments = \ - self.__sc_handler.filter_source_line_comments( - f, self.line, self.checker_name) - except SpellException as ex: - self.__source_code_comment_warnings.append( - f"{self.file.name} contains {str(ex)}") - - if len(self.__source_code_comments) == 1: - LOG.debug("Found source code comment for report '%s' in file " - "'%s': %s", - self.report_hash, self.file.path, - self.__source_code_comments) - elif len(self.__source_code_comments) > 1: - self.__source_code_comment_warnings.append( - f"Multiple source code comment can be found for " - f"'{self.checker_name}' checker in '{self.file.path}' at " - f"line {self.line}. This bug will not be suppressed!") - - @property - def source_code_comment_warnings(self) -> List[str]: - """ Get source code comment warnings. """ - self.__init_source_code_comments() - return self.__source_code_comment_warnings - - def dump_source_code_comment_warnings(self): - """ Dump source code comments warnings. """ - for warning in self.source_code_comment_warnings: - LOG.warning(warning) - - @property - def source_code_comments(self) -> SourceCodeComments: - """ - Get source code comments for the report. - It will read the source file only once. - """ - self.__init_source_code_comments() - - if self.__source_code_comments is None: - self.__source_code_comments = [] - - return self.__source_code_comments - - @source_code_comments.setter - def source_code_comments(self, source_code_comments: SourceCodeComments): - """ Sets the source code comments manually if it's not set yet. """ - if self.__source_code_comments is None: - self.__source_code_comments = source_code_comments - - def check_source_code_comments(self, comment_types: Iterable[str]) -> bool: - """ - 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) - - @property - def review_status(self) -> str: - """ Return review status for the given report. """ - if len(self.source_code_comments) == 1: - return self.source_code_comments[0].status \ - .lower().replace('_', ' ') - - return 'unreviewed' - def skip(self, skip_handlers: Optional[SkipListHandlers]) -> bool: """ True if the report should be skipped. """ if not skip_handlers: @@ -556,9 +489,7 @@ def to_json(self) -> Dict: "analyzer_name": self.analyzer_name, "category": self.category, "type": self.type, - "source_code_comments": [ - s.to_json() for s in self.source_code_comments], - "review_status": self.review_status, + "review_status": self.review_status.status, "bug_path_events": [e.to_json() for e in self.bug_path_events], "bug_path_positions": [ p.to_json() for p in self.bug_path_positions], @@ -578,5 +509,19 @@ def __eq__(self, other): raise NotImplementedError( f"Comparison Range object with '{type(other)}' is not supported") + def __hash__(self): + """ + We consider two report objects equivalent if these members are the + same. This way a report object can be used as key in a dict. + WARNING! Make sure that the same members are compared by __eq__(). + """ + return builtins.hash(( + self.file, + self.line, + self.column, + self.message, + self.checker_name, + self.report_hash)) + def __repr__(self): return json.dumps(self.to_json()) diff --git a/tools/report-converter/codechecker_report_converter/report/output/html/html.py b/tools/report-converter/codechecker_report_converter/report/output/html/html.py index fc1e2c0561..819109e25f 100644 --- a/tools/report-converter/codechecker_report_converter/report/output/html/html.py +++ b/tools/report-converter/codechecker_report_converter/report/output/html/html.py @@ -249,7 +249,7 @@ def to_macro_expansions( 'events': to_bug_path_events(report.bug_path_events), 'macros': to_macro_expansions(report.macro_expansions), 'notes': to_bug_path_events(report.notes), - 'reviewStatus': report.review_status, + 'reviewStatus': report.review_status.formatted_status(), 'severity': self.get_severity(report.checker_name) }) diff --git a/tools/report-converter/codechecker_report_converter/report/output/plaintext.py b/tools/report-converter/codechecker_report_converter/report/output/plaintext.py index 86d1ef683c..e838770538 100644 --- a/tools/report-converter/codechecker_report_converter/report/output/plaintext.py +++ b/tools/report-converter/codechecker_report_converter/report/output/plaintext.py @@ -64,8 +64,8 @@ def format_report(report: Report, content_is_not_changed: bool) -> str: out = f"[{report.severity}] {file_path}:{report.line}:{report.column}: " \ f"{report.message} [{report.checker_name}]" - if content_is_not_changed and report.source_code_comments: - out += f" [{report.review_status.capitalize()}]" + if content_is_not_changed and report.review_status.status != 'unreviewed': + out += f" [{report.review_status.formatted_status()}]" return out @@ -144,6 +144,7 @@ def get_file_report_map( def convert( + review_status_handler, source_file_report_map: Dict[str, List[Report]], processed_file_paths: Optional[Set[str]] = None, print_steps: bool = False, @@ -164,16 +165,12 @@ def convert( content_is_not_changed = \ report.file.original_path not in report.changed_files - if content_is_not_changed: - report.dump_source_code_comment_warnings() - output.write(f"{format_report(report, content_is_not_changed)}\n") if content_is_not_changed: - # Print source code comments. - for source_code_comment in report.source_code_comments: - if source_code_comment.line: - output.write(f"{source_code_comment.line.rstrip()}\n") + source_comment = review_status_handler.source_comment(report) + if source_comment and source_comment.line: + output.write(f"{source_comment.line.rstrip()}\n") output.write(f"{format_main_report(report)}") else: diff --git a/tools/report-converter/codechecker_report_converter/report/reports.py b/tools/report-converter/codechecker_report_converter/report/reports.py index 6db7e99112..7ace456d9e 100644 --- a/tools/report-converter/codechecker_report_converter/report/reports.py +++ b/tools/report-converter/codechecker_report_converter/report/reports.py @@ -7,7 +7,6 @@ # ------------------------------------------------------------------------- import logging -import sys from typing import Any, Callable, Iterable, List, Optional, Set @@ -58,7 +57,7 @@ def skip( processed_path_hashes: Optional[Set[str]] = None, skip_handlers: Optional[SkipListHandlers] = None, suppr_handler: Optional[GenericSuppressHandler] = None, - src_comment_status_filter: Optional[Iterable[str]] = None + review_status_filter: Optional[Iterable[str]] = None ) -> List[Report]: """ Skip reports. """ kept_reports = [] @@ -74,29 +73,22 @@ def skip( report.checker_name, report.report_hash) continue - if report.source_code_comments: - if len(report.source_code_comments) > 1: - LOG.error("Multiple source code comment can be found for '%s' " - "checker in '%s' at line %d.", report.checker_name, - report.file.original_path, report.line) - sys.exit(1) - + if report.review_status: if suppr_handler: if not report.report_hash: LOG.warning("Can't store suppress information for report " "because no report hash is set: %s", report) continue - source_code_comment = report.source_code_comments[0] - suppr_handler.store_suppress_bug_id( - report.report_hash, - report.file.name, - source_code_comment.message, - source_code_comment.status) + if report.review_status.status != 'unreviewed': + suppr_handler.store_suppress_bug_id( + report.report_hash, + report.file.name, + report.review_status.message.decode(), + report.review_status.status) - if src_comment_status_filter and \ - not report.check_source_code_comments( - src_comment_status_filter): + if review_status_filter and \ + report.review_status.status not in review_status_filter: LOG.debug("Filtered out by --review-status filter option: " "%s:%s [%s] %s [%s]", report.file.original_path, report.line, @@ -104,8 +96,8 @@ def skip( report.review_status) continue else: - if src_comment_status_filter and \ - 'unreviewed' not in src_comment_status_filter: + if review_status_filter and \ + 'unreviewed' not in review_status_filter: LOG.debug("Filtered out by --review-status filter option: " "%s:%s [%s] %s [unreviewed]", report.file.original_path, report.line, diff --git a/tools/report-converter/tests/unit/source_code_comment/__init__.py b/tools/report-converter/tests/unit/source_code_comment/__init__.py deleted file mode 100644 index 4259749345..0000000000 --- a/tools/report-converter/tests/unit/source_code_comment/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# ------------------------------------------------------------------------- -# -# 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 -# -# ------------------------------------------------------------------------- diff --git a/web/client/codechecker_client/cmd/store.py b/web/client/codechecker_client/cmd/store.py index f1ed65f19b..4328277444 100644 --- a/web/client/codechecker_client/cmd/store.py +++ b/web/client/codechecker_client/cmd/store.py @@ -38,13 +38,13 @@ reports as reports_helper, statistics as report_statistics from codechecker_report_converter.report.hash import HashType, \ get_report_path_hash -from codechecker_report_converter.source_code_comment_handler import \ - SourceCodeCommentHandler from codechecker_client import client as libclient from codechecker_client import product from codechecker_common import arg, logger, cmd_config from codechecker_common.checker_labels import CheckerLabels +from codechecker_common.source_code_comment_handler import \ + SourceCodeCommentHandler from codechecker_common.util import load_json from codechecker_web.shared import webserver_context, host_check diff --git a/web/client/codechecker_client/cmd_line_client.py b/web/client/codechecker_client/cmd_line_client.py index fba88dfc0d..e18f42d90f 100644 --- a/web/client/codechecker_client/cmd_line_client.py +++ b/web/client/codechecker_client/cmd_line_client.py @@ -35,6 +35,7 @@ from codechecker_common import logger from codechecker_common.checker_labels import CheckerLabels +from codechecker_common.review_status_handler import ReviewStatusHandler from codechecker_common.util import load_json from codechecker_web.shared import convert, webserver_context @@ -181,10 +182,13 @@ def process_run_args(client, run_args_with_tag: Iterable[str]): def get_suppressed_reports(reports: List[Report], review_st: List[ttypes.ReviewStatus]) -> List[str]: """Returns a list of suppressed report hashes.""" - return [report.report_hash for report in reports - if not report.check_source_code_comments( - [ttypes.ReviewStatus._VALUES_TO_NAMES[x] - for x in review_st])] + statuses_str = [ttypes.ReviewStatus._VALUES_TO_NAMES[x].lower() + for x in review_st] + + return [ + report.report_hash for report in reports + if report.review_status.status != 'unreviewed' and + report.review_status not in statuses_str] def get_report_dir_results( @@ -197,6 +201,7 @@ def get_report_dir_results( Absolute paths are expected to the given report directories. """ all_reports = [] + review_status_handler = ReviewStatusHandler() processed_path_hashes = set() for _, file_paths in report_file.analyzer_result_files(report_dirs): @@ -204,6 +209,13 @@ def get_report_dir_results( # Get reports. reports = report_file.get_reports(file_path, checker_labels) + try: + for report in reports: + report.review_status = \ + review_status_handler.get_review_status(report) + except ValueError as err: + LOG.error(err) + # Skip duplicated reports. reports = reports_helper.skip(reports, processed_path_hashes) @@ -835,9 +847,9 @@ def get_diff_local_dir_remote_run( report_dir_results = [ r for r in report_dir_results if - r.review_status not in ['false positive', 'intentional'] and + r.review_status.status not in ['false_positive', 'intentional'] and (r.report_hash not in closed_hashes or - r.review_status == 'confirmed')] + r.review_status.status == 'confirmed')] local_report_hashes = set(r.report_hash for r in report_dir_results) local_report_hashes.update( baseline.get_report_hashes(baseline_files) - closed_hashes) @@ -932,9 +944,10 @@ def get_diff_remote_run_local_dir( report_dir_results = [ r for r in report_dir_results if - r.review_status not in ['false positive', 'intentional'] and + r.review_status.status not in ['false_positive', 'intentional'] and (r.report_hash not in closed_hashes or - r.review_status == 'confirmed')] + r.review_status.status == 'confirmed')] + local_report_hashes = set(r.report_hash for r in report_dir_results) local_report_hashes.update( baseline.get_report_hashes(baseline_files) - closed_hashes) @@ -1044,16 +1057,17 @@ def get_diff_local_dirs( context = webserver_context.get_context() statuses_str = [ttypes.ReviewStatus._VALUES_TO_NAMES[x].lower() for x in report_filter.reviewStatus] + statuses_str.append('unreviewed') base_results = get_report_dir_results( report_dirs, report_filter, context.checker_labels) base_results = [res for res in base_results - if res.check_source_code_comments(statuses_str)] + if res.review_status.status in statuses_str] new_results = get_report_dir_results( new_report_dirs, report_filter, context.checker_labels) new_results = [res for res in new_results - if res.check_source_code_comments(statuses_str)] + if res.review_status.status in statuses_str] base_hashes = set([res.report_hash for res in base_results]) new_hashes = set([res.report_hash for res in new_results]) @@ -1116,6 +1130,7 @@ def print_reports( if output_format == 'plaintext': file_report_map = plaintext.get_file_report_map(reports) plaintext.convert( + ReviewStatusHandler(), file_report_map, processed_file_paths, print_steps) context = webserver_context.get_context() diff --git a/web/client/codechecker_client/suppress_file_handler.py b/web/client/codechecker_client/suppress_file_handler.py index 9133fc039d..89d1fcca5d 100644 --- a/web/client/codechecker_client/suppress_file_handler.py +++ b/web/client/codechecker_client/suppress_file_handler.py @@ -25,7 +25,7 @@ import re from codechecker_common.logger import get_logger -from codechecker_report_converter.source_code_comment_handler import \ +from codechecker_common.source_code_comment_handler import \ SourceCodeCommentHandler LOG = get_logger('system') diff --git a/web/server/codechecker_server/api/mass_store_run.py b/web/server/codechecker_server/api/mass_store_run.py index f5e8686273..8eb9be73ba 100644 --- a/web/server/codechecker_server/api/mass_store_run.py +++ b/web/server/codechecker_server/api/mass_store_run.py @@ -898,7 +898,7 @@ def get_missing_file_ids(report: Report) -> List[str]: rs_from_source = SourceReviewStatus() try: rs_from_source = \ - review_status_handler.get_review_status_from_source(report) + review_status_handler.get_review_status(report) rs_from_source.author = self.user_name rs_from_source.date = run_history_time except ValueError as err: @@ -1095,7 +1095,7 @@ def get_skip_handler( .join(DBReport, DBReport.bug_id == ReviewStatusRule.bug_hash) \ .filter(sqlalchemy.and_( DBReport.run_id == run_id, - DBReport.review_status_is_in_source == False, + DBReport.review_status_is_in_source.is_(False), ReviewStatusRule.bug_hash.in_(self.__new_report_hashes))) # Set the newly stored reports @@ -1354,6 +1354,4 @@ def store(self) -> int: if self.__wrong_src_code_comments: raise codechecker_api_shared.ttypes.RequestFailed( codechecker_api_shared.ttypes.ErrorCode.SOURCE_FILE, - "Multiple source code comment can be found with the same " - "checker name for same bug!", self.__wrong_src_code_comments) diff --git a/web/server/codechecker_server/migrations/report/versions/75ae226b5d88_review_status_for_each_report.py b/web/server/codechecker_server/migrations/report/versions/75ae226b5d88_review_status_for_each_report.py index f73f1ea310..663dd73591 100644 --- a/web/server/codechecker_server/migrations/report/versions/75ae226b5d88_review_status_for_each_report.py +++ b/web/server/codechecker_server/migrations/report/versions/75ae226b5d88_review_status_for_each_report.py @@ -18,8 +18,7 @@ import zlib from io import StringIO -from codechecker_common import util -from codechecker_report_converter.source_code_comment_handler import \ +from codechecker_common.source_code_comment_handler import \ SourceCodeCommentHandler, SpellException diff --git a/web/tests/functional/diff_cmdline/test_diff_cmdline.py b/web/tests/functional/diff_cmdline/test_diff_cmdline.py index 2f91e99be7..765ba82a05 100644 --- a/web/tests/functional/diff_cmdline/test_diff_cmdline.py +++ b/web/tests/functional/diff_cmdline/test_diff_cmdline.py @@ -124,7 +124,7 @@ def __analyze(self, file_dir, source_code): [{{ "directory": "{file_dir}", "file": "main.c", - "command": "gcc main.c -o /dev/null" + "command": "gcc main.c -o /dev/null -c" }}] """ os.makedirs(file_dir, exist_ok=True)