blob: 9a414114600309bfe5c93ec639230c9bbaf27ece [file] [edit]
/* 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));
}
}