From 2519a2fffd64e18152ea749affd5eb012b979bee Mon Sep 17 00:00:00 2001 From: Pascal Bach Date: Fri, 2 Sep 2022 22:44:21 +0200 Subject: [PATCH 1/2] [db] Replace `Binary` with `LargeBinary` `Binary` is deprecated in SQLAlchemy and was removed in 1.4 --- .../database/run_db_model.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/web/server/codechecker_server/database/run_db_model.py b/web/server/codechecker_server/database/run_db_model.py index 1faba6c088..8862d1eed4 100644 --- a/web/server/codechecker_server/database/run_db_model.py +++ b/web/server/codechecker_server/database/run_db_model.py @@ -12,8 +12,8 @@ from math import ceil import os -from sqlalchemy import MetaData, Column, Integer, UniqueConstraint, String, \ - DateTime, Boolean, ForeignKey, Binary, Enum, Table, Text +from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Integer, \ + LargeBinary, MetaData, String, UniqueConstraint, Table, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from sqlalchemy.sql.expression import true, false @@ -34,7 +34,7 @@ class AnalysisInfo(Base): __tablename__ = 'analysis_info' id = Column(Integer, autoincrement=True, primary_key=True) - analyzer_command = Column(Binary) + analyzer_command = Column(LargeBinary) def __init__(self, analyzer_command): self.analyzer_command = analyzer_command @@ -110,10 +110,10 @@ class AnalyzerStatistic(Base): ondelete='CASCADE'), index=True) analyzer_type = Column(String) - version = Column(Binary) + version = Column(LargeBinary) successful = Column(Integer) failed = Column(Integer) - failed_files = Column(Binary, nullable=True) + failed_files = Column(LargeBinary, nullable=True) def __init__(self, run_history_id, analyzer_type, version, successful, failed, failed_files): @@ -178,11 +178,11 @@ class FileContent(Base): __tablename__ = 'file_contents' content_hash = Column(String, primary_key=True) - content = Column(Binary) + content = Column(LargeBinary) # Note: two different authors can commit the same file content to # different paths in which case the blame info will be the same. - blame_info = Column(Binary, nullable=True) + blame_info = Column(LargeBinary, nullable=True) def __init__(self, content_hash, content, blame_info): self.content_hash, self.content, self.blame_info = \ @@ -376,7 +376,7 @@ class Report(Base): nullable=False, server_default='unreviewed') review_status_author = Column(String) - review_status_message = Column(Binary) + review_status_message = Column(LargeBinary) review_status_date = Column(DateTime, nullable=True) # We'd like to indicate whether a suppression comes from a source code # comment or set via the GUI. Former ones must not change when set from @@ -454,7 +454,7 @@ class Comment(Base): id = Column(Integer, autoincrement=True, primary_key=True) bug_hash = Column(String, nullable=False, index=True) author = Column(String, nullable=False) - message = Column(Binary, nullable=False) + message = Column(LargeBinary, nullable=False) # Default value is 0 which means a user given comment. kind = Column(Integer, @@ -484,7 +484,7 @@ class ReviewStatus(Base): bug_hash = Column(String, primary_key=True) status = Column(ReviewStatusType, nullable=False) author = Column(String, nullable=False) - message = Column(Binary, nullable=False) + message = Column(LargeBinary, nullable=False) date = Column(DateTime, nullable=False) @@ -496,7 +496,7 @@ class SourceComponent(Base): # Contains multiple file paths separated by new line characters. Each file # path start with a '+' (path should be filtered) or '-' (path should not # be filtered) sign. E.g.: "+/a/b/x.cpp\n-/a/b/" - value = Column(Binary, nullable=False) + value = Column(LargeBinary, nullable=False) description = Column(Text, nullable=True) username = Column(String, nullable=True) From f4a80892172b4471d8b98d1e6424970bc9fee3e7 Mon Sep 17 00:00:00 2001 From: Whisperity Date: Fri, 1 Mar 2024 17:16:20 +0100 Subject: [PATCH 2/2] feat(server): Store checker list as analysis-info This patch allows users to gather whether a checker was enabled or disabled during the analysis, irrespective of whether a checker produced any reports (which might have been deleted from the server since!). This improves auditing capabilities as the definite knowledge on which checkers were _available_ is kept in the database, and not lost after the analysis temporaries are cleaned up from the analysing client. Features: - Create a new table, `checkers`, to store unique ID (per product database) for a checker's name. - Add information about checkers and enabledness to the database, based on the `metadata.json`, if available. - Extend the `AnalysisInfo` API object to report the collected information to the client. Refactoring: - Normalise the use of the `checkers` table by lifting additional checker-unique information (`severity`) from `reports`, leaving only a `FOREIGN KEY` in the `reports` table. - Ensure that all versions of `metadata.json` is represented the same way in memory once the `MetadataInfoParser` succeeded. - Ensure that a long migration of a report database does not result in time-outs for the connection of the configuration database, and that the failure in the migration of one product does not kill the entire server. --- .github/workflows/test.yml | 6 +- alembic.ini | 35 -- analyzer/tests/unit/test_checker_labels.py | 7 + codechecker_common/checker_labels.py | 13 +- .../report/__init__.py | 15 +- .../report/parser/plist.py | 13 +- .../dist/codechecker-api-6.54.0.tgz | Bin 62288 -> 0 bytes .../dist/codechecker-api-6.55.0.tgz | Bin 0 -> 62764 bytes web/api/js/codechecker-api-node/package.json | 2 +- .../dist/codechecker_api.tar.gz | Bin 54261 -> 54550 bytes web/api/py/codechecker_api/setup.py | 2 +- .../dist/codechecker_api_shared.tar.gz | Bin 3634 -> 3633 bytes web/api/py/codechecker_api_shared/setup.py | 2 +- web/api/report_server.thrift | 17 +- web/codechecker_web/shared/version.py | 2 +- .../codechecker_server/api/mass_store_run.py | 534 +++++++++++------ .../codechecker_server/api/report_server.py | 251 +++++--- web/server/codechecker_server/cmd/server.py | 184 +++--- .../database/config_db_model.py | 4 +- .../codechecker_server/database/database.py | 6 + .../codechecker_server/database/db_cleanup.py | 267 +++++---- .../database/run_db_model.py | 109 +++- web/server/codechecker_server/metadata.py | 52 +- .../migrations/config/README | 1 - ...cf38af_add_extra_product_detail_columns.py | 5 +- .../migrations/report/README | 1 - .../migrations/report/env.py | 66 ++- ...e226b5d88_review_status_for_each_report.py | 5 + ...enabled_and_disabled_checkers_for_a_run.py | 541 ++++++++++++++++++ .../dabc6998b8f0_analysis_info_table.py | 8 +- web/server/codechecker_server/server.py | 6 +- .../tests/unit/metadata_test_files/v1.5.json | 50 ++ .../tests/unit/metadata_test_files/v1.json | 14 +- web/server/tests/unit/test_metadata_merge.py | 1 + web/server/tests/unit/test_metadata_parser.py | 60 +- web/server/vue-cli/package-lock.json | 12 +- web/server/vue-cli/package.json | 2 +- .../src/components/AnalysisInfoDialog.vue | 6 + web/tests/functional/blame/test_blame_info.py | 10 +- .../functional/cppcheck/test_cppcheck.py | 8 +- .../report_viewer_api/test_get_run_results.py | 13 +- .../report_viewer_api/test_run_data.py | 69 ++- 42 files changed, 1836 insertions(+), 563 deletions(-) delete mode 100644 web/api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz create mode 100644 web/api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz delete mode 100644 web/server/codechecker_server/migrations/config/README delete mode 100644 web/server/codechecker_server/migrations/report/README create mode 100644 web/server/codechecker_server/migrations/report/versions/c3dad71f8e6b_store_information_about_enabled_and_disabled_checkers_for_a_run.py create mode 100644 web/server/tests/unit/metadata_test_files/v1.5.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4e2983193..00e8ffca07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,6 +155,7 @@ jobs: run: sh .github/workflows/install-deps.sh - name: Init .pgpass + if: matrix.database != 'sqlite' run: | echo '*:*:*:*:postgres' > $HOME/.pgpass chmod 0600 $HOME/.pgpass @@ -163,7 +164,10 @@ jobs: env: PGPASSWORD: postgres run: | - export PGPASSFILE=$HOME/.pgpass + if [[ "${{ matrix.database != 'sqlite' }}" == "true" ]] + then + export PGPASSFILE=$HOME/.pgpass + fi make pip_dev_deps pip3 install -r web/requirements_py/auth/requirements.txt diff --git a/alembic.ini b/alembic.ini index fa1dd4ce4d..55fbd96dfd 100644 --- a/alembic.ini +++ b/alembic.ini @@ -61,38 +61,3 @@ script_location = web/server/codechecker_server/migrations/report sqlalchemy.url = postgres://postgres@localhost:5432/default #sqlalchemy.url = sqlite:////home/username/.codechecker/Default.sqlite - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/analyzer/tests/unit/test_checker_labels.py b/analyzer/tests/unit/test_checker_labels.py index 262c7c6882..22593e9254 100644 --- a/analyzer/tests/unit/test_checker_labels.py +++ b/analyzer/tests/unit/test_checker_labels.py @@ -115,6 +115,13 @@ def initialize_labels_dir(self): def test_checker_labels(self): cl = CheckerLabels(self.labels_dir.name) + self.assertEqual( + sorted(cl.get_analyzers()), + sorted([ + "clang-tidy", + "clangsa" + ])) + self.assertEqual( sorted(cl.checkers_by_labels([ 'profile:extreme'])), diff --git a/codechecker_common/checker_labels.py b/codechecker_common/checker_labels.py index dea883ebc6..6b7039e8ff 100644 --- a/codechecker_common/checker_labels.py +++ b/codechecker_common/checker_labels.py @@ -1,6 +1,12 @@ -import os +# ------------------------------------------------------------------------- +# +# 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 +# +# ------------------------------------------------------------------------- from collections import defaultdict - +import os from typing import Any, cast, DefaultDict, Dict, Iterable, List, Optional, \ Set, Tuple, Union @@ -148,6 +154,9 @@ def __get_analyzer_data( if analyzer is None or a == analyzer: yield a, c + def get_analyzers(self) -> Iterable[str]: + return self.__data.keys() + def checkers_by_labels( self, filter_labels: Iterable[str], diff --git a/tools/report-converter/codechecker_report_converter/report/__init__.py b/tools/report-converter/codechecker_report_converter/report/__init__.py index cadea68fbc..712659af7e 100644 --- a/tools/report-converter/codechecker_report_converter/report/__init__.py +++ b/tools/report-converter/codechecker_report_converter/report/__init__.py @@ -14,13 +14,16 @@ import logging import os -from typing import Callable, Dict, List, Optional, Protocol, Set +from typing import Callable, Dict, List, Optional, Protocol, Set, Tuple from .. import util LOG = logging.getLogger('report-converter') +FakeChecker: Tuple[str, str] = ("__FAKE__", "__FAKE__") +UnknownChecker: Tuple[str, str] = ("UNKNOWN", "NOT FOUND") + class SkipListHandlers(Protocol): should_skip: Callable[[str], bool] @@ -338,8 +341,8 @@ def __init__( self.severity = severity self.report_hash = report_hash self.analyzer_name = analyzer_name - self.category = category - self.type = type + self.category = category # TODO: Remove this. DEPRECATED. + self.type = type # TODO: Remove this. DEPRECATED. self.annotations = annotations self.static_message = \ @@ -488,7 +491,13 @@ def to_json(self) -> Dict: "severity": self.severity, "report_hash": self.report_hash, "analyzer_name": self.analyzer_name, + # DEPRECATED: 'category' is deprecated in 6.24.0, as it is not + # parsed, understood, or handled by the report server. + # It should be removed! "category": self.category, + # DEPRECATED: 'type' is deprecated in 6.24.0, as it is not + # parsed, understood, or handled by the report server. + # It should be removed! "type": self.type, "review_status": self.review_status.status if self.review_status else '', diff --git a/tools/report-converter/codechecker_report_converter/report/parser/plist.py b/tools/report-converter/codechecker_report_converter/report/parser/plist.py index 34a75cb72e..405981feeb 100644 --- a/tools/report-converter/codechecker_report_converter/report/parser/plist.py +++ b/tools/report-converter/codechecker_report_converter/report/parser/plist.py @@ -26,8 +26,13 @@ else: from mypy_extensions import TypedDict -from codechecker_report_converter.report import BugPathEvent, \ - BugPathPosition, File, get_or_create_file, MacroExpansion, Range, Report +from codechecker_report_converter.report import \ + BugPathEvent, BugPathPosition, \ + File, \ + MacroExpansion, \ + Range, Report, \ + UnknownChecker, \ + get_or_create_file from codechecker_report_converter.report.hash import get_report_hash, HashType from codechecker_report_converter.report.parser.base import AnalyzerInfo, \ BaseParser, get_tool_info @@ -242,8 +247,8 @@ def __create_report( files: Dict[int, File], metadata: Dict[str, Any] ) -> Report: - location = diag.get('location', {}) - checker_name = diag.get('check_name', "unknown") + location = diag.get("location", {}) + checker_name = diag.get("check_name", UnknownChecker[1]) analyzer_name = self.__get_analyzer_name(checker_name, metadata) severity = self.get_severity(checker_name) diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz deleted file mode 100644 index ad14888696a3a95238ea7862f03ba4609c602845..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62288 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 diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..a8a714c56f2a86f0e1e527b4469633279a78a8cf GIT binary patch literal 62764 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 literal 0 HcmV?d00001 diff --git a/web/api/js/codechecker-api-node/package.json b/web/api/js/codechecker-api-node/package.json index 6bdf935418..873f658109 100644 --- a/web/api/js/codechecker-api-node/package.json +++ b/web/api/js/codechecker-api-node/package.json @@ -1,6 +1,6 @@ { "name": "codechecker-api", - "version": "6.54.0", + "version": "6.55.0", "description": "Generated node.js compatible API stubs for CodeChecker server.", "main": "lib", "homepage": "https://github.com/Ericsson/codechecker", diff --git a/web/api/py/codechecker_api/dist/codechecker_api.tar.gz b/web/api/py/codechecker_api/dist/codechecker_api.tar.gz index e2fb1622dfddb96b91ea1bb2c5082e0237f585f8..02c125fa48d277447aef2868333a9fd89a5fe0fe 100644 GIT binary patch delta 51049 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

7is=ewiDV)!o=GN~7yRVjVE?F9 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 W@CSeJ2Y>J<|NVaukHN$M@DBivplm$= delta 50757 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 DuCm+F diff --git a/web/api/py/codechecker_api/setup.py b/web/api/py/codechecker_api/setup.py index 806b546b36..c5f7fe14fb 100644 --- a/web/api/py/codechecker_api/setup.py +++ b/web/api/py/codechecker_api/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.54.0' +api_version = '6.55.0' setup( name='codechecker_api', diff --git a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz index ee89d70e0599b3f87d39ca5881f029cdd201a233..81163d3165e2f0c1b0f661a1616d8daef45d9a05 100644 GIT binary patch literal 3633 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 D7AaSJ delta 3628 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 diff --git a/web/api/py/codechecker_api_shared/setup.py b/web/api/py/codechecker_api_shared/setup.py index 747e74be03..13dda9d3f1 100644 --- a/web/api/py/codechecker_api_shared/setup.py +++ b/web/api/py/codechecker_api_shared/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.54.0' +api_version = '6.55.0' setup( name='codechecker_api_shared', diff --git a/web/api/report_server.thrift b/web/api/report_server.thrift index e37dcf67d6..a7962f456f 100644 --- a/web/api/report_server.thrift +++ b/web/api/report_server.thrift @@ -198,7 +198,8 @@ struct RunHistoryData { 4: string user, // User name who analysed the run. 5: string time, // Date time when the run was analysed. 6: i64 id, // Id of the run history tag. - 7: string checkCommand, // Check command. !!!DEPRECATED!!! This field will be empty so use the getCheckCommand API function to get the check command for a run. + // !!!DEPRECATED!!! This field will be empty so use the getCheckCommand() API function to get the check command for a run. + 7: string checkCommand, 8: string codeCheckerVersion, // CodeChecker client version of the latest analysis. 9: AnalyzerStatisticsData analyzerStatistics, // Statistics for analyzers. Only number of failed and successfully analyzed // files field will be set. To get full analyzer statistics please use the @@ -467,8 +468,16 @@ union AnalysisInfoFilter { 3: i64 reportId, } +struct AnalysisInfoChecker { + 1: bool enabled, // If the checker was enabled during the analysis. +} + struct AnalysisInfo { - 1: string analyzerCommand, + 1: string analyzerCommand, + // For each analyzer, the checkers and their status as was available during + // the analysis. + 2: optional map> checkers, } typedef string CommitHash @@ -534,12 +543,12 @@ service codeCheckerDBAccess { // Get check command for a run. // PERMISSION: PRODUCT_VIEW - // !DEPRECATED Use getAnalysisInfo API to get the check commands. + // !DEPRECATED Use getAnalysisInfo() API to get the check commands. string getCheckCommand(1: i64 runHistoryId, 2: i64 runId) throws (1: codechecker_api_shared.RequestFailed requestError), - // Get analyzer commands based on the given filters. + // Get analyzer execution information based on the given filters. // PERMISSION: PRODUCT_VIEW list getAnalysisInfo(1: AnalysisInfoFilter analysisInfoFilter, 2: i64 limit, diff --git a/web/codechecker_web/shared/version.py b/web/codechecker_web/shared/version.py index c045815196..d5f83d22b0 100644 --- a/web/codechecker_web/shared/version.py +++ b/web/codechecker_web/shared/version.py @@ -18,7 +18,7 @@ # The newest supported minor version (value) for each supported major version # (key) in this particular build. SUPPORTED_VERSIONS = { - 6: 54 + 6: 55 } # Used by the client to automatically identify the latest major and minor diff --git a/web/server/codechecker_server/api/mass_store_run.py b/web/server/codechecker_server/api/mass_store_run.py index 4f8aac0ac9..4b861d1822 100644 --- a/web/server/codechecker_server/api/mass_store_run.py +++ b/web/server/codechecker_server/api/mass_store_run.py @@ -16,10 +16,10 @@ import zlib from collections import defaultdict -from datetime import datetime +from datetime import datetime, timedelta from hashlib import sha256 from tempfile import TemporaryDirectory -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast import codechecker_api_shared from codechecker_api.codeCheckerDBAccess_v6 import ttypes @@ -31,16 +31,21 @@ from codechecker_common.util import load_json, path_for_fake_root from codechecker_report_converter.util import trim_path_prefixes -from codechecker_report_converter.report import report_file, Report +from codechecker_report_converter.report import \ + FakeChecker, Report, UnknownChecker, report_file from codechecker_report_converter.report.hash import get_report_path_hash from ..database import db_cleanup from ..database.config_db_model import Product from ..database.database import DBSession -from ..database.run_db_model import AnalysisInfo, AnalyzerStatistic, \ - BugPathEvent, BugReportPoint, ReportAnnotations, ExtendedReportData, \ - File, FileContent, Report as DBReport, ReviewStatus as ReviewStatusRule, \ - Run, RunHistory, RunLock +from ..database.run_db_model import \ + AnalysisInfo, AnalysisInfoChecker, AnalyzerStatistic, \ + BugPathEvent, BugReportPoint, \ + Checker, \ + ExtendedReportData, \ + File, FileContent, \ + Report as DBReport, ReportAnnotations, ReviewStatus as ReviewStatusRule, \ + Run, RunLock, RunHistory from ..metadata import checker_is_unavailable, MetadataInfoParser from .report_server import ThriftRequestHandler @@ -61,10 +66,42 @@ def __enter__(self, *args): LOG.info("[%s] %s...", self.__run_name, self.__msg) def __exit__(self, *args): - LOG.info("[%s] %s done... (duration: %s sec)", self.__run_name, + LOG.info("[%s] %s. Done. (Duration: %s sec)", self.__run_name, self.__msg, round(time.time() - self.__start_time, 2)) +class RunLocking: + def __init__(self, session: DBSession, run_name: str): + self.__session = session + self.__run_name = run_name + + def __enter__(self, *args): + # Load the lock record for "FOR UPDATE" so that the transaction that + # handles the run's store operations has a lock on the database row + # itself. + self.__run_lock = self.__session.query(RunLock) \ + .filter(RunLock.name == self.__run_name) \ + .with_for_update(nowait=True) \ + .one() + + # Do *NOT* remove this seemingly dummy print. The query functions on + # result in a **proxy** object, essentially a weak pointer, without(!) + # the execution of any underlying SQL statements. Which means that + # without the evaluation of the member of this query (with the + # . operator) there would be no actual DBMS-level lock in place. + # (Not having debug logs enabled is not a problem: the evaluation of + # the arguments still take place even if the logging configuration + # prevents the actual printing of the log line.) + LOG.debug("Acquired exclusive lock for run '%s' that was originally " + "locked at '%s'.", + self.__run_name, self.__run_lock.locked_at) + return self + + def __exit__(self, *args): + self.__run_lock = None + self.__session = None + + def unzip(b64zip: str, output_dir: str) -> int: """ This function unzips the base64 encoded zip file. This zip is extracted @@ -191,6 +228,11 @@ def get_blame_file_data( return blame_info, remote_url, tracking_branch +def checker_name_for_report(report: Report) -> Tuple[str, str]: + return (report.analyzer_name or UnknownChecker[0], + report.checker_name or UnknownChecker[1]) + + class MassStoreRun: def __init__( self, @@ -214,17 +256,21 @@ def __init__( self.__trim_path_prefixes = trim_path_prefixes self.__description = description - self.__mips: Dict[str, MetadataInfoParser] = {} - self.__analysis_info: Dict[str, AnalysisInfo] = {} + self.__mips: Dict[str, MetadataInfoParser] = dict() + self.__analysis_info: Dict[str, AnalysisInfo] = dict() + self.__checker_row_cache: Dict[Tuple[str, str], Checker] = dict() self.__duration: int = 0 self.__report_count: int = 0 self.__report_limit: int = 0 - self.__wrong_src_code_comments: List[str] = [] + self.__wrong_src_code_comments: List[str] = list() self.__already_added_report_hashes: Set[str] = set() - self.__severity_map: Dict[str, int] = {} - self.__new_report_hashes: Dict[str, Tuple] = {} + self.__new_report_hashes: Dict[str, Tuple] = dict() self.__all_report_checkers: Set[str] = set() self.__added_reports: List[Tuple[DBReport, Report]] = list() + self.__reports_with_fake_checkers: Dict[ + # Either a DBReport *without* an ID, or the ID of a committed + # DBReport. + str, Tuple[Report, Union[DBReport, int]]] = dict() self.__get_report_limit_for_product() @@ -529,6 +575,69 @@ def __add_file_content( # the meantime. session.rollback() + def __store_checker_identifiers(self, checkers: Set[Tuple[str, str]]): + """ + Stores the identifiers "(analyzer, checker_name)" in the database into + a look-up table where each unique checker is given a unique numeric + identifier. + + Due to the use of an M-to-N connection table + (see `AnalysisInfoChecker`) one side of the joins must have their IDs + eagerly populated, otherwise the Python bindings will fail. + However, eager population will result in exceptions that a flush was + created before the transaction was complete. + + Moreover, this is performed separately from the storing of the details + of a run to reduce contention if two parallel stores, especially across + server instances (in a distributed/load-balanced environment) want to + store the same identifier(s). + """ + max_tries, tries, wait_time = 3, 0, timedelta(seconds=30) + # The "fake" checker is a temporary row that is needed intermittently + # during report storage because there might be reports that point to + # checkers which are not found in a preemptively parsed + # 'metadata.json', or, in the worst case, there might simply not be + # a 'metadata.json' at all in the to-be-stored structure. + all_checkers = {FakeChecker, UnknownChecker} | checkers + while tries < max_tries: + tries += 1 + try: + LOG.debug("[%s] Begin attempt %d...", self.__name, tries) + with DBSession(self.__Session) as session: + known_checkers = {(r.analyzer_name, r.checker_name) + for r in session + .query(Checker.analyzer_name, + Checker.checker_name) + .all()} + for analyzer, checker in \ + sorted(all_checkers - known_checkers): + s = self.__context.checker_labels.severity(checker) + s = ttypes.Severity._NAMES_TO_VALUES[s] + session.add(Checker(analyzer, checker, s)) + LOG.debug("Acquiring ID for checker '%s/%s' " + "for the first time.", analyzer, checker) + + session.commit() + return + except (sqlalchemy.exc.OperationalError, + sqlalchemy.exc.ProgrammingError) as ex: + LOG.error("Storing checkers of run '%s' failed: %s.\n" + "Waiting %d before trying again...", + self.__name, ex, wait_time) + time.sleep(wait_time.total_seconds()) + wait_time *= 2 + except Exception as ex: + LOG.error("Failed to store checkers due to some other error: " + "%s", ex) + import traceback + traceback.print_exc() + raise + + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + "Storing the names of the checkers in the run failed due to " + "excessive contention!") + def __store_analysis_statistics( self, session: DBSession, @@ -619,7 +728,24 @@ def __store_analysis_info( analysis_info = analysis_info_rows[0] else: analysis_info = AnalysisInfo(analyzer_command=cmd) + + # Obtain the ID eagerly to be able to use the M-to-N table. session.add(analysis_info) + session.flush() + session.refresh(analysis_info, ["id"]) + + for analyzer in mip.analyzers: + q = session \ + .query(Checker) \ + .filter(Checker.analyzer_name == analyzer) + db_checkers = {r.checker_name: r for r in q.all()} + + connection_rows = [AnalysisInfoChecker( + analysis_info, db_checkers[chk], is_enabled) + for chk, is_enabled + in mip.checkers.get(analyzer, dict()).items()] + for r in connection_rows: + session.add(r) run_history.analysis_info.append(analysis_info) self.__analysis_info[src_dir_path] = analysis_info @@ -718,54 +844,81 @@ def __add_or_update_run( codechecker_api_shared.ttypes.ErrorCode.GENERAL, str(ex)) + def __get_checker(self, + session: DBSession, + analyzer_name: str, + checker_name: str) -> Optional[Checker]: + try: + return self.__checker_row_cache[(analyzer_name, checker_name)] + except KeyError: + maybe_orm: Optional[Checker] = session.query(Checker) \ + .filter(sqlalchemy.and_( + Checker.analyzer_name == analyzer_name, + Checker.checker_name == checker_name)) \ + .one_or_none() + if maybe_orm: + self.__checker_row_cache[(analyzer_name, checker_name)] = \ + cast(Checker, maybe_orm) + return maybe_orm + + def __checker_for_report(self, + session: DBSession, + report: Report) -> Optional[Checker]: + analyzer_name, checker_name = checker_name_for_report(report) + return self.__get_checker(session, analyzer_name, checker_name) + def __add_report( self, session: DBSession, run_id: int, report: Report, + report_path_hash: str, file_path_to_id: Dict[str, int], review_status: SourceReviewStatus, detection_status: str, detection_time: datetime, run_history_time: datetime, - analysis_info: AnalysisInfo, - analyzer_name: Optional[str] = None, + analysis_info: Optional[AnalysisInfo], fixed_at: Optional[datetime] = None ) -> int: """ Add report to the database. """ try: - checker_name = report.checker_name - - # Cache the severity of the checkers - try: - severity = self.__severity_map[checker_name] - except KeyError: - severity_name = \ - self.__context.checker_labels.severity(checker_name) - severity = ttypes.Severity._NAMES_TO_VALUES[severity_name] - self.__severity_map[checker_name] = severity + checker = self.__checker_for_report(session, report) + if not checker: + # It would be too easy to create a 'Checker' instance with the + # observed data right here, but __add_report() is called in + # the context of the *BIG* TRANSACTION which has all the + # reports of the entire store pending. Losing all that + # information on a potential UNIQUE CONSTRAINT violation due + # to multiple concurrent massStoreRun()s trying to store the + # same checker ID which was never seen in a 'metadata.json' is + # not worth it. + checker = self.__get_checker(session, + FakeChecker[0], FakeChecker[1]) + if not checker: + LOG.fatal("Psuedo-checker '%s/%s' has no " + "identity in the database, even though " + "__store_checker_identifiers() should have " + "always preemptively created it!", + FakeChecker[0], FakeChecker[1]) + raise KeyError(FakeChecker[1]) db_report = DBReport( - run_id, report.report_hash, file_path_to_id[report.file.path], - report.message, checker_name or 'NOT FOUND', - report.category, report.type, report.line, report.column, - severity, review_status.status, review_status.author, + file_path_to_id[report.file.path], run_id, report.report_hash, + checker, report.line, report.column, + len(report.bug_path_events), report.message, detection_status, + review_status.status, review_status.author, review_status.message, run_history_time, - review_status.in_source, - detection_status, detection_time, - len(report.bug_path_events), analyzer_name) - - db_report.fixed_at = fixed_at - + review_status.in_source, detection_time, fixed_at) if analysis_info: db_report.analysis_info.append(analysis_info) session.add(db_report) self.__added_reports.append((db_report, report)) + if db_report.checker.checker_name == FakeChecker[1]: + self.__reports_with_fake_checkers[report_path_hash] = \ + (report, db_report) - # THE id is none at this point of time - # wondering if not returning anything is good? - # The report is already handled at the above lines return db_report.id except Exception as ex: @@ -773,6 +926,74 @@ def __add_report( codechecker_api_shared.ttypes.ErrorCode.GENERAL, str(ex)) + def __get_faked_checkers(self) \ + -> Set[Tuple[str, str]]: + """ + Extracts the "real" checker identifiers from the + __reports_with_fake_checkers that might contain some yet not fully + handled reports by __add_report(). This function does NOT touch the + database! + """ + return set(checker_name_for_report(report) + for report, _ + in self.__reports_with_fake_checkers.values()) + + def __load_report_ids_for_reports_with_fake_checkers(self, session): + """ + Transforms the __reports_with_fake_checkers data structure by loading + the report.id column in place for the DBReport ORM objects to allow + __realise_fake_checkers() to execute appropriately. + + This must only be run **once** between the __add_report() sequence and + the __realise_fake_checkers() operation. The reports **MUST** have + been committed prior. + """ + for rph in self.__reports_with_fake_checkers: + report, db_report = cast(Tuple[Report, DBReport], + self.__reports_with_fake_checkers[rph]) + # Only load the "id" field from the database, not the entire row. + session.refresh(db_report, ["id"]) + id_: int = db_report.id + + self.__reports_with_fake_checkers[rph] = (report, id_) + + def __realise_fake_checkers(self, session): + """ + __add_report() might leave some reports that have checker names in + their data that were not found in the usually full checker list + available in the 'metadata.json'. This usually happens if the report + directory that is being stored is not from an 'analyze' or 'check' + invocation (that would create an appropriate 'metadata.json') but from + other tools (e.g., report-converter, or Cppcheck's native PLISTs). + + This function assumes that for each report in the + __reports_with_fake_checkers, their actual checkers' IDs are now added + added to the database (which was not the case when __add_report() ran), + so all it does is upgrade the 'checker_id' FOREIGN KEY field to point + at the real checker. + """ + try: + grouped_by_checker: Dict[Tuple[str, str], List[int]] = \ + defaultdict(list) + for _, (report, db_id) in \ + self.__reports_with_fake_checkers.items(): + checker: Tuple[str, str] = checker_name_for_report(report) + grouped_by_checker[checker].append(cast(int, db_id)) + + for chk, report_ids in grouped_by_checker.items(): + analyzer_name, checker_name = chk + chk_obj = cast(Checker, self.__get_checker(session, + analyzer_name, + checker_name)) + session.query(DBReport) \ + .filter(DBReport.id.in_(report_ids)) \ + .update({"checker_id": chk_obj.id}, + synchronize_session=False) + except Exception as ex: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + str(ex)) + def __add_report_context(self, session, file_path_to_id): try: for db_report, report in self.__added_reports: @@ -892,7 +1113,7 @@ def get_missing_file_ids(report: Report) -> List[str]: if old_status == 'resolved' else 'unresolved' detected_at = old_report.detected_at - analyzer_name = mip.checker_to_analyzer.get( + report.analyzer_name = mip.checker_to_analyzer.get( report.checker_name, report.analyzer_name) review_status = SourceReviewStatus() @@ -918,17 +1139,18 @@ def get_missing_file_ids(report: Report) -> List[str]: fixed_at = run_history_time self.__check_report_count() - self.__add_report( - session, run_id, report, file_path_to_id, - review_status, detection_status, detected_at, - run_history_time, analysis_info, analyzer_name, fixed_at) + self.__add_report(session, run_id, report, report_path_hash, + file_path_to_id, review_status, detection_status, + detected_at, run_history_time, analysis_info, + fixed_at) self.__new_report_hashes[report.report_hash] = \ review_status.status self.__already_added_report_hashes.add(report_path_hash) - LOG.debug(f"Storing report done. bug_hash:{report.report_hash}, " + - f"source_file:{report_file_path}") + LOG.debug("Storing report done. " + "path_hash=%s, bug_id/report_hash=%s, source_file=%s", + report_path_hash, report.report_hash, report_file_path) return True @@ -1047,7 +1269,6 @@ def get_skip_handler( self.__already_added_report_hashes = set() self.__new_report_hashes = dict() self.__all_report_checkers = set() - self.__severity_map = dict() all_reports = session.query(DBReport) \ .filter(DBReport.run_id == run_id) \ @@ -1148,10 +1369,11 @@ def get_skip_handler( reports_to_delete.update([x.id for x in reports]) else: for report in reports: - checker = report.checker_id - if checker in disabled_checkers: + checker_name: str = report.checker.checker_name + if checker_name in disabled_checkers: report.detection_status = 'off' - elif checker_is_unavailable(checker, enabled_checkers): + elif checker_is_unavailable(checker_name, + enabled_checkers): report.detection_status = 'unavailable' else: report.detection_status = 'resolved' @@ -1229,121 +1451,109 @@ def store(self) -> int: run_history_time = datetime.now() # Parse all metadata information from the report directory. - for root_dir_path, _, _ in os.walk(report_dir): - metadata_file_path = os.path.join( - root_dir_path, 'metadata.json') - - self.__mips[root_dir_path] = \ - MetadataInfoParser(metadata_file_path) - - # When we use multiple server instances and we try to run - # multiple storage to each server which contain at least two - # reports which have the same report hash and have source code - # comments it is possible that the following exception will be - # thrown: (psycopg2.extensions.TransactionRollbackError) - # deadlock detected. - # The problem is that the report hash is the key for the - # review_statuses table and both of the store actions try to - # update the same review_statuses data row. - # Neither of the two processes can continue, and they will wait - # for each other indefinitely. PostgreSQL in this case will - # terminate one transaction with the above exception. - # For this reason in case of failure we will wait some seconds - # and try to run the storage again. - # For more information see #2655 and #2653 issues on github. - # TODO: Since review status is stored in "reports" table and - # "review_statuses" table is not written during storage, this - # multiple trials should be unnecessary. - max_num_of_tries = 3 - num_of_tries = 0 - sec_to_wait_after_failure = 60 - while True: - try: - # This session's transaction buffer stores the actual - # run data into the database. - with DBSession(self.__Session) as session: - # Load the lock record for "FOR UPDATE" so that the - # transaction that handles the run's store - # operations has a lock on the database row itself. - run_lock = session.query(RunLock) \ - .filter(RunLock.name == self.__name) \ - .with_for_update(nowait=True).one() - - # Do not remove this seemingly dummy print, we need - # to make sure that the execution of the SQL - # statement is not optimised away and the fetched - # row is not garbage collected. - LOG.debug("Storing into run '%s' locked at '%s'.", - self.__name, run_lock.locked_at) - - # Actual store operation begins here. - run_id, update_run = self.__add_or_update_run( - session, run_history_time) + with LogTask(run_name=self.__name, + message="Parse 'metadata.json's"): + for root_dir_path, _, _ in os.walk(report_dir): + metadata_file_path = os.path.join( + root_dir_path, 'metadata.json') + + self.__mips[root_dir_path] = \ + MetadataInfoParser(metadata_file_path) + with LogTask(run_name=self.__name, + message="Store look-up ID for checkers in " + "'metadata.json'"): + checkers_in_metadata = { + (analyzer, checker) + for metadata in self.__mips.values() + for analyzer in metadata.analyzers + for checker + in metadata.checkers.get(analyzer, dict()).keys()} + self.__store_checker_identifiers(checkers_in_metadata) + + try: + # This session's transaction buffer stores the actual + # run data into the database. + with DBSession(self.__Session) as session, \ + RunLocking(session, self.__name): + # Actual store operation begins here. + run_id, update_run = self.__add_or_update_run( + session, run_history_time) + + with LogTask(run_name=self.__name, + message="Store reports"): + self.__store_reports( + session, report_dir, source_root, run_id, + file_path_to_id, run_history_time) + + session.commit() + self.__load_report_ids_for_reports_with_fake_checkers( + session) + + if self.__reports_with_fake_checkers: + with LogTask(run_name=self.__name, + message="Get look-up ID for checkers " + "not present in 'metadata.json'"): + additional_checkers = self.__get_faked_checkers() + # __store_checker_identifiers() has its own + # TRANSACTION! + self.__store_checker_identifiers( + additional_checkers) + + with DBSession(self.__Session) as session, \ + RunLocking(session, self.__name): + # The data of the run has been successfully committed + # into the database. Deal with post-processing issues + # that could only be done after-the-fact. + if self.__reports_with_fake_checkers: with LogTask(run_name=self.__name, - message="Store reports"): - self.__store_reports( - session, report_dir, source_root, run_id, - file_path_to_id, run_history_time) - - self.finish_checker_run(session, run_id) - - session.commit() - - inc_num_of_runs = 1 - - # If it's a run update, do not increment the number - # of runs of the current product. - if update_run: - inc_num_of_runs = None - - self.__report_server._set_run_data_for_curr_product( - inc_num_of_runs, run_history_time) - - runtime = round(time.time() - start_time, 2) - zip_size_kb = round(zip_size / 1024) - - tag_desc = "" - if self.__tag: - tag_desc = f", under tag '{self.__tag}'" - - LOG.info("'%s' stored results (%s KB " - "/decompressed/) to run '%s' (id: %d) %s in " - "%s seconds.", self.user_name, - zip_size_kb, self.__name, run_id, tag_desc, - runtime) - - iso_start_time = datetime.fromtimestamp( - start_time).isoformat() - - log_msg = f"{iso_start_time}, " +\ - f"{runtime}s, " +\ - f'"{self.__product.name}", ' +\ - f'"{self.__name}", ' +\ - f"{zip_size_kb}KB, " +\ - f"{self.__report_count}, " +\ - f"{run_id}" - - STORE_TIME_LOG.info(log_msg) - - return run_id - except (sqlalchemy.exc.OperationalError, - sqlalchemy.exc.ProgrammingError) as ex: - num_of_tries += 1 - - if num_of_tries == max_num_of_tries: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes. - ErrorCode.DATABASE, - "Storing reports to the database failed: " - "{0}".format(ex)) - - LOG.error("Storing reports of '%s' run failed: " - "%s.\nWaiting %d sec before trying to store " - "it again!", self.__name, ex, - sec_to_wait_after_failure) - time.sleep(sec_to_wait_after_failure) - sec_to_wait_after_failure *= 2 + message="Fix-up report-to-checker " + "associations"): + self.__realise_fake_checkers(session) + + self.finish_checker_run(session, run_id) + session.commit() + + # If it's a run update, do not increment the number + # of runs of the current product. + inc_num_of_runs = 1 if not update_run else None + + self.__report_server._set_run_data_for_curr_product( + inc_num_of_runs, run_history_time) + + runtime = round(time.time() - start_time, 2) + zip_size_kb = round(zip_size / 1024) + + tag_desc = "" + if self.__tag: + tag_desc = f", under tag '{self.__tag}'" + + LOG.info("'%s' stored results (%s KB " + "/decompressed/) to run '%s' (id: %d) %s in " + "%s seconds.", self.user_name, + zip_size_kb, self.__name, run_id, tag_desc, + runtime) + + iso_start_time = datetime.fromtimestamp( + start_time).isoformat() + + log_msg = f"{iso_start_time}, " +\ + f"{runtime}s, " +\ + f'"{self.__product.name}", ' +\ + f'"{self.__name}", ' +\ + f"{zip_size_kb}KB, " +\ + f"{self.__report_count}, " +\ + f"{run_id}" + + STORE_TIME_LOG.info(log_msg) + + return run_id + except (sqlalchemy.exc.OperationalError, + sqlalchemy.exc.ProgrammingError) as ex: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + "Storing reports to the database failed: {0}" + .format(ex)) except Exception as ex: LOG.error("Failed to store results: %s", ex) import traceback diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py index be082d2577..41f2db1136 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -22,21 +22,27 @@ from copy import deepcopy from collections import OrderedDict, defaultdict from datetime import datetime, timedelta -from typing import Dict, List, Optional, Set, Tuple +from typing import Any, Dict, List, Optional, Set, Tuple import sqlalchemy from sqlalchemy.sql.expression import or_, and_, not_, func, \ asc, desc, union_all, select, bindparam, literal_column +from sqlalchemy.orm import contains_eager import codechecker_api_shared from codechecker_api.codeCheckerDBAccess_v6 import constants, ttypes -from codechecker_api.codeCheckerDBAccess_v6.ttypes import AnalysisInfoFilter, \ - BlameData, BlameInfo, BugPathPos, CheckerCount, Commit, CommitAuthor, \ - CommentData, DiffType, Encoding, RunHistoryData, Order, ReportData, \ - ReportDetails, ReviewData, ReviewStatusRule, ReviewStatusRuleFilter, \ - ReviewStatusRuleSortMode, ReviewStatusRuleSortType, RunData, RunFilter, \ - RunReportCount, RunSortType, RunTagCount, SourceComponentData, \ - SourceFileData, SortMode, SortType, ExportData +from codechecker_api.codeCheckerDBAccess_v6.ttypes import \ + AnalysisInfoFilter, AnalysisInfoChecker as API_AnalysisInfoChecker, \ + BlameData, BlameInfo, BugPathPos, \ + CheckerCount, Commit, CommitAuthor, CommentData, \ + DiffType, \ + Encoding, ExportData, \ + Order, \ + ReportData, ReportDetails, ReviewData, ReviewStatusRule, \ + ReviewStatusRuleFilter, ReviewStatusRuleSortMode, \ + ReviewStatusRuleSortType, RunData, RunFilter, RunHistoryData, \ + RunReportCount, RunSortType, RunTagCount, \ + SourceComponentData, SourceFileData, SortMode, SortType from codechecker_common import util from codechecker_common.logger import get_logger @@ -51,10 +57,14 @@ from ..database.config_db_model import Product from ..database.database import conv, DBSession, escape_like from ..database.run_db_model import \ - AnalysisInfo, AnalyzerStatistic, BugPathEvent, BugReportPoint, \ - CleanupPlan, CleanupPlanReportHash, Comment, ReportAnnotations, \ - ExtendedReportData, File, FileContent, Report, ReportAnalysisInfo, \ - ReviewStatus, Run, RunHistory, RunHistoryAnalysisInfo, RunLock, \ + AnalysisInfo, AnalysisInfoChecker as DB_AnalysisInfoChecker, \ + AnalyzerStatistic, \ + BugPathEvent, BugReportPoint, \ + CleanupPlan, CleanupPlanReportHash, Checker, Comment, \ + ExtendedReportData, \ + File, FileContent, \ + Report, ReportAnnotations, ReportAnalysisInfo, ReviewStatus, \ + Run, RunHistory, RunHistoryAnalysisInfo, RunLock, \ SourceComponent from .thrift_enum_helper import detection_status_enum, \ @@ -138,6 +148,8 @@ def wrapper(*args, **kwargs): except sqlalchemy.exc.SQLAlchemyError as alchemy_ex: # Convert SQLAlchemy exceptions. msg = str(alchemy_ex) + import traceback + traceback.print_exc() raise codechecker_api_shared.ttypes.RequestFailed( codechecker_api_shared.ttypes.ErrorCode.DATABASE, msg) except codechecker_api_shared.ttypes.RequestFailed as rf: @@ -233,15 +245,22 @@ def process_report_filter( for cm in report_filter.checkerMsg] AND.append(or_(*OR)) - if report_filter.checkerName: - OR = [Report.checker_id.ilike(conv(cn)) - for cn in report_filter.checkerName] - AND.append(or_(*OR)) + if report_filter.analyzerNames or report_filter.checkerName \ + or report_filter.severity: + if report_filter.analyzerNames: + OR = [Checker.analyzer_name.ilike(conv(an)) + for an in report_filter.analyzerNames] + AND.append(or_(*OR)) - if report_filter.analyzerNames: - OR = [Report.analyzer_name.ilike(conv(an)) - for an in report_filter.analyzerNames] - AND.append(or_(*OR)) + if report_filter.checkerName: + OR = [Checker.checker_name.ilike(conv(cn)) + for cn in report_filter.checkerName] + AND.append(or_(*OR)) + + if report_filter.severity: + AND.append(Checker.severity.in_(report_filter.severity)) + + join_tables.append(Checker) if report_filter.runName: OR = [Run.name.ilike(conv(rn)) @@ -280,9 +299,6 @@ def process_report_filter( AND.append(or_(*OR)) - if report_filter.severity: - AND.append(Report.severity.in_(report_filter.severity)) - if report_filter.detectionStatus: dst = list(map(detection_status_str, report_filter.detectionStatus)) @@ -948,16 +964,26 @@ def create_review_data( isInSource=is_in_source) -def apply_report_filter(q, filter_expression, join_tables): +def apply_report_filter(q, filter_expression, + join_tables: List[Any], + already_joined_tables: Optional[List[Any]] = None): """ - Applies the given filter expression and joins the File, Run and RunHistory - tables if necessary based on join_tables parameter. + Applies the given filter expression and joins the Checker, File, Run, and + RunHistory tables if necessary based on join_tables parameter. If a table + is already joined by the main query and this is indicated, that will not + be joined by this function to prevent a "duplicate alias" error. """ - if File in join_tables: + def needs_join(tbl): + return tbl in join_tables and (already_joined_tables is None or + tbl not in already_joined_tables) + + if needs_join(Checker): + q = q.join(Checker, Report.checker_id == Checker.id) + if needs_join(File): q = q.outerjoin(File, Report.file_id == File.id) - if Run in join_tables: + if needs_join(Run): q = q.outerjoin(Run, Run.id == Report.run_id) - if RunHistory in join_tables: + if needs_join(RunHistory): q = q.outerjoin(RunHistory, RunHistory.run_id == Report.run_id) return q.filter(filter_expression) @@ -969,8 +995,8 @@ def get_sort_map(sort_types, is_unique=False): SortType.FILENAME: [(File.filepath, 'filepath'), (Report.line, 'line')], SortType.BUG_PATH_LENGTH: [(Report.path_length, 'bug_path_length')], - SortType.CHECKER_NAME: [(Report.checker_id, 'checker_id')], - SortType.SEVERITY: [(Report.severity, 'severity')], + SortType.CHECKER_NAME: [(Checker.checker_name, 'checker_name')], + SortType.SEVERITY: [(Checker.severity, 'severity')], SortType.REVIEW_STATUS: [(Report.review_status, 'rw_status')], SortType.DETECTION_STATUS: [(Report.detection_status, 'dt_status')], SortType.TIMESTAMP: [('annotation_timestamp', 'annotation_timestamp')]} @@ -1538,11 +1564,28 @@ def getAnalysisInfo(self, analysis_info_filter, limit, offset): .limit(limit).offset(offset) for cmd in analysis_info_query: - command = \ - zlib.decompress(cmd.analyzer_command).decode('utf-8') + command = zlib.decompress(cmd.analyzer_command) \ + .decode("utf-8") + + checkers_q = session \ + .query(Checker.analyzer_name, + Checker.checker_name, + DB_AnalysisInfoChecker.enabled) \ + .join(Checker, DB_AnalysisInfoChecker.checker_id == + Checker.id) \ + .filter(DB_AnalysisInfoChecker. + analysis_info_id == cmd.id) + + checkers: Dict[str, Dict[str, API_AnalysisInfoChecker]] = \ + defaultdict(dict) + for chk in checkers_q.all(): + analyzer, checker, enabled = chk + checkers[analyzer][checker] = API_AnalysisInfoChecker( + enabled=enabled) res.append(ttypes.AnalysisInfo( - analyzerCommand=html.escape(command))) + analyzerCommand=html.escape(command), + checkers=checkers)) return res @@ -1609,6 +1652,8 @@ def getReport(self, reportId): result = session \ .query(Report, File) \ .filter(Report.id == reportId) \ + .join(Checker, Report.checker_id == Checker.id) \ + .options(contains_eager(Report.checker)) \ .outerjoin(File, Report.file_id == File.id) \ .limit(1).one_or_none() @@ -1627,8 +1672,9 @@ def getReport(self, reportId): fileId=source_file.id, line=report.line, column=report.column, - checkerId=report.checker_id, - severity=report.severity, + analyzerName=report.checker.analyzer_name, + checkerId=report.checker.checker_name, + severity=report.checker.severity, reviewData=create_review_data( report.review_status, report.review_status_message, @@ -1768,10 +1814,10 @@ def getRunResults(self, run_ids, limit, offset, sort_types, # # reports # ================= - # id, severity, ... - # ----------------- - # 1, HIGH, ... - # 2, MEDIUM, ... + # id, checker_id, ... + # ------------------- + # 1, 123456, ... + # 2, 999999, ... # # report_annotations # ======================= @@ -1785,10 +1831,10 @@ def getRunResults(self, run_ids, limit, offset, sort_types, # # reports extended # =================================================== - # id, severity, ..., annotation_key1, annotation_key2 + # id, checker_id, ..., annotation_key1, annotation_key2 # --------------------------------------------------- - # 1, HIGH, ..., value1, value2 - # 2, MEDIUM, ..., value3, NULL + # 1, 123456, ..., value1, value2 + # 2, 999999, ..., value3, NULL # # The SQL query which results this table is similar to this: # @@ -1855,22 +1901,22 @@ def getRunResults(self, run_ids, limit, offset, sort_types, .group_by(Report.bug_id) \ .subquery() - # Sort the results - sorted_reports = \ - session.query(unique_reports.c.id) - + # Sort the results. + sorted_reports = session.query(unique_reports.c.id) sorted_reports = sort_results_query(sorted_reports, sort_types, sort_type_map, order_type_map, True) - sorted_reports = sorted_reports \ .limit(limit).offset(offset).subquery() q = session.query(Report, File.filename, *annotation_cols.values()) \ + .join(Checker, + Report.checker_id == Checker.id) \ + .options(contains_eager(Report.checker)) \ .outerjoin( File, Report.file_id == File.id) \ @@ -1897,7 +1943,7 @@ def getRunResults(self, run_ids, limit, offset, sort_types, sort_types, sort_type_map, order_type_map) - q = q.group_by(Report.id, File.id) + q = q.group_by(Report.id, File.id, Checker.id) query_result = q.all() @@ -1908,8 +1954,7 @@ def getRunResults(self, run_ids, limit, offset, sort_types, report_details = get_report_details(session, report_ids) for row in query_result: - report = row[0] - filename = row[1] + report, filename = row[0], row[1] annotations = { k: v for k, v in zip(annotation_keys, row[2:]) if v is not None} @@ -1930,8 +1975,9 @@ def getRunResults(self, run_ids, limit, offset, sort_types, fileId=report.file_id, line=report.line, column=report.column, - checkerId=report.checker_id, - severity=report.severity, + analyzerName=report.checker.analyzer_name, + checkerId=report.checker.checker_name, + severity=report.checker.severity, reviewData=review_data, detectionStatus=detection_status_enum( report.detection_status), @@ -1939,7 +1985,6 @@ def getRunResults(self, run_ids, limit, offset, sort_types, fixedAt=str(report.fixed_at), bugPathLength=report.path_length, details=report_details.get(report.id), - analyzerName=report.analyzer_name, annotations=annotations)) else: # not is_unique filter_expression, join_tables = process_report_filter( @@ -1952,24 +1997,34 @@ def getRunResults(self, run_ids, limit, offset, sort_types, q = session.query(Report, File.filepath, *annotation_cols.values()) \ + .join(Checker, + Report.checker_id == Checker.id) \ + .options(contains_eager(Report.checker)) \ + .outerjoin(File, + Report.file_id == File.id) \ .outerjoin( ReportAnnotations, Report.id == ReportAnnotations.report_id) - if File not in join_tables: - q = q.outerjoin(File, Report.file_id == File.id) - # Grouping by "reports.id" is described at the beginning of # this function. Grouping by "files.id" is necessary, because # "files" table is joined for gathering file names belonging to # the given report. According to SQL syntax if there is a group # by report IDs then files should also be either grouped or an - # aggregate function must be applied on them. - q = q.group_by(Report.id, File.id) - - q = apply_report_filter(q, filter_expression, join_tables) - - q = sort_results_query(q, sort_types, sort_type_map, + # aggregate function must be applied on them. The same applies + # to the "checkers" table. + q = q.group_by(Report.id, File.id, Checker.id) + + # The "Checker" entity is eagerly loaded for each "Report" as + # there is a guaranteed FOREIGN KEY ... NOT NULL relationship + # to a valid entity. Because of this, letting "join_tables" + # add "Checker" here is actually ill-formed, as it would + # result in queries that ambiguously refer to the same table. + q = apply_report_filter(q, filter_expression, join_tables, + [File, Checker]) + q = sort_results_query(q, + sort_types, + sort_type_map, order_type_map) if report_filter.annotations is not None: @@ -1993,8 +2048,7 @@ def getRunResults(self, run_ids, limit, offset, sort_types, report_details = get_report_details(session, report_ids) for row in query_result: - report = row[0] - filepath = row[1] + report, filepath = row[0], row[1] annotations = { k: v for k, v in zip(annotation_keys, row[2:]) if v is not None} @@ -2015,8 +2069,9 @@ def getRunResults(self, run_ids, limit, offset, sort_types, fileId=report.file_id, line=report.line, column=report.column, - checkerId=report.checker_id, - severity=report.severity, + analyzerName=report.checker.analyzer_name, + checkerId=report.checker.checker_name, + severity=report.checker.severity, reviewData=review_data, detectionStatus=detection_status_enum( report.detection_status), @@ -2025,7 +2080,6 @@ def getRunResults(self, run_ids, limit, offset, sort_types, report.fixed_at else None, bugPathLength=report.path_length, details=report_details.get(report.id), - analyzerName=report.analyzer_name, annotations=annotations)) return results @@ -2591,11 +2645,11 @@ def getCheckerLabels( """ Return the list of labels to each checker. """ labels = [] for checker in checkers: - # Analyzer default value in the database is 'unknown' which is not - # a valid analyzer name. So this code handles this use case. - analyzer_name = None - if checker.analyzerName != "unknown": - analyzer_name = checker.analyzerName + analyzer_name = None if not checker.analyzerName \ + else str(checker.analyzerName) + analyzer_name = analyzer_name \ + if (analyzer_name and analyzer_name.lower() != "unknown") \ + else None labels.append(list(map( lambda x: f'{x[0]}:{x[1]}', @@ -2753,10 +2807,12 @@ def getCheckerCounts(self, run_ids, report_filter, cmp_data, limit, filter_expression, join_tables = process_report_filter( session, run_ids, report_filter, cmp_data) - extended_table = session.query( - Report.checker_id, - Report.severity, - Report.bug_id) + extended_table = session \ + .query(Report.bug_id, + Checker.checker_name, + Checker.severity) \ + .join(Checker, + Report.checker_id == Checker.id) if report_filter.annotations is not None: extended_table = extended_table.outerjoin( @@ -2765,18 +2821,19 @@ def getCheckerCounts(self, run_ids, report_filter, cmp_data, limit, extended_table = extended_table.group_by(Report.id) extended_table = apply_report_filter( - extended_table, filter_expression, join_tables) + extended_table, filter_expression, join_tables, [Checker]) extended_table = extended_table.subquery() if report_filter.isUnique: q = session.query( - func.max(extended_table.c.checker_id).label('checker_id'), - func.max(extended_table.c.severity).label('severity'), + func.max(extended_table.c.checker_name) + .label("checker_name"), + func.max(extended_table.c.severity).label("severity"), extended_table.c.bug_id) else: q = session.query( - extended_table.c.checker_id, + extended_table.c.checker_name, extended_table.c.severity, func.count(literal_column('*'))) @@ -2784,15 +2841,15 @@ def getCheckerCounts(self, run_ids, report_filter, cmp_data, limit, if report_filter.isUnique: q = q.group_by(extended_table.c.bug_id).subquery() - unique_checker_q = session.query(q.c.checker_id, + unique_checker_q = session.query(q.c.checker_name, func.max(q.c.severity), func.count(q.c.bug_id)) \ - .group_by(q.c.checker_id) \ - .order_by(q.c.checker_id) + .group_by(q.c.checker_name) \ + .order_by(q.c.checker_name) else: - unique_checker_q = q.group_by(extended_table.c.checker_id, + unique_checker_q = q.group_by(extended_table.c.checker_name, extended_table.c.severity) \ - .order_by(extended_table.c.checker_id) + .order_by(extended_table.c.checker_name) if limit: unique_checker_q = unique_checker_q.limit(limit).offset(offset) @@ -2822,9 +2879,11 @@ def getAnalyzerNameCounts(self, run_ids, report_filter, cmp_data, limit, filter_expression, join_tables = process_report_filter( session, run_ids, report_filter, cmp_data) - extended_table = session.query( - Report.analyzer_name, - Report.bug_id) + extended_table = session \ + .query(Checker.analyzer_name, + Report.bug_id) \ + .join(Checker, + Report.checker_id == Checker.id) if report_filter.annotations is not None: extended_table = extended_table.outerjoin( @@ -2833,7 +2892,7 @@ def getAnalyzerNameCounts(self, run_ids, report_filter, cmp_data, limit, extended_table = extended_table.group_by(Report.id) extended_table = apply_report_filter( - extended_table, filter_expression, join_tables) + extended_table, filter_expression, join_tables, [Checker]) extended_table = extended_table.subquery() @@ -2879,9 +2938,11 @@ def getSeverityCounts(self, run_ids, report_filter, cmp_data): filter_expression, join_tables = process_report_filter( session, run_ids, report_filter, cmp_data) - extended_table = session.query( - Report.severity, - Report.bug_id) + extended_table = session \ + .query(Report.bug_id, + Checker.severity) \ + .join(Checker, + Report.checker_id == Checker.id) if report_filter.annotations is not None: extended_table = extended_table.outerjoin( @@ -2890,13 +2951,13 @@ def getSeverityCounts(self, run_ids, report_filter, cmp_data): extended_table = extended_table.group_by(Report.id) extended_table = apply_report_filter( - extended_table, filter_expression, join_tables) + extended_table, filter_expression, join_tables, [Checker]) extended_table = extended_table.subquery() if report_filter.isUnique: q = session.query( - func.max(extended_table.c.severity).label('severity'), + func.max(extended_table.c.severity).label("severity"), extended_table.c.bug_id) else: q = session.query(extended_table.c.severity, diff --git a/web/server/codechecker_server/cmd/server.py b/web/server/codechecker_server/cmd/server.py index ae69773782..e18dd82213 100644 --- a/web/server/codechecker_server/cmd/server.py +++ b/web/server/codechecker_server/cmd/server.py @@ -18,10 +18,13 @@ import socket import sys import time +from typing import List import psutil from alembic import config from alembic import script +from alembic.util import CommandError +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import sessionmaker from codechecker_api_shared.ttypes import DBStatus @@ -471,18 +474,24 @@ def check_product_db_status(cfg_sql_server, migration_root, environ): :returns: dictionary of product endpoints with database statuses """ - engine = cfg_sql_server.create_engine() config_session = sessionmaker(bind=engine) sess = config_session() + products: List[ORMProduct] = list() try: - products = sess.query(ORMProduct).all() + products = sess.query(ORMProduct) \ + .order_by(ORMProduct.endpoint.asc()) \ + .all() except Exception as ex: LOG.debug(ex) LOG.error("Failed to get product configurations from the database.") LOG.error("Please check your command arguments.") sys.exit(1) + finally: + # sys.exit raises SystemExit, which still performs finally clauses! + sess.close() + engine.dispose() package_schema = get_schema_version_from_package(migration_root) @@ -499,15 +508,20 @@ def check_product_db_status(cfg_sql_server, migration_root, environ): interactive=False, env=environ) db_location = db.get_db_location() - ret = db.connect() - s_ver = db.get_schema_version() - if s_ver in db_errors: - s_ver = None - prod_status[pd.endpoint] = (ret, s_ver, package_schema, db_location) - sess.commit() - sess.close() - engine.dispose() + try: + status = db.connect() + s_ver = db.get_schema_version() + if s_ver in db_errors: + s_ver = None + prod_status[pd.endpoint] = (status, s_ver, package_schema, + db_location) + except Exception: + LOG.error("Unable to get the status for product '%s', " + "considering as if the connection failed.", + pd.endpoint) + prod_status[pd.endpoint] = (DBStatus.FAILED_TO_CONNECT, None, + package_schema, db_location) return prod_status @@ -539,11 +553,17 @@ def __db_status_check(cfg_sql_server, migration_root, environ, return 0 +class NonExistentProductError(Exception): + def __init__(self, product_name): + super().__init__(f"Non-existent product '{product_name}'") + self.product_name = product_name + + def __db_migration(cfg_sql_server, migration_root, environ, - product_to_upgrade='all', force_upgrade=False): + product_to_upgrade: str = 'all', + force_upgrade: bool = False): """ - Handle database management. - Schema checking and migration. + Handles database management, schema checking and migrations. """ LOG.info("Preparing schema upgrade for %s", str(product_to_upgrade)) product_name = product_to_upgrade @@ -551,9 +571,9 @@ def __db_migration(cfg_sql_server, migration_root, environ, prod_statuses = check_product_db_status(cfg_sql_server, migration_root, environ) - prod_to_upgrade = [] + prod_to_upgrade: List[str] = list() - if product_name != 'all': + if product_name != "all": avail = prod_statuses.get(product_name) if not avail: LOG.error("No product was found with this endpoint: %s", @@ -562,61 +582,93 @@ def __db_migration(cfg_sql_server, migration_root, environ, prod_to_upgrade.append(product_name) else: prod_to_upgrade = list(prod_statuses.keys()) + prod_to_upgrade.sort() - LOG.warning("Please note after migration only " - "newer CodeChecker versions can be used " - "to start the server") - LOG.warning("It is advised to make a full backup of your " - "run databases.") + LOG.warning("Please note after migration only newer CodeChecker versions " + "can be used to start the server") + LOG.warning("It is advised to make a full backup of your run databases.") for prod in prod_to_upgrade: LOG.info("========================") LOG.info("Checking: %s", prod) - engine = cfg_sql_server.create_engine() - config_session = sessionmaker(bind=engine) - sess = config_session() - product = sess.query(ORMProduct).filter( - ORMProduct.endpoint == prod).first() - db = database.SQLServer.from_connection_string(product.connection, - RUN_META, - migration_root, - interactive=False, - env=environ) - - db_status = db.connect() + endpoint, connection_str = None, None + try: + # Obtain the configuration information for the current product. + engine = cfg_sql_server.create_engine() + config_session = sessionmaker(bind=engine) + sess = config_session() + product = sess.query(ORMProduct).filter( + ORMProduct.endpoint == prod).first() + if product is None: + raise NonExistentProductError(prod) + + endpoint = product.endpoint + connection_str = product.connection + + # Close the connection to the CONFIG database. It is not needed + # anymore, but an intermittent timeout would cause scary + # exceptions if the migration itself is running too long. + sess.close() + engine.dispose() + except NonExistentProductError as nepe: + LOG.error("Attempted to upgrade product '%s', but it was not " + "found in the server's configuration database.", + nepe.product_name) + except Exception: + LOG.error("Failed to get the configuration for product '%s'", + prod) + import traceback + traceback.print_exc() - msg = database_status.db_status_msg.get(db_status, - 'Unknown database status') + if not endpoint or not connection_str: + continue - LOG.info(msg) - if db_status == DBStatus.SCHEMA_MISSING: - question = 'Do you want to initialize a new schema for ' \ - + product.endpoint + '? Y(es)/n(o) ' - if force_upgrade or env.get_user_input(question): - ret = db.connect(init=True) - msg = database_status.db_status_msg.get( - ret, 'Unknown database status') - LOG.info(msg) - else: - LOG.info("No schema initialization was done.") - - elif db_status == DBStatus.SCHEMA_MISMATCH_OK: - question = 'Do you want to upgrade to new schema for ' \ - + product.endpoint + '? Y(es)/n(o) ' - if force_upgrade or env.get_user_input(question): - LOG.info("Upgrading schema ...") - ret = db.upgrade() - LOG.info("Done.") - msg = database_status.db_status_msg.get( - ret, 'Unknown database status') - LOG.info(msg) - else: - LOG.info("No schema migration was done.") + try: + db = database.SQLServer.from_connection_string(connection_str, + RUN_META, + migration_root, + interactive=False, + env=environ) + db_status = db.connect() + + status_str = database_status.db_status_msg.get( + db_status, "Unknown database status") + LOG.info(status_str) + + if db_status == DBStatus.SCHEMA_MISSING: + question = "Do you want to initialize a new schema for " \ + + endpoint + "? Y(es)/n(o) " + if force_upgrade or env.get_user_input(question): + conn_status = db.connect(init=True) + status_str = database_status.db_status_msg.get( + conn_status, "Unknown database status") + LOG.info(status_str) + else: + LOG.info("No schema initialization was done.") + elif db_status == DBStatus.SCHEMA_MISMATCH_OK: + question = "Do you want to upgrade to new schema for " \ + + endpoint + "? Y(es)/n(o) " + if force_upgrade or env.get_user_input(question): + LOG.info("Upgrading schema ...") + new_status = db.upgrade() + LOG.info("Done.") + status_str = database_status.db_status_msg.get( + new_status, "Unknown database status") + LOG.info(status_str) + else: + LOG.info("No schema migration was done.") + except (CommandError, SQLAlchemyError): + LOG.error("A database error occurred during the init/migration " + "of '%s'", prod) + import traceback + traceback.print_exc() + except Exception as e: + LOG.error("A generic error '%s' occurred during the " + "init/migration of '%s'", str(type(e)), prod) + import traceback + traceback.print_exc() - sess.commit() - sess.close() - engine.dispose() LOG.info("========================") return 0 @@ -949,11 +1001,11 @@ def server_init_start(args): break if non_ok_db: - msg = "There are some database issues. " \ - "Do you want to start the " \ - "server? Y(es)/n(o) " - if not env.get_user_input(msg): - sys.exit(1) + print("There are some database issues.") + if not force_upgrade: + msg = "Do you want to start the server? Y(es)/n(o) " + if not env.get_user_input(msg): + sys.exit(1) # Start database viewer. package_data = {'www_root': context.www_root, diff --git a/web/server/codechecker_server/database/config_db_model.py b/web/server/codechecker_server/database/config_db_model.py index 376162d90c..bfccb91eb6 100644 --- a/web/server/codechecker_server/database/config_db_model.py +++ b/web/server/codechecker_server/database/config_db_model.py @@ -11,8 +11,8 @@ from datetime import datetime import sys -from sqlalchemy import MetaData, Column, Integer, Enum, String, Boolean, \ - ForeignKey, CHAR, DateTime, Text +from sqlalchemy import Boolean, CHAR, Column, DateTime, Enum, ForeignKey, \ + Integer, MetaData, String, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql.expression import false, true diff --git a/web/server/codechecker_server/database/database.py b/web/server/codechecker_server/database/database.py index 4043947be4..2faa4891fa 100644 --- a/web/server/codechecker_server/database/database.py +++ b/web/server/codechecker_server/database/database.py @@ -203,6 +203,8 @@ def _create_schema(self): except Exception as ex: LOG.error("Failed to create initial database schema") LOG.error(ex) + import traceback + traceback.print_exc() return False def get_schema_version(self): @@ -328,10 +330,14 @@ def upgrade(self): return DBStatus.OK except sqlalchemy.exc.SQLAlchemyError as alch_err: + import traceback + traceback.print_exc() LOG.error(str(alch_err)) return DBStatus.SCHEMA_UPGRADE_FAILED except CommandError as cerr: + import traceback + traceback.print_exc() LOG.debug(str(cerr)) return DBStatus.SCHEMA_UPGRADE_FAILED diff --git a/web/server/codechecker_server/database/db_cleanup.py b/web/server/codechecker_server/database/db_cleanup.py index c0c54afd25..8c6feacdd6 100644 --- a/web/server/codechecker_server/database/db_cleanup.py +++ b/web/server/codechecker_server/database/db_cleanup.py @@ -10,9 +10,9 @@ or dangling records from the database. """ from datetime import datetime, timedelta +from typing import Dict import sqlalchemy -from sqlalchemy.sql.expression import bindparam, union_all, select, cast from codechecker_api.codeCheckerDBAccess_v6.ttypes import Severity @@ -20,26 +20,50 @@ from codechecker_common.logger import get_logger from .database import DBSession -from .run_db_model import AnalysisInfo, BugPathEvent, BugReportPoint, \ - Comment, File, FileContent, Report, ReportAnalysisInfo, \ - RunHistoryAnalysisInfo, RunLock +from .run_db_model import \ + AnalysisInfo, \ + BugPathEvent, BugReportPoint, \ + Comment, Checker, \ + File, FileContent, \ + Report, ReportAnalysisInfo, RunHistoryAnalysisInfo, RunLock LOG = get_logger('server') RUN_LOCK_TIMEOUT_IN_DATABASE = 30 * 60 # 30 minutes. SQLITE_LIMIT_COMPOUND_SELECT = 500 -def remove_expired_run_locks(session_maker): - LOG.debug("Garbage collection of expired run locks started...") +def remove_expired_data(session_maker): + """ Remove information that has timed out from the database. """ + remove_expired_run_locks(session_maker) + + +def remove_unused_data(session_maker): + """ Remove dangling data (files, comments, etc.) from the database. """ + remove_unused_files(session_maker) + remove_unused_comments(session_maker) + remove_unused_analysis_info(session_maker) + +def update_contextual_data(session_maker, context): + """ + Updates information in the database that comes from potentially changing + contextual configuration of the server package. + """ + upgrade_severity_levels(session_maker, context.checker_labels) + + +def remove_expired_run_locks(session_maker): with DBSession(session_maker) as session: + LOG.debug("Garbage collection of expired run locks started...") try: locks_expired_at = datetime.now() - timedelta( seconds=RUN_LOCK_TIMEOUT_IN_DATABASE) - session.query(RunLock) \ + count = session.query(RunLock) \ .filter(RunLock.locked_at < locks_expired_at) \ .delete(synchronize_session=False) + if count: + LOG.debug("%d expired run locks deleted.", count) session.commit() @@ -50,17 +74,16 @@ def remove_expired_run_locks(session_maker): def remove_unused_files(session_maker): - LOG.debug("Garbage collection of dangling files started...") - # File deletion is a relatively slow operation due to database cascades. # Removing files in big chunks prevents reaching a potential database # statement timeout. This hard-coded value is a safe choice according to # some measurements. Maybe this could be a command-line parameter. But in # the long terms we are planning to reduce cascade deletes by redesigning # bug_path_events and bug_report_points tables. - CHUNK_SIZE = 500000 + CHUNK_SIZE = 500_000 with DBSession(session_maker) as session: + LOG.debug("Garbage collection of dangling files started...") try: bpe_files = session.query(BugPathEvent.file_id) \ .group_by(BugPathEvent.file_id) \ @@ -73,10 +96,16 @@ def remove_unused_files(session_maker): .filter(File.id.notin_(bpe_files), File.id.notin_(brp_files)) files_to_delete = map(lambda x: x[0], files_to_delete) + total_count = 0 for chunk in util.chunks(iter(files_to_delete), CHUNK_SIZE): - session.query(File) \ - .filter(File.id.in_(chunk)) \ - .delete(synchronize_session=False) + q = session.query(File) \ + .filter(File.id.in_(chunk)) + count = q.delete(synchronize_session=False) + if count: + total_count += count + + if total_count: + LOG.debug("%d dangling files deleted.", total_count) files = session.query(File.content_hash) \ .group_by(File.content_hash) \ @@ -94,26 +123,20 @@ def remove_unused_files(session_maker): LOG.error("Failed to remove unused files: %s", str(ex)) -def remove_unused_data(session_maker): - """ Remove dangling data (files, comments, etc.) from the database. """ - remove_unused_files(session_maker) - remove_unused_comments(session_maker) - remove_unused_analysis_info(session_maker) - - def remove_unused_comments(session_maker): """ Remove dangling comments from the database. """ - LOG.debug("Garbage collection of dangling comments started...") - with DBSession(session_maker) as session: + LOG.debug("Garbage collection of dangling comments started...") try: report_hashes = session.query(Report.bug_id) \ .group_by(Report.bug_id) \ .subquery() - session.query(Comment) \ + count = session.query(Comment) \ .filter(Comment.bug_hash.notin_(report_hashes)) \ .delete(synchronize_session=False) + if count: + LOG.debug("%d dangling comments deleted.", count) session.commit() @@ -123,78 +146,6 @@ def remove_unused_comments(session_maker): LOG.error("Failed to remove dangling comments: %s", str(ex)) -def upgrade_severity_levels(session_maker, checker_labels): - """ - Updates the potentially changed severities at the reports. - """ - LOG.debug("Upgrading severity levels started...") - - severity_map = {} - for checker in checker_labels.checkers(): - severity_map[checker] = checker_labels.severity(checker) - - for severity_map_small in util.chunks( - iter(severity_map.items()), SQLITE_LIMIT_COMPOUND_SELECT): - severity_map_small = dict(severity_map_small) - - with DBSession(session_maker) as session: - try: - # Create a sql query from the severity map. - severity_map_q = union_all(*[ - select([cast(bindparam('checker_id' + str(i), - str(checker_id)) - .label('checker_id'), sqlalchemy.String), - cast(bindparam('severity' + str(i), - Severity._NAMES_TO_VALUES[ - severity_map_small[checker_id]]) - .label('severity'), sqlalchemy.Integer)]) - for i, checker_id in enumerate(severity_map_small)]) \ - .alias('new_severities') - - checker_ids = list(severity_map_small.keys()) - - # Get checkers which has been changed. - changed_checker_q = select( - [Report.checker_id, Report.severity]) \ - .group_by(Report.checker_id, Report.severity) \ - .where(Report.checker_id.in_(checker_ids)) \ - .except_(session.query(severity_map_q)) \ - .alias('changed_severites') - - changed_checkers = session.query( - changed_checker_q.c.checker_id, - changed_checker_q.c.severity) - - # Update severity levels of checkers. - if changed_checkers: - updated_checker_ids = set() - for checker_id, severity_old in changed_checkers: - severity_new = severity_map_small[checker_id] - severity_id = Severity._NAMES_TO_VALUES[severity_new] - - LOG.info("Upgrading severity level of '%s' checker " - "from %s to %s", - checker_id, - Severity._VALUES_TO_NAMES[severity_old], - severity_new) - - if checker_id in updated_checker_ids: - continue - - session.query(Report) \ - .filter(Report.checker_id == checker_id) \ - .update({Report.severity: severity_id}) - - updated_checker_ids.add(checker_id) - - session.commit() - - LOG.debug("Upgrading of severity levels finished...") - except (sqlalchemy.exc.OperationalError, - sqlalchemy.exc.ProgrammingError) as ex: - LOG.error("Failed to upgrade severity levels: %s", str(ex)) - - def remove_unused_analysis_info(session_maker): """ Remove unused analysis information from the database. """ # Analysis info deletion is a relatively slow operation due to database @@ -203,9 +154,8 @@ def remove_unused_analysis_info(session_maker): # according to some measurements. CHUNK_SIZE = 500 - LOG.debug("Garbage collection of dangling analysis info started...") - with DBSession(session_maker) as session: + LOG.debug("Garbage collection of dangling analysis info started...") try: to_delete = session.query(AnalysisInfo.id) \ .join( @@ -223,13 +173,132 @@ def remove_unused_analysis_info(session_maker): to_delete = map(lambda x: x[0], to_delete) + total_count = 0 for chunk in util.chunks(to_delete, CHUNK_SIZE): - session.query(AnalysisInfo) \ + count = session.query(AnalysisInfo) \ .filter(AnalysisInfo.id.in_(chunk)) \ .delete(synchronize_session=False) - session.commit() + if count: + total_count += count + + if total_count: + LOG.debug("%d dangling analysis info deleted.", total_count) + + session.commit() LOG.debug("Garbage collection of dangling analysis info finished.") except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.ProgrammingError) as ex: LOG.error("Failed to remove dangling analysis info: %s", str(ex)) + + +def upgrade_severity_levels(session_maker, checker_labels): + """ + Updates the potentially changed severities to reflect the data in the + current label configuration files. + """ + with DBSession(session_maker) as session: + LOG.debug("Upgrading severity levels started...") + try: + count = 0 + for analyzer in sorted(checker_labels.get_analyzers()): + checkers_for_analyzer_in_database = session \ + .query(Checker.id, + Checker.checker_name, + Checker.severity) \ + .filter(Checker.analyzer_name == analyzer) \ + .all() + for checker_row in checkers_for_analyzer_in_database: + checker: str = checker_row.checker_name + old_severity_db: int = checker_row.severity + try: + old_severity: str = \ + Severity._VALUES_TO_NAMES[old_severity_db] + except KeyError: + LOG.error("Checker '%s/%s' contains invalid " + "severity %d, considering as if " + "'UNSPECIFIED' (0)!", + analyzer, checker, old_severity_db) + old_severity_db, old_severity = 0, "UNSPECIFIED" + new_severity: str = \ + checker_labels.severity(checker, analyzer) + + if old_severity == new_severity: + continue + + if new_severity == "UNSPECIFIED": + # No exact match for the checker's name in the + # label config for the analyzer. This can mean that + # the records are older than a change in the checker + # naming scheme (e.g., cppchecker results pre-2021). + LOG.warning("Checker '%s/%s' (database severity: " + "'%s' (%d)) does not have a " + "corresponding entry in the label " + "config file.", + analyzer, checker, + old_severity, old_severity_db) + + new_sev_attempts: Dict[str, str] = { + chk_name: severity + for chk_name, severity in + ((name_attempt, + checker_labels.severity(name_attempt, analyzer)) + for name_attempt in [ + "%s.%s" % (analyzer, checker), + "%s-%s" % (analyzer, checker), + "%s/%s" % (analyzer, checker) + ]) + if severity != "UNSPECIFIED" + } + + if len(new_sev_attempts) == 0: + LOG.debug("%s/%s: Keeping the old severity " + "intact...", analyzer, checker) + continue + if len(new_sev_attempts) >= 2 and \ + len(set(new_sev_attempts.values())) >= 2: + LOG.error("%s/%s: Multiple similar checkers " + "WITH CONFLICTING SEVERITIES were " + "found instead: %s", + analyzer, checker, + str(list(new_sev_attempts.items()))) + LOG.debug("%s/%s: Keeping the old severity " + "intact...", analyzer, checker) + continue + if len(set(new_sev_attempts.values())) == 1: + attempted_name, new_severity = \ + next(iter(sorted(new_sev_attempts.items()))) + + LOG.info("%s/%s: Found similar checker '%s/%s' " + "(severity: '%s'), using this for the " + "upgrade.", + analyzer, checker, + analyzer, attempted_name, + new_severity) + if old_severity == new_severity: + continue + + new_severity_db: int = \ + Severity._NAMES_TO_VALUES[new_severity] + + LOG.info("Upgrading the severity of checker " + "'%s/%s' from '%s' (%d) to '%s' (%d).", + analyzer, checker, + old_severity, old_severity_db, + new_severity, new_severity_db) + session.query(Checker) \ + .filter(Checker.id == checker_row.id) \ + .update({Checker.severity: new_severity_db}) + count += 1 + + session.flush() + + if count: + LOG.debug("%d checker severities upgraded.", count) + + session.commit() + + LOG.debug("Upgrading severity levels finished.") + except (sqlalchemy.exc.OperationalError, + sqlalchemy.exc.ProgrammingError) as ex: + LOG.error("Failed to upgrade severity levels: %s", str(ex)) diff --git a/web/server/codechecker_server/database/run_db_model.py b/web/server/codechecker_server/database/run_db_model.py index 8862d1eed4..c8eaec963f 100644 --- a/web/server/codechecker_server/database/run_db_model.py +++ b/web/server/codechecker_server/database/run_db_model.py @@ -11,6 +11,7 @@ from datetime import datetime, timedelta from math import ceil import os +from typing import Optional from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Integer, \ LargeBinary, MetaData, String, UniqueConstraint, Table, Text @@ -30,13 +31,62 @@ Base = declarative_base(metadata=CC_META) +class Checker(Base): + """ + Records of a look-up table that associates a product-global ID for each + analyzer name and checker name encountered. + """ + __tablename__ = "checkers" + + id = Column(Integer, autoincrement=True, primary_key=True) + analyzer_name = Column(String) + checker_name = Column(String) + severity = Column(Integer, index=True) + + __table_args__ = ( + UniqueConstraint("analyzer_name", "checker_name"), + ) + + def __init__(self, analyzer_name: str, checker_name: str, severity: int): + self.analyzer_name = analyzer_name + self.checker_name = checker_name + self.severity = severity + + +class AnalysisInfoChecker(Base): + __tablename__ = "analysis_info_checkers" + + analysis_info_id = Column(Integer, + ForeignKey("analysis_info.id", + deferrable=True, + initially="DEFERRED", + ondelete="CASCADE"), + primary_key=True) + checker_id = Column(Integer, + ForeignKey("checkers.id", + deferrable=True, + initially="DEFERRED", + ondelete="RESTRICT"), + primary_key=True) + enabled = Column(Boolean) + + def __init__(self, + analysis_info: "AnalysisInfo", + checker: Checker, + is_enabled: bool): + self.analysis_info_id = analysis_info.id + self.checker_id = checker.id + self.enabled = is_enabled + + class AnalysisInfo(Base): - __tablename__ = 'analysis_info' + __tablename__ = "analysis_info" id = Column(Integer, autoincrement=True, primary_key=True) analyzer_command = Column(LargeBinary) + available_checkers = relationship(AnalysisInfoChecker, uselist=True) - def __init__(self, analyzer_command): + def __init__(self, analyzer_command: bytes): self.analyzer_command = analyzer_command @@ -352,16 +402,17 @@ class Report(Base): ondelete='CASCADE'), index=True) bug_id = Column(String, index=True) - checker_id = Column(String) - checker_cat = Column(String) - bug_type = Column(String) - severity = Column(Integer) + checker_id = Column(Integer, ForeignKey("checkers.id", + deferrable=False, + ondelete="RESTRICT"), + nullable=False, + index=True) + checker = relationship(Checker, innerjoin=True, lazy="joined", + foreign_keys=[checker_id]) + line = Column(Integer) column = Column(Integer) path_length = Column(Integer) - analyzer_name = Column(String, - nullable=False, - server_default="unknown") # TODO: multiple messages to multiple source locations? checker_message = Column(String) @@ -403,32 +454,38 @@ class Report(Base): annotations = relationship("ReportAnnotations") - # Priority/severity etc... - def __init__(self, run_id, bug_id, file_id, checker_message, checker_id, - checker_cat, bug_type, line, column, severity, review_status, - review_status_author, review_status_message, - review_status_date, review_status_is_in_source, - detection_status, detection_date, path_length, - analyzer_name=None): - self.run_id = run_id + def __init__(self, + file_id: int, + run_id: int, + bug_id: Optional[str], + checker: Checker, + line: int, + column: int, + path_length: int, + checker_message: str, + detection_status, + review_status, + review_status_author: Optional[str], + review_status_message: Optional[bytes], + review_status_date: Optional[datetime], + review_status_is_in_source: bool, detection_date: datetime, + fixed_date: Optional[datetime]): self.file_id = file_id + self.run_id = run_id self.bug_id = bug_id + self.checker = checker + self.line = line + self.column = column + self.path_length = path_length self.checker_message = checker_message - self.severity = severity - self.checker_id = checker_id - self.checker_cat = checker_cat - self.bug_type = bug_type + self.detection_status = detection_status self.review_status = review_status self.review_status_author = review_status_author self.review_status_message = review_status_message self.review_status_date = review_status_date self.review_status_is_in_source = review_status_is_in_source - self.detection_status = detection_status - self.line = line - self.column = column self.detected_at = detection_date - self.path_length = path_length - self.analyzer_name = analyzer_name + self.fixed_at = fixed_date class ReportAnnotations(Base): diff --git a/web/server/codechecker_server/metadata.py b/web/server/codechecker_server/metadata.py index 6c9e69977d..13aef8b2fc 100644 --- a/web/server/codechecker_server/metadata.py +++ b/web/server/codechecker_server/metadata.py @@ -9,7 +9,7 @@ Helpers to parse metadata.json file. """ -from typing import Any, Dict, List, Optional, Set, Union +from typing import Any, Dict, Iterable, List, Optional, Set, cast import os from codechecker_common.logger import get_logger @@ -20,13 +20,18 @@ AnalyzerStatistics = Any +AnalyzerList = List[str] CheckCommands = List[str] CheckDurations = List[float] +CheckerNamesView = Iterable[str] CheckerToAnalyzer = Dict[str, str] CodeCheckerVersion = Optional[str] DisabledCheckers = Set[str] EnabledCheckers = Set[str] -MetadataCheckers = Dict[str, Union[Dict[str, bool], List[str]]] +# Checker name to enabled status. +MetadataCheckerInfo = Dict[str, bool] +# Analyzer to checker info. +MetadataCheckers = Dict[str, MetadataCheckerInfo] def checker_is_unavailable( @@ -61,32 +66,29 @@ def __init__(self, metadata_file_path): self.disabled_checkers: DisabledCheckers = set() self.checker_to_analyzer: CheckerToAnalyzer = dict() - self.__metadata_dict = {} + self.__metadata_dict: Dict[str, Any] = {} if os.path.isfile(metadata_file_path): - self.__metadata_dict = load_json(metadata_file_path, {}) + self.__metadata_dict = cast(Dict[str, Any], + load_json(metadata_file_path, {})) if 'version' in self.__metadata_dict: self.__process_metadata_info_v2() else: self.__process_metadata_info_v1() + self.analyzers: AnalyzerList = list(self.checkers.keys()) + def __process_metadata_checkers(self): """ Get enabled/disabled checkers and a checker to analyze dictionary. """ for analyzer_name, analyzer_checkers in self.checkers.items(): - if isinstance(analyzer_checkers, dict): - for checker_name, enabled in analyzer_checkers.items(): - self.checker_to_analyzer[checker_name] = analyzer_name - if enabled: - self.enabled_checkers.add(checker_name) - else: - self.disabled_checkers.add(checker_name) - else: - self.enabled_checkers.update(analyzer_checkers) - - for checker_name in analyzer_checkers: - self.checker_to_analyzer[checker_name] = analyzer_name + for checker_name, enabled in analyzer_checkers.items(): + self.checker_to_analyzer[checker_name] = analyzer_name + if enabled: + self.enabled_checkers.add(checker_name) + else: + self.disabled_checkers.add(checker_name) def __process_metadata_info_v1(self): """ Set metadata information from the old version json file. """ @@ -109,6 +111,24 @@ def __process_metadata_info_v1(self): # Get analyzer checkers. self.checkers = self.__metadata_dict.get('checkers', {}) + for analyzer, checkers in self.checkers.items(): + if isinstance(checkers, list): + # Version 1 metadata files originally only stored the list of + # enabled checkers grouped by analyzer. This was true from at + # least September 2017 + # (commit 7254d05a8b7262e4979ac613f32d6c3e0aa0d3cc) all the + # way to March 2020 + # (commit bd775d60950d48884b8f1dc83d8b82653b83cfa3). Before + # the official introduction of "v2" files, in December 2019, + # (commit 0cd28acac7d31e4a0260c147b8e803b7a36908f0) the + # ability to store the enabled status (bool) of a checker + # was added. However, the mismatch between the representation + # types in old formats are causing all sorts of troubles when + # getting the 'checkers' data structure, so instead, represent + # the structure with the new format even for a "v1" file. + # (See web/tests/functional/report_viewer_api) + self.checkers[analyzer] = {checker: True + for checker in checkers} self.__process_metadata_checkers() def __insert_analyzer_statistics( diff --git a/web/server/codechecker_server/migrations/config/README b/web/server/codechecker_server/migrations/config/README deleted file mode 100644 index 98e4f9c44e..0000000000 --- a/web/server/codechecker_server/migrations/config/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/web/server/codechecker_server/migrations/config/versions/4db450cf38af_add_extra_product_detail_columns.py b/web/server/codechecker_server/migrations/config/versions/4db450cf38af_add_extra_product_detail_columns.py index a1b6cbef61..0226b3954a 100644 --- a/web/server/codechecker_server/migrations/config/versions/4db450cf38af_add_extra_product_detail_columns.py +++ b/web/server/codechecker_server/migrations/config/versions/4db450cf38af_add_extra_product_detail_columns.py @@ -12,18 +12,19 @@ branch_labels = None depends_on = None +from logging import getLogger + from alembic import op import sqlalchemy as sa -from codechecker_common.logger import get_logger from codechecker_server.database import database from codechecker_server.database.run_db_model import IDENTIFIER as RUN_META from codechecker_web.shared import webserver_context -LOG = get_logger('system') def upgrade(): + LOG = getLogger("migration") op.add_column( 'products', sa.Column( diff --git a/web/server/codechecker_server/migrations/report/README b/web/server/codechecker_server/migrations/report/README deleted file mode 100644 index 98e4f9c44e..0000000000 --- a/web/server/codechecker_server/migrations/report/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/web/server/codechecker_server/migrations/report/env.py b/web/server/codechecker_server/migrations/report/env.py index 40bfb4466e..0bdd89f0f8 100644 --- a/web/server/codechecker_server/migrations/report/env.py +++ b/web/server/codechecker_server/migrations/report/env.py @@ -5,15 +5,15 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # # ------------------------------------------------------------------------- - +import logging import os import sys from alembic import context from sqlalchemy import engine_from_config, pool -# This is the Alembic Config object, which provides -# access to the values within the .ini file in use. +# This is the Alembic Config object, which provides access to the values +# within the .ini file in use. config = context.config # Add model's MetaData object here for 'autogenerate' support. @@ -24,11 +24,61 @@ target_metadata = Base.metadata +# Other values from the config, defined by the needs of env.py, can be +# acquired: my_important_option = config.get_main_option("my_important_option") + + +class MigrationFormatter(logging.Formatter): + """ + Truncates the filename to show only the revision that is being migrated + in the log output. + """ + def __init__(self): + super().__init__(fmt="[%(levelname)s][%(asctime)s] " + "{migration/report} " + "[%(schemaVersion)s]:%(lineno)d " + "- %(message)s", + datefmt="%Y-%m-%d %H:%M:%S") + + def format(self, record): + record.schemaVersion = record.filename[:record.filename.find("_")] + return super().format(record) + + +def setup_logger(): + """ + Set up a logging system that should be used during schema migration. + These outputs are not affected by the environment that executes a migration, + e.g., by the running CodeChecker server! + + In migration scripts, use the built-in logging facilities instead of + CodeChecker's wrapper, and ensure that the name of the logger created + exactly matches "migration"! + """ + sys_logger = logging.getLogger("system") + codechecker_loglvl = sys_logger.getEffectiveLevel() + if codechecker_loglvl >= logging.INFO: + # This might be 30 (WARNING) if the migration is run outside of + # CodeChecker's context, e.g., in a downgrade. + codechecker_loglvl = logging.INFO + + # Use the default logging class that came with Python for the migration, + # temporarily turning away from potentially existing global changes. + existing_logger_cls = logging.getLoggerClass() + logging.setLoggerClass(logging.Logger) + logger = logging.getLogger("migration") + logging.setLoggerClass(existing_logger_cls) + + if not logger.hasHandlers(): + fmt = MigrationFormatter() + handler = logging.StreamHandler() + handler.setFormatter(fmt) + handler.setLevel(codechecker_loglvl) + handler.setStream(sys.stdout) + + logger.setLevel(codechecker_loglvl) + logger.addHandler(handler) -# Other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. def run_migrations_offline(): """Run migrations in 'offline' mode. @@ -71,7 +121,7 @@ def run_migrations_online(): with context.begin_transaction(): context.run_migrations() - +setup_logger() if context.is_offline_mode(): run_migrations_offline() else: 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 663dd73591..3254bf5c46 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 @@ -56,6 +56,11 @@ def decode_file_content(content): batch_op.add_column(col_rs_message) op.execute('PRAGMA foreign_keys=on') + # FIXME: "UPDATE ... SET ... FROM ..." is only supported starting + # with SQLite version 3.33.0 (2020-08-14). Until this version + # reaches LTS maturity (Ubuntu 20.04 LTS comes with 3.31.0, raising + # a syntax error on the "FROM" in the "UPDATE" query), this + # branching here needs to stay. conn.execute(""" UPDATE reports SET (review_status, review_status_author, review_status_date, review_status_message) = diff --git a/web/server/codechecker_server/migrations/report/versions/c3dad71f8e6b_store_information_about_enabled_and_disabled_checkers_for_a_run.py b/web/server/codechecker_server/migrations/report/versions/c3dad71f8e6b_store_information_about_enabled_and_disabled_checkers_for_a_run.py new file mode 100644 index 0000000000..22af73c8ef --- /dev/null +++ b/web/server/codechecker_server/migrations/report/versions/c3dad71f8e6b_store_information_about_enabled_and_disabled_checkers_for_a_run.py @@ -0,0 +1,541 @@ +"""Store information about enabled and disabled checkers for a run + +Revision ID: c3dad71f8e6b +Revises: 9d956a0fae8d +Create Date: 2023-10-20 14:11:48.371981 + +""" + +# revision identifiers, used by Alembic. +revision = 'c3dad71f8e6b' +down_revision = '9d956a0fae8d' +branch_labels = None +depends_on = None + + +from logging import getLogger +from typing import Dict, Tuple + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.orm import Session +from sqlalchemy.sql.expression import and_, func, not_ +from sqlalchemy.ext.automap import automap_base + +from codechecker_report_converter.report import FakeChecker, UnknownChecker + + +REPORT_UPDATE_CHUNK_SIZE = 1_000_000 + + +def upgrade(): + # Note: The instantiation of the LOG variable *MUST* stay here so that it + # uses the facilities that are sourced from the Alembic env.py. + # Symbols created on the module-level are created *before* Alembic's env.py + # had loaded. + LOG = getLogger("migration") + dialect = op.get_context().dialect.name + conn = op.get_bind() + + def normalise_report_analyzer_and_checker_names(): + # Unknown analyzers and checkers might be represented in the database + # with several values all meaning the same, gathered over the years. + # Notably, the DEFAULT value for 'analyzer_name' is "unknown" prior + # to the upgrade. While 'checker_id' can be NULL, it could be an empty + # string, or a "NOT FOUND" created by the report-converter, for reports + # newer than the existence of the report-converter. + # These values are normalised such that in the following, when the + # foreign key-based look-up is added to the schema, their new + # 'checker_id' will all point to the single "UNKNOWN/NOT FOUND" case. + LOG.info("Normalising unknowns in 'reports'.'analyzer_name' and " + "'reports'.'checker_id'...") + + analyzer_name_affected = conn.execute(f""" + UPDATE reports + SET analyzer_name = '{UnknownChecker[0]}' + WHERE analyzer_name = '' + OR LOWER(analyzer_name) = 'unknown' + ; + """).rowcount + if analyzer_name_affected: + LOG.info("Normalising 'reports'... %d unknown 'analyzer_name'.", + analyzer_name_affected) + + checker_id_affected = conn.execute(f""" + UPDATE reports + SET checker_id = '{UnknownChecker[1]}' + WHERE checker_id IS NULL + OR checker_id = '' + OR LOWER(checker_id) = 'not found' + OR LOWER(checker_id) = 'unknown' + ; + """).rowcount + if checker_id_affected: + LOG.info("Normalising 'reports'... %d unknown 'checker_id'.", + checker_id_affected) + + if dialect == "postgresql": + # The changes of the above queries must be committed prematurely, + # as later schema changes (such as renaming and dropping the + # columns normalised now) would otherwise fail with an error: + # + # > sqlalchemy.exc.OperationalError: (psycopg2.errors.ObjectInUse) + # > cannot ALTER TABLE "reports" because it has pending + # > trigger events. + # > [SQL: ALTER TABLE reports ADD COLUMN checker_id INTEGER + # > DEFAULT '0' NOT NULL] + # > [SQL: ALTER TABLE reports DROP COLUMN checker_name] + # + # There is no good sequencing of these changes that would resolve + # this while keeping the entire migration in one transaction. + # However, these changes are not destructive because no data is + # lost, only the representation slightly changed. + conn.execute("COMMIT;") + conn.execute("START TRANSACTION;") + + LOG.info("Done normalising 'reports'.") + + def create_new_tables(): + op.create_table( + "checkers", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("analyzer_name", sa.String(), nullable=True), + sa.Column("checker_name", sa.String(), nullable=True), + sa.Column("severity", sa.Integer()), + sa.PrimaryKeyConstraint("id", name=op.f("pk_checkers")), + sa.UniqueConstraint("analyzer_name", "checker_name", + name=op.f("uq_checkers_analyzer_name")) + ) + op.create_index( + op.f("ix_checkers_severity"), + "checkers", + ["severity"], + unique=False + ) + + fk_analysisinfo_checkers_name = \ + "fk_analysis_info_checkers_analysis_info_id_analysis_info" + op.create_table( + "analysis_info_checkers", + sa.Column("analysis_info_id", sa.Integer(), nullable=False), + sa.Column("checker_id", sa.Integer(), nullable=False), + sa.Column("enabled", sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint( + ["analysis_info_id"], ["analysis_info.id"], + name=op.f(fk_analysisinfo_checkers_name), + ondelete="CASCADE", initially="DEFERRED", + deferrable=True), + sa.ForeignKeyConstraint( + ["checker_id"], ["checkers.id"], + name=op.f("fk_analysis_info_checkers_checker_id_checkers"), + ondelete="RESTRICT", initially="DEFERRED", deferrable=True), + sa.PrimaryKeyConstraint("analysis_info_id", "checker_id", + name=op.f("pk_analysis_info_checkers")) + ) + + def add_new_checker_id_column(): + # Upgrade the 'reports' table to use the 'checkers' foreign look-up + # instead of containing the strings allocated locally with the record. + col_reports_checker_id = sa.Column("checker_id", sa.Integer(), + nullable=False, server_default="1") + + if dialect == "sqlite": + with op.batch_alter_table("reports", recreate="never") as ba: + ba.alter_column("checker_id", new_column_name="checker_name") + # Due to the rename reusing the previous name, these can't merge. + with op.batch_alter_table("reports", recreate="never") as ba: + ba.add_column(col_reports_checker_id) + else: + op.alter_column("reports", "checker_id", + new_column_name="checker_name") + op.add_column("reports", col_reports_checker_id) + + def add_checkers(): + # Pre-allocate IDs in the look-up table for all checkers that were + # encountered according to the currently present reports in the DB. + Base = automap_base() + Base.prepare(conn, reflect=True) + Report = Base.classes.reports # 'reports' is the table name! + Checker = Base.classes.checkers + + db = Session(bind=conn) + db.add(Checker(analyzer_name=FakeChecker[0], + checker_name=FakeChecker[1], + severity=0)) + db.add(Checker(analyzer_name=UnknownChecker[0], + checker_name=UnknownChecker[1], + severity=0)) + + count = db.query(Report.id).count() + if not count: + return + + # It is possible that the same (analyzer_name, checker_name) query, + # when GROUPed BY, will still have multiple reports with distinct + # severities. Adding all entries here would result in the UNIQUE + # constraint violation. Luckily, severities are numeric, and thus can + # be sorted, and saying that for each checkers the largest severity + # should be the new severity is a reasonable assumption. + LOG.info("Preparing to fill 'checkers' from %d 'reports'...", count) + checker_count = 0 + for chk in db.query(Report.analyzer_name, + Report.checker_name, + func.max(Report.severity).label("severity")) \ + .filter(not_(and_( + Report.analyzer_name == UnknownChecker[0], + Report.checker_name == UnknownChecker[1]))) \ + .group_by(Report.analyzer_name, + Report.checker_name) \ + .all(): + db.add(Checker(analyzer_name=chk.analyzer_name, + checker_name=chk.checker_name, + severity=chk.severity)) + checker_count += 1 + + db.commit() + LOG.info("Done filling 'checkers', %d unique entries.", checker_count) + + def upgrade_reports(): + db = Session(bind=conn) + report_count = db.execute("SELECT COUNT(id) FROM reports;").scalar() + if not report_count: + return + + LOG.info("Upgrading %d 'reports' to refer 'checkers'," + " in batches of %d...", + report_count, REPORT_UPDATE_CHUNK_SIZE) + num_batches = report_count // REPORT_UPDATE_CHUNK_SIZE + 1 + + def _print_progress(batch: int): + LOG.info("[%d/%d] Upgrading 'reports'... (%d–%d) %.0f%% done.", + batch, num_batches, + (REPORT_UPDATE_CHUNK_SIZE * i) + 1, + (REPORT_UPDATE_CHUNK_SIZE * (i + 1)) + if batch < num_batches else report_count, + float(batch) / num_batches * 100) + + for i in range(0, num_batches): + # FIXME: "UPDATE ... SET ... FROM ..." is only supported starting + # with SQLite version 3.33.0 (2020-08-14). Until this version + # reaches LTS maturity (Ubuntu 20.04 LTS comes with 3.31.0, raising + # a syntax error on the "FROM" in the "UPDATE" query), this + # branching here needs to stay. + if dialect == "sqlite": + db.execute(f""" + UPDATE reports + SET + checker_id = ( + SELECT checkers.id + FROM checkers + WHERE checkers.analyzer_name = reports.analyzer_name + AND checkers.checker_name = reports.checker_name + ) + WHERE reports.id IN ( + SELECT reports.id + FROM reports + WHERE reports.checker_id = 0 + LIMIT {REPORT_UPDATE_CHUNK_SIZE} + ) + ; + """) + else: + db.execute(f""" + UPDATE reports + SET + checker_id = checkers.id + FROM checkers + WHERE checkers.analyzer_name = reports.analyzer_name + AND checkers.checker_name = reports.checker_name + AND reports.id IN ( + SELECT reports.id + FROM reports + WHERE reports.checker_id = 0 + LIMIT {REPORT_UPDATE_CHUNK_SIZE} + ) + ; + """) + _print_progress(i + 1) + + db.commit() + LOG.info("Done upgrading 'reports'.") + + def drop_reports_table_columns(): + if dialect == "sqlite": + op.execute("PRAGMA foreign_keys=OFF") + + with op.batch_alter_table("reports", recreate="never") as ba: + # FIXME: Allowing recreate="auto" (the default) here would + # result in SQLAlchemy creating a new 'reports' table, which + # will break the database as the incoming constraints + # FOREIGN KEYing against 'reports' will essentially TRUNCATE + # due to the ON DELETE CASCADE markers, e.g., in + # 'bug_path_events'. This manifests as the Python-based + # database cleanup routine essentially wiping all reports off + # the database, following this migration. + # + # Until SQLite 3.35.0 is widely available (Ubuntu 22.04, see + # also dabc6998b8f0_analysis_info_table.py), we side-step the + # current problem with a simple rename. However, in general, + # this is always bound to get bad, and a real solution is only + # expectable from a **significant**, ground-up redesign of both + # the database and the migration logic, and our processes. + # (E.g., if we always assumed migrations can trash the database + # because there are always back-ups, the whole problem with + # "SQLite + transaction + pragma = no-op" would not be here.) + # + # It generally appears that the big issue here is the fact that + # migration scripts execute as part of a TRANSACTION! As such, + # the "PRAGMA foreign_keys=OFF" is useless. See the docs at + # http://sqlite.org/foreignkeys.html (quoted Jan 16, 2024) + # + # > It is not possible to enable or disable foreign key + # > constraints in the middle of a multi-statement transaction + # > (when SQLite is not in autocommit mode). Attempting to do + # > so does not return an error; it simply has no effect. + # + # This means that the foreign keys are still enabled, and when + # the migration commits, 'bug_path_events' and + # 'bug_report_points' **WILL** TRUNCATE themselves as the batch + # operation executed a "DROP TABLE reports;". These claims can + # be re-verified later by executing the following steps: + # 1. Change the database manually and set the incoming + # foreign keys to "ON DELETE RESTRICT" instead of + # "CASCADE". + # 2. Run the migration. An exception will come from this + # context but will refer to an internal "DROP TABLE" stmt. + # 3. Add an op.execute("COMMIT") before the PRAGMA call + # above. + # 4. Re-run the migration and observe everything is good and + # there was no data loss whatsoever! + + # ba.drop_column("checker_name") + ba.alter_column( + "checker_name", + new_column_name="checker_name_MOVED_TO_checkers") + + # These columns are deleted as this data is now available + # through the 'checkers' lookup-table. + # ba.drop_column("analyzer_name") + # ba.drop_column("severity") + ba.alter_column( + "analyzer_name", + new_column_name="analyzer_name_MOVED_TO_checkers") + ba.alter_column( + "severity", + new_column_name="severity_MOVED_TO_checkers") + + # These columns are dropped because they rarely contained any + # meaningful data with new informational value, and their + # contents were never actually exposed on the API. + # ba.drop_column("checker_cat") + # ba.drop_column("bug_type") + ba.alter_column("checker_cat", + new_column_name="checker_cat_UNUSED") + ba.alter_column("bug_type", + new_column_name="bug_type_UNUSED") + + op.execute("PRAGMA foreign_keys=ON") + else: + op.drop_column("reports", "checker_name") + op.drop_column("reports", "analyzer_name") + op.drop_column("reports", "severity") + op.drop_column("reports", "checker_cat") + op.drop_column("reports", "bug_type") + + def upgrade_reports_table_constraints(): + ix_reports_checker_id = { + "index_name": op.f("ix_reports_checker_id"), + "columns": ["checker_id"], + "unique": False + } + fk_reports_checker_id = { + "constraint_name": op.f("fk_reports_checker_id_checkers"), + "referent_table": "checkers", + "local_cols": ["checker_id"], + "remote_cols": ["id"], + "deferrable": False, + "ondelete": "RESTRICT" + } + if dialect == "sqlite": + op.execute("PRAGMA foreign_keys=OFF") + + with op.batch_alter_table("reports", recreate="never") as ba: + # Now that the values are filled, ensure that the constriants + # are appropriately enforced. + ba.create_index(**ix_reports_checker_id) + # This should really be a FOREIGN KEY, but it is not possible + # without recreating the entire 'reports' table, which breaks + # other FOREIGN KEYs. + # ba.create_foreign_key(**fk_reports_checker_id) + + op.execute("PRAGMA foreign_keys=ON") + else: + op.create_index(table_name="reports", **ix_reports_checker_id) + op.create_foreign_key(source_table="reports", + **fk_reports_checker_id) + op.alter_column("reports", "checker_id", nullable=False, + server_default=None) + + normalise_report_analyzer_and_checker_names() + create_new_tables() + add_new_checker_id_column() + add_checkers() + upgrade_reports() + drop_reports_table_columns() + upgrade_reports_table_constraints() + + +def downgrade(): + LOG = getLogger("migration") + dialect = op.get_context().dialect.name + conn = op.get_bind() + + def restore_report_columns(): + col_reports_analyzer_name = sa.Column("analyzer_name", + sa.String(), nullable=False, + server_default="unknown") + col_reports_checker_id = sa.Column("checker_id", sa.String()) + col_reports_checker_cat = sa.Column("checker_cat", sa.String()) + col_reports_bug_type = sa.Column("bug_type", sa.String()) + col_reports_severity = sa.Column("severity", sa.Integer()) + + if dialect == "sqlite": + op.execute("PRAGMA foreign_keys=OFF") + + with op.batch_alter_table("reports", recreate="never") as ba: + # Recreation of FOREIGN KEYs is not possible without recreating + # the entire 'reports' table. + # ba.drop_constraint(op.f("fk_reports_checker_id_checkers")) + ba.drop_index(op.f("ix_reports_checker_id")) + ba.alter_column("checker_id", + new_column_name="checker_id_lookup") + + with op.batch_alter_table("reports", recreate="never") as ba: + # Restore the columns that were deleted in this revision. + # ba.add_column(col_reports_analyzer_name) + # ba.add_column(col_reports_checker_id) + # ba.add_column(col_reports_checker_cat) + # ba.add_column(col_reports_bug_type) + # ba.add_column(col_reports_severity) + + # FIXME: Until SQLite 3.35 is available and we can actually + # delete columns without a table recreation, we can instead + # just restore the existing partial data! + ba.alter_column("analyzer_name_MOVED_TO_checkers", + new_column_name="analyzer_name") + ba.alter_column("checker_name_MOVED_TO_checkers", + new_column_name="checker_id") + ba.alter_column("severity_moved_to_checkers", + new_column_name="severity") + ba.alter_column("checker_cat_UNUSED", + new_column_name="checker_cat") + ba.alter_column("bug_type_UNUSED", + new_column_name="bug_type") + + op.execute("PRAGMA foreign_keys=ON") + else: + op.drop_constraint(op.f("fk_reports_checker_id_checkers"), + "reports") + op.drop_index(op.f("ix_reports_checker_id"), "reports") + op.alter_column("reports", "checker_id", + new_column_name="checker_id_lookup") + + op.add_column("reports", col_reports_analyzer_name) + op.add_column("reports", col_reports_checker_id) + op.add_column("reports", col_reports_checker_cat) + op.add_column("reports", col_reports_bug_type) + op.add_column("reports", col_reports_severity) + + LOG.info("Restored type of columns 'reports.bug_type', " + "'reports.checker_cat'. However, their contents can NOT " + "be restored to the original values, as those were " + "irrevocably lost during a previous schema upgrade. " + "Note, that these columns NEVER contained any actual " + "value that was accessible by users of the API, so " + "this is a technical note.") + + def downgrade_reports(): + db = Session(bind=conn) + report_count = db.execute("SELECT COUNT(id) FROM reports;").scalar() + if not report_count: + return + + LOG.info("Downgrading %d 'reports' from 'checkers'," + " in batches of %d...", + report_count, REPORT_UPDATE_CHUNK_SIZE) + num_batches = report_count // REPORT_UPDATE_CHUNK_SIZE + 1 + + def _print_progress(batch: int): + LOG.info("[%d/%d] Downgrading 'reports'... (%d–%d) %.0f%% done.", + batch, num_batches, + (REPORT_UPDATE_CHUNK_SIZE * i) + 1, + (REPORT_UPDATE_CHUNK_SIZE * (i + 1)) + if batch < num_batches else report_count, + float(batch) / num_batches * 100) + + for i in range(0, num_batches): + # FIXME: "UPDATE ... SET ... FROM ..." is only supported starting + # with SQLite version 3.33.0 (2020-08-14). Until this version + # reaches LTS maturity (Ubuntu 20.04 LTS comes with 3.31.0, raising + # a syntax error on the "FROM" in the "UPDATE" query), this + # branching here needs to stay. + if dialect == "sqlite": + db.execute(f""" + UPDATE reports + SET + (analyzer_name, checker_id, severity, checker_id_lookup) = + (SELECT analyzer_name, checker_name, severity, '0' + FROM checkers + WHERE checkers.id = reports.checker_id_lookup) + WHERE reports.id IN ( + SELECT reports.id + FROM reports + WHERE reports.checker_id_lookup != 0 + LIMIT {REPORT_UPDATE_CHUNK_SIZE} + ) + ; + """) + else: + db.execute(f""" + UPDATE reports + SET + analyzer_name = checkers.analyzer_name, + checker_id = checkers.checker_name, + severity = checkers.severity, + checker_id_lookup = 0 + FROM checkers + WHERE checkers.id = reports.checker_id_lookup + AND reports.id IN ( + SELECT reports.id + FROM reports + WHERE reports.checker_id_lookup != 0 + LIMIT {REPORT_UPDATE_CHUNK_SIZE} + ) + ; + """) + _print_progress(i + 1) + + db.commit() + LOG.info("Done downgrading 'reports'.") + + def drop_checker_id_numeric_column(): + if dialect == "sqlite": + with op.batch_alter_table("reports", recreate="never") as ba: + # FIXME: SQLite >= 3.35 will allow DROP COLUMN... + # ba.drop_column("checker_id_lookup") + ba.alter_column("checker_id_lookup", + new_column_name="checker_id_lookup_UNUSED") + else: + op.drop_column("reports", "checker_id_lookup") + + def drop_new_tables(): + # Drop all tables and columns that were created in this revision. + # This data is not needed anymore. + op.drop_index(op.f("ix_checkers_severity"), "checkers") + op.drop_table("analysis_info_checkers") + op.drop_table("checkers") + + restore_report_columns() + downgrade_reports() + drop_checker_id_numeric_column() + drop_new_tables() diff --git a/web/server/codechecker_server/migrations/report/versions/dabc6998b8f0_analysis_info_table.py b/web/server/codechecker_server/migrations/report/versions/dabc6998b8f0_analysis_info_table.py index a275d73c59..80ad54ef22 100644 --- a/web/server/codechecker_server/migrations/report/versions/dabc6998b8f0_analysis_info_table.py +++ b/web/server/codechecker_server/migrations/report/versions/dabc6998b8f0_analysis_info_table.py @@ -12,11 +12,14 @@ branch_labels = None depends_on = None +from logging import getLogger + from alembic import op import sqlalchemy as sa def upgrade(): + LOG = getLogger("migration") conn = op.get_bind() ctx = op.get_context() dialect = ctx.dialect.name @@ -88,12 +91,13 @@ def upgrade(): op.bulk_insert( run_history_analysis_info_tbl, run_history_analysis_info) except: - print("Analyzer command data migration failed!") + LOG.error("Analyzer command data migration failed!") else: # If data migration was successfully finished we can remove the # columns. if dialect == 'sqlite': - # Unfortunately removing columns in SQLite is not supported. + # Unfortunately removing columns in SQLite is not supported until + # version 3.35.0 (only Ubuntu 22.04's default version). Using the # 'batch_alter_table' function can be used to remove a column here (it # will create a new database) but it will clear the table which have # foreign keys with cascade delete property. Unfortunately disabling diff --git a/web/server/codechecker_server/server.py b/web/server/codechecker_server/server.py index 7dd218c683..618c0cf8a7 100644 --- a/web/server/codechecker_server/server.py +++ b/web/server/codechecker_server/server.py @@ -669,10 +669,10 @@ def cleanup_run_db(self): LOG.info("Garbage collection for product '%s' started...", self.endpoint) - db_cleanup.remove_expired_run_locks(self.session_factory) + db_cleanup.remove_expired_data(self.session_factory) db_cleanup.remove_unused_data(self.session_factory) - db_cleanup.upgrade_severity_levels(self.session_factory, - self.__context.checker_labels) + db_cleanup.update_contextual_data(self.session_factory, + self.__context) LOG.info("Garbage collection finished.") return True diff --git a/web/server/tests/unit/metadata_test_files/v1.5.json b/web/server/tests/unit/metadata_test_files/v1.5.json new file mode 100644 index 0000000000..c54930953d --- /dev/null +++ b/web/server/tests/unit/metadata_test_files/v1.5.json @@ -0,0 +1,50 @@ +{ + "action_num": 1, + "analyzer_statistics": { + "clang-tidy": { + "failed": 0, + "failed_sources": [], + "successful": 10, + "version": "LLVM version 7.0.0" + }, + "clangsa": { + "failed": 0, + "failed_sources": [], + "successful": 1, + "version": "clang version 7.0.0" + } + }, + "checkers": { + "clang-tidy": { + "abseil-string-find-startswith": false, + "bugprone-use-after-move": true + }, + "clangsa": { + "alpha.clone.CloneChecker": false, + "deadcode.DeadStores": true + } + }, + "command": [ + "CodeChecker.py", + "analyze", + "-o", + "/path/to/reports", + "/path/to/build.json" + ], + "output_path": "/path/to/reports", + "result_source_files": { + "/path/to/reports/main.cpp_cd2085addd2b226005b7f9cf1827c082.plist": "/path/to/main.cpp", + "/path/to/reports/reports/main.cpp_ed1ce6c18431138a19465e60aa69a4ba.plist": "/path/to/main.cpp" + }, + "skipped": 0, + "timestamps": { + "begin": 1571297867, + "end": 1571297868 + }, + "versions": { + "clang": "clang version 7.0.0", + "clang-tidy": "LLVM version 7.0.0", + "codechecker": "6.11 (930440d6a6cae80f615146547f4b169c7629d558)" + }, + "working_directory": "/path/to/workspace" +} diff --git a/web/server/tests/unit/metadata_test_files/v1.json b/web/server/tests/unit/metadata_test_files/v1.json index c54930953d..44cc941c6f 100644 --- a/web/server/tests/unit/metadata_test_files/v1.json +++ b/web/server/tests/unit/metadata_test_files/v1.json @@ -15,14 +15,12 @@ } }, "checkers": { - "clang-tidy": { - "abseil-string-find-startswith": false, - "bugprone-use-after-move": true - }, - "clangsa": { - "alpha.clone.CloneChecker": false, - "deadcode.DeadStores": true - } + "clang-tidy": [ + "bugprone-use-after-move" + ], + "clangsa": [ + "deadcode.DeadStores" + ] }, "command": [ "CodeChecker.py", diff --git a/web/server/tests/unit/test_metadata_merge.py b/web/server/tests/unit/test_metadata_merge.py index 579b78dcd3..62e723a8f2 100644 --- a/web/server/tests/unit/test_metadata_merge.py +++ b/web/server/tests/unit/test_metadata_merge.py @@ -136,6 +136,7 @@ def test_merge_metadata(self): }] } + # FIXME: What even is this?! The name is 'v3' but it says "version: 2". metadata_v3 = { "version": 2, 'num_of_report_dir': 1, diff --git a/web/server/tests/unit/test_metadata_parser.py b/web/server/tests/unit/test_metadata_parser.py index 4533ec2ad2..70d400611c 100644 --- a/web/server/tests/unit/test_metadata_parser.py +++ b/web/server/tests/unit/test_metadata_parser.py @@ -43,6 +43,28 @@ 'bugprone-use-after-move': True, 'abseil-string-find-startswith': False } + }, + "checkers_enabled_only": { + "clangsa": { + "deadcode.DeadStores": True + }, + "clang-tidy": { + "bugprone-use-after-move": True, + } + }, + "analyzers": [ + "clang-tidy", + "clangsa" + ], + "analyzers_to_checkers": { + "clangsa": [ + "alpha.clone.CloneChecker", + "deadcode.DeadStores" + ], + "clang-tidy": [ + "abseil-string-find-startswith", + "bugprone-use-after-move" + ] } } @@ -73,6 +95,10 @@ "version": "Cppcheck 1.87" } }, + "analyzers": [ + "clang-tidy", + "clangsa" + ], 'checkers': { 'clangsa': { 'alpha.clone.CloneChecker': False, @@ -135,9 +161,30 @@ def test_metadata_info_v1(self): self.assertDictEqual(metadata_cc_info['analyzer_statistics'], mip.analyzer_statistics) - self.assertDictEqual(metadata_cc_info['checkers'], + self.assertListEqual(metadata_cc_info["analyzers"], + mip.analyzers) + + self.assertDictEqual(metadata_cc_info["checkers_enabled_only"], mip.checkers) + def test_metadata_info_v1_point_5(self): + """ + Get metadata info for old version format json file, but the kind where + checker "enabled status" was already represented, but before it + officially became the version 2 format. + """ + metadata_v1_5 = os.path.join(self.__metadata_test_files, "v1.5.json") + mip = MetadataInfoParser(metadata_v1_5) + + self.assertDictEqual(metadata_cc_info["checkers"], + mip.checkers) + self.assertListEqual(metadata_cc_info + ["analyzers_to_checkers"]["clangsa"], + list(mip.checkers["clangsa"].keys())) + self.assertListEqual(metadata_cc_info + ["analyzers_to_checkers"]["clang-tidy"], + list(mip.checkers["clang-tidy"].keys())) + def test_metadata_info_v2(self): """ Get metadata info for new version format json. """ metadata_v2 = os.path.join(self.__metadata_test_files, 'v2.json') @@ -155,8 +202,17 @@ def test_metadata_info_v2(self): self.assertDictEqual(metadata_cc_info['analyzer_statistics'], mip.analyzer_statistics) - self.assertDictEqual(metadata_cc_info['checkers'], + self.assertListEqual(metadata_cc_info["analyzers"], + mip.analyzers) + + self.assertDictEqual(metadata_cc_info["checkers"], mip.checkers) + self.assertListEqual(metadata_cc_info + ["analyzers_to_checkers"]["clangsa"], + list(mip.checkers["clangsa"].keys())) + self.assertListEqual(metadata_cc_info + ["analyzers_to_checkers"]["clang-tidy"], + list(mip.checkers["clang-tidy"].keys())) def test_multiple_metadata_info(self): """ Get metadata info from multiple analyzers. """ diff --git a/web/server/vue-cli/package-lock.json b/web/server/vue-cli/package-lock.json index fbc6ce3540..273e13895d 100644 --- a/web/server/vue-cli/package-lock.json +++ b/web/server/vue-cli/package-lock.json @@ -11,7 +11,7 @@ "@mdi/font": "^6.5.95", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz", "codemirror": "^5.65.0", "date-fns": "^2.28.0", "js-cookie": "^3.0.1", @@ -4875,9 +4875,9 @@ } }, "node_modules/codechecker-api": { - "version": "6.54.0", - "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz", - "integrity": "sha512-1+5Q0B4ehDO7s9bHe0iIqprajdQqVl9iW4TPjyMq9ITlsN5/XQLhzyCwmvhlADpNLKVQVY7oAuvQ67JVMw62GQ==", + "version": "6.55.0", + "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz", + "integrity": "sha512-gCh2H/q68D3zGgpYAgNX/2h+7puzPtPVMgQq+af/F/Yq7eQGBjIQqSWwL+/hhsawwdjIHE9bbEgUuCXzDi4l/Q==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "thrift": "0.13.0-hotfix.1" @@ -19832,8 +19832,8 @@ "dev": true }, "codechecker-api": { - "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz", - "integrity": "sha512-1+5Q0B4ehDO7s9bHe0iIqprajdQqVl9iW4TPjyMq9ITlsN5/XQLhzyCwmvhlADpNLKVQVY7oAuvQ67JVMw62GQ==", + "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz", + "integrity": "sha512-gCh2H/q68D3zGgpYAgNX/2h+7puzPtPVMgQq+af/F/Yq7eQGBjIQqSWwL+/hhsawwdjIHE9bbEgUuCXzDi4l/Q==", "requires": { "thrift": "0.13.0-hotfix.1" } diff --git a/web/server/vue-cli/package.json b/web/server/vue-cli/package.json index ca09e78b09..b0043acf96 100644 --- a/web/server/vue-cli/package.json +++ b/web/server/vue-cli/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@mdi/font": "^6.5.95", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.54.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.55.0.tgz", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", "codemirror": "^5.65.0", diff --git a/web/server/vue-cli/src/components/AnalysisInfoDialog.vue b/web/server/vue-cli/src/components/AnalysisInfoDialog.vue index cfeed9ff34..cfba1c26be 100644 --- a/web/server/vue-cli/src/components/AnalysisInfoDialog.vue +++ b/web/server/vue-cli/src/components/AnalysisInfoDialog.vue @@ -126,6 +126,12 @@ export default { offset, handleThriftError(analysisInfo => { this.analysisInfo = analysisInfo.map(cmd => this.highlightOptions(cmd)); + + // FIXME: Do this more ergonomically. This is only good enough for + // testing the backend's stability for now... + console.warn("The available checkers might have been received, " + + "but showing them is not supported yet!"); + console.warn(analysisInfo.map(ai => ai.checkers)); })); } } diff --git a/web/tests/functional/blame/test_blame_info.py b/web/tests/functional/blame/test_blame_info.py index a8482d185a..df9b8c48dd 100644 --- a/web/tests/functional/blame/test_blame_info.py +++ b/web/tests/functional/blame/test_blame_info.py @@ -187,7 +187,12 @@ def test_update_blame_info(self): self.assertFalse(blame_info.commits) self.assertFalse(blame_info.blame) - subprocess.Popen(['git', 'init', proj_dir]).communicate() + # Create a .git structure that is as bare as possible, without + # getting interference from the user's configuration. + subprocess.Popen(['git', 'init', proj_dir, + "--template", "/usr/share/git-core/templates" + ]).communicate() + subprocess.Popen([ 'git', 'remote', @@ -200,7 +205,8 @@ def test_update_blame_info(self): '-c', 'user.name=hello', '-c', 'user.email=world', 'commit', - '-m', 'message']).communicate() + '--no-verify', + '--message', 'message']).communicate() codechecker.store(self._codechecker_cfg, run_name) diff --git a/web/tests/functional/cppcheck/test_cppcheck.py b/web/tests/functional/cppcheck/test_cppcheck.py index 20c4040c6a..8bfafc36d2 100644 --- a/web/tests/functional/cppcheck/test_cppcheck.py +++ b/web/tests/functional/cppcheck/test_cppcheck.py @@ -122,6 +122,10 @@ def test_cppcheck_report_storage(self): print(out) reports = json.loads(out) self.assertEqual(len(reports), 5) - # The stored hash should not be "0". for report in reports: - self.assertNotEqual(report['bugHash'], "0") + # The stored hash should not be "0". + self.assertNotEqual(report["bugHash"], "0") + # The stored checker name should not be the fake(d) default that + # was created because no 'metadata.json' (and thus no checker + # list) exists for this "project". + self.assertNotEqual(report["checkerId"], "__FAKE__") diff --git a/web/tests/functional/report_viewer_api/test_get_run_results.py b/web/tests/functional/report_viewer_api/test_get_run_results.py index 79b5f7feda..575d3a3892 100644 --- a/web/tests/functional/report_viewer_api/test_get_run_results.py +++ b/web/tests/functional/report_viewer_api/test_get_run_results.py @@ -432,18 +432,23 @@ def test_get_checker_labels(self): ]) checker_labels = self._cc_client.getCheckerLabels([ - Checker('clangsa', 'core.DivideZero')]) + Checker("clangsa", "core.DivideZero")]) self.assertEqual(len(checker_labels), 1) self.assertEqual(set(checker_labels[0]), div_zero_labels) checker_labels = self._cc_client.getCheckerLabels([ - Checker('unknown', 'core.DivideZero')]) + Checker("unknown", "core.DivideZero")]) self.assertEqual(len(checker_labels), 1) self.assertEqual(set(checker_labels[0]), div_zero_labels) checker_labels = self._cc_client.getCheckerLabels([ - Checker('clangsa', 'core.DivideZero'), - Checker('clang-tidy', 'dummy-checker')]) + Checker("UNKNOWN", "core.DivideZero")]) + self.assertEqual(len(checker_labels), 1) + self.assertEqual(set(checker_labels[0]), div_zero_labels) + + checker_labels = self._cc_client.getCheckerLabels([ + Checker("clangsa", "core.DivideZero"), + Checker("clang-tidy", "dummy-checker")]) self.assertEqual(len(checker_labels), 2) self.assertEqual(set(checker_labels[0]), div_zero_labels) self.assertEqual(set(checker_labels[1]), set()) diff --git a/web/tests/functional/report_viewer_api/test_run_data.py b/web/tests/functional/report_viewer_api/test_run_data.py index b258e021f8..18671124eb 100644 --- a/web/tests/functional/report_viewer_api/test_run_data.py +++ b/web/tests/functional/report_viewer_api/test_run_data.py @@ -17,8 +17,11 @@ from libtest import env -from codechecker_api.codeCheckerDBAccess_v6.ttypes import DetectionStatus, \ - Order, ReportFilter, RunFilter, RunSortMode, RunSortType +from codechecker_api.codeCheckerDBAccess_v6.ttypes import \ + AnalysisInfoFilter, \ + DetectionStatus, \ + Order, \ + ReportFilter, RunFilter, RunSortMode, RunSortType from . import setup_class_common, teardown_class_common @@ -171,3 +174,65 @@ def test_sort_run_data(self): # contains special characters. sort_mode = RunSortMode(RunSortType.NAME, Order.ASC) self._cc_client.getRunData(None, None, 0, sort_mode) + + def test_analysis_info(self): + """ + Test that storing runs to the server records the executed analyzer + command and the list of checkers present and executed. + """ + workspace = os.environ["TEST_WORKSPACE"] + runs = self.__get_runs("test_files*%") + self.assertEqual(len(runs), 1, + "There should be one run for this test.") + run = runs[0] + run_id = run.runId + + analysis_infos = self._cc_client.getAnalysisInfo( + AnalysisInfoFilter(run_id, None, None), 1, 0) + self.assertEqual(len(analysis_infos), 1, + "An analysis_info must be recorded for the run!") + + info = analysis_infos[0] + cmd = info.analyzerCommand + print(run_id, analysis_infos) + + self.assertTrue(workspace in cmd, + "The name of the test workspace should be part of " + "the report directory, found in the cmdline.") + # Ensure that the tests here are up-to-date with what's in __init__.py. + self.assertTrue("-d core.StackAddressEscape" in cmd, + "A disabled checker is needed for this test to work!") + self.assertTrue("-d unix.Malloc" in cmd, + "A disabled checker is needed for this test to work!") + self.assertTrue("-d clang-diagnostic" in cmd, + "A disabled checker is needed for this test to work!") + self.assertTrue("-e clang-diagnostic-division-by-zero" in cmd, + "An enabled checker is needed for this test to work!") + + checkers = info.checkers + + def assertChecker(analyzer, checker): + self.assertTrue(checkers[analyzer][checker].enabled) + + def assertNotChecker(analyzer, checker): + self.assertFalse(checkers[analyzer][checker].enabled) + + assertNotChecker("clangsa", "core.StackAddressEscape") + assertChecker("clangsa", "core.CallAndMessage") + assertChecker("clangsa", "cplusplus.NewDelete") + assertChecker("clangsa", "deadcode.DeadStores") + assertNotChecker("clangsa", "osx.cocoa.Loops") + assertNotChecker("clangsa", "unix.Malloc") + + assertNotChecker("clang-tidy", "bugprone-easily-swappable-parameters") + assertChecker("clang-tidy", "clang-diagnostic-division-by-zero") + assertNotChecker("clang-tidy", "clang-diagnostic-return-type") + assertNotChecker("clang-tidy", "clang-diagnostic-vla") + assertNotChecker("clang-tidy", "llvmlibc-restrict-system-libc-headers") + assertChecker("clang-tidy", "misc-definitions-in-headers") + assertNotChecker("clang-tidy", "objc-super-self") + + self.assertTrue("cppcheck" not in checkers.keys(), + "This analysis was run without CppCheck!") + self.assertTrue("gcc" not in checkers.keys(), + "This analysis was run without GCC!")