| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmCMakeSarifLogger.h" | |
| #include <cstddef> | |
| #include <limits> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/optional> | |
| #include <cm/string_view> | |
| #include "cmsys/String.h" | |
| #include "cmDiagnostics.h" | |
| #include "cmListFileCache.h" | |
| #include "cmMessageType.h" | |
| #include "cmMessenger.h" | |
| #include "cmSarif.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTimestamp.h" | |
| #include "cmVersionConfig.h" | |
| // CMake-specific SARIF helpers | |
| namespace { | |
| /// @brief Express the location of a `cmListFileContext` in SARIF | |
| /// @param[in] uriBaseIds A list of logical base directory names and their path | |
| /// | |
| /// Build a SARIF location object detailing the location data available from a | |
| /// context. More specific information like the region (line number) and | |
| /// function call name will be included if available. | |
| /// | |
| /// SARIF requests that paths are given relative to a logical base for | |
| /// relocatability. Context locations will be made relative to a logical base | |
| /// iff they fall under one of the directories listed in the `uriBaseIds` | |
| /// map. Bases are tried in order. | |
| cmSarif::Location LocationFromContext( | |
| cmListFileContext const& lfc, | |
| std::vector<std::pair<std::string, std::string>> const& uriBaseIds = {}) | |
| { | |
| cmSarif::Location location; | |
| location.Physical.Artifact.Uri = lfc.FilePath; | |
| // SARIF requests that paths are given relative to a logical base for | |
| // relocatability. Check if these files are under any of the bases, if | |
| // provided. | |
| for (auto const& baseUri : uriBaseIds) { | |
| std::string relative = cmSystemTools::RelativeIfUnder( | |
| baseUri.second, location.Physical.Artifact.Uri); | |
| if (relative != location.Physical.Artifact.Uri) { | |
| location.Physical.Artifact.Uri = relative; | |
| location.Physical.Artifact.UriBaseId = baseUri.first; | |
| } | |
| } | |
| if (!lfc.Name.empty()) { | |
| location.Logical.emplace_back( | |
| cmSarif::LogicalLocation{ lfc.Name, cmSarif::LocationKind::Function }); | |
| } | |
| // Add info about the region within the file depending on how specific the | |
| // context is. Watch for deferred call and variable watch placeholders or | |
| // a zero, which indicates the start of processing a list file. | |
| if (lfc.Line == cmListFileContext::DeferPlaceholderLine) { | |
| location.Message = cmSarif::Message{ "DEFERRED" }; | |
| } else if (lfc.Line > 0 && lfc.Line != std::numeric_limits<long>::max()) { | |
| cmSarif::Region region; | |
| region.StartLine = lfc.Line; | |
| location.Physical.ArtifactRegion = region; | |
| } | |
| return location; | |
| } | |
| cm::optional<cmSarif::Location> LastLocation( | |
| cmListFileBacktrace backtrace, | |
| std::vector<std::pair<std::string, std::string>> const& uriBaseIds = {}) | |
| { | |
| if (backtrace.Empty()) { | |
| return {}; | |
| } | |
| return LocationFromContext(backtrace.Top(), uriBaseIds); | |
| } | |
| cm::optional<cmSarif::Stack> StackFromBacktrace( | |
| cmListFileBacktrace bt, | |
| std::vector<std::pair<std::string, std::string>> const& uriBaseIds = {}) | |
| { | |
| if (bt.Empty()) { | |
| return {}; | |
| } | |
| cmSarif::Stack stack; | |
| for (; !bt.Empty(); bt = bt.Pop()) { | |
| cmSarif::Location topLocation = LocationFromContext(bt.Top(), uriBaseIds); | |
| // If the location doesn't have a specific region, this entry is a | |
| // placeholder and should not appear in the call stack. | |
| if (!topLocation.Message && !topLocation.Physical.ArtifactRegion) { | |
| continue; | |
| } | |
| cmSarif::StackFrame frame; | |
| frame.Location = std::move(topLocation); | |
| stack.Frames.emplace_back(std::move(frame)); | |
| } | |
| return stack; | |
| } | |
| cmSarif::Tool CreateCMakeTool() | |
| { | |
| cmSarif::ToolComponent cmDriver; | |
| cmDriver.Name = "CMake"; | |
| cmDriver.Version = CMake_VERSION; | |
| return cmSarif::Tool{ cmDriver }; | |
| } | |
| std::string RuleIdForMessageType(MessageType type, | |
| cmDiagnosticCategory category) | |
| { | |
| cm::string_view name = cmDiagnostics::GetCategoryString(category); | |
| if (!name.empty()) { | |
| // Strip the "CMD_" prefix from the category name and convert to PascalCase | |
| std::string sarifIdName; | |
| bool nextWord = true; | |
| for (char c : name.substr(4)) { | |
| if (c == '_') { | |
| nextWord = true; | |
| continue; | |
| } | |
| if (nextWord) { | |
| sarifIdName += c; | |
| nextWord = false; | |
| } else { | |
| sarifIdName += cmsysString_tolower(c); | |
| } | |
| } | |
| return cmStrCat("CMake.", sarifIdName); | |
| } | |
| // Fall back to message type if not a diagnostic | |
| switch (type) { | |
| case MessageType::FATAL_ERROR: | |
| return "CMake.FatalError"; | |
| case MessageType::INTERNAL_ERROR: | |
| return "CMake.InternalError"; | |
| case MessageType::WARNING: | |
| return "CMake.Warning"; | |
| default: | |
| return ""; | |
| } | |
| } | |
| cm::string_view NameForMessageType(MessageType type, | |
| cmDiagnosticCategory category) | |
| { | |
| if (category != cmDiagnostics::CMD_NONE) { | |
| return cmDiagnostics::GetCategoryString(category); | |
| } | |
| switch (type) { | |
| case MessageType::FATAL_ERROR: | |
| return "CMake Error"; | |
| case MessageType::INTERNAL_ERROR: | |
| return "CMake Internal Error"; | |
| case MessageType::WARNING: | |
| return "CMake Warning"; | |
| default: | |
| return ""; | |
| } | |
| } | |
| cmSarif::ReportingDescriptor ReportingDescriptorForMessageType( | |
| MessageType type, cmDiagnosticCategory category) | |
| { | |
| cmSarif::ReportingDescriptor rd; | |
| rd.Id = RuleIdForMessageType(type, category); | |
| rd.Name = NameForMessageType(type, category); | |
| return rd; | |
| }; | |
| cmSarif::ResultSeverityLevel SarifLevelFromMessageType(MessageType type) | |
| { | |
| switch (type) { | |
| case MessageType::FATAL_ERROR: | |
| case MessageType::INTERNAL_ERROR: | |
| return cmSarif::ResultSeverityLevel::Error; | |
| case MessageType::WARNING: | |
| return cmSarif::ResultSeverityLevel::Warning; | |
| default: | |
| return cmSarif::ResultSeverityLevel::Note; | |
| } | |
| } | |
| } // namespace | |
| cmCMakeSarifLogger::~cmCMakeSarifLogger() | |
| { | |
| this->GenerateForRun(); | |
| } | |
| void cmCMakeSarifLogger::SetOutputPath(std::string const& path) | |
| { | |
| this->FilePath = path; | |
| } | |
| void cmCMakeSarifLogger::AddBaseDirectory(cm::string_view name, | |
| cm::string_view path) | |
| { | |
| this->UriBaseIds.emplace_back(std::string(name), std::string(path)); | |
| this->CMakeRun.OriginalUriBaseIds.emplace( | |
| std::string(name), | |
| cmSarif::ArtifactLocation{ cmStrCat("file://", path, '/'), "" }); | |
| } | |
| void cmCMakeSarifLogger::RecordDiagnostics( | |
| std::vector<cmMessenger::Message> const& messages) | |
| { | |
| this->CMakeRun.Tool = CreateCMakeTool(); | |
| // Helper to add rules to the run as encountered in results and get their | |
| // index for reporting | |
| auto use_rule = [&](MessageType type, cmDiagnosticCategory category) { | |
| auto category_name = RuleIdForMessageType(type, category); | |
| auto ruleIt = this->RuleIndices.find(category_name); | |
| if (ruleIt != this->RuleIndices.end()) { | |
| return std::make_pair(category_name, ruleIt->second); | |
| } | |
| this->CMakeRun.Tool.Driver.Rules.emplace_back( | |
| ReportingDescriptorForMessageType(type, category)); | |
| this->RuleIndices.emplace(category_name, | |
| this->CMakeRun.Tool.Driver.Rules.size() - 1); | |
| return std::make_pair(category_name, | |
| this->CMakeRun.Tool.Driver.Rules.size() - 1); | |
| }; | |
| for (auto const& message : messages) { | |
| // SARIF should only emit diagnostic messages, not general messages/logs | |
| switch (message.Type) { | |
| case MessageType::MESSAGE: | |
| case MessageType::LOG: | |
| case MessageType::UNDEFINED: | |
| continue; | |
| default: | |
| break; | |
| } | |
| std::pair<std::string, std::size_t> ruleInfo = | |
| use_rule(message.Type, message.Category); | |
| cmSarif::Result result; | |
| result.RuleId = ruleInfo.first; | |
| result.RuleIndex = ruleInfo.second; | |
| result.Message = cmSarif::Message{ message.Text }; | |
| result.Location = LastLocation(message.Backtrace, this->UriBaseIds); | |
| if (cm::optional<cmSarif::Stack> stack = | |
| StackFromBacktrace(message.Backtrace, this->UriBaseIds)) { | |
| result.Stacks.emplace_back(std::move(*stack)); | |
| } | |
| result.Level = SarifLevelFromMessageType(message.Type); | |
| this->CMakeRun.Results.emplace_back(std::move(result)); | |
| } | |
| } | |
| void cmCMakeSarifLogger::RecordInvocation( | |
| int ac, char const* const* av, int exitCode, | |
| std::chrono::system_clock::time_point startTime, | |
| std::chrono::system_clock::time_point endTime) | |
| { | |
| cmTimestamp timestamp; | |
| cmSarif::Invocation invocation; | |
| invocation.Arguments.assign(av, av + ac); | |
| invocation.ExecutableLocation.Uri = cmSystemTools::GetCMakeCommand(); | |
| invocation.StartTimeUtc = timestamp.CreateTimestampFromTimeT( | |
| std::chrono::system_clock::to_time_t(startTime), "", true); | |
| invocation.EndTimeUtc = timestamp.CreateTimestampFromTimeT( | |
| std::chrono::system_clock::to_time_t(endTime), "", true); | |
| invocation.ExitCode = exitCode; | |
| invocation.ExecutionSuccessful = (exitCode == 0); | |
| this->CMakeRun.Invocations.emplace_back(std::move(invocation)); | |
| } | |
| bool cmCMakeSarifLogger::WriteFile(std::string const& path) const | |
| { | |
| std::string const dir = cmSystemTools::GetFilenamePath(path); | |
| if (!cmSystemTools::FileIsDirectory(dir)) { | |
| return false; | |
| } | |
| return cmSarif::WriteLog(path, this->CMakeRun); | |
| } | |
| void cmCMakeSarifLogger::GenerateForRun() const | |
| { | |
| if (this->FilePath.empty()) { | |
| return; | |
| } | |
| if (!this->WriteFile(this->FilePath)) { | |
| cmSystemTools::Error( | |
| cmStrCat("Failed to write SARIF log to ", this->FilePath)); | |
| } | |
| } |