mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
311 lines
9.3 KiB
C++
311 lines
9.3 KiB
C++
/* 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));
|
|
}
|
|
}
|