mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
cmSarif: Improve conformance with SARIF in CMake result reporting
Reorganize CMake's SARIF implementation to clearly distinguish between the SARIF standard and CMake's result reporting structure, fixing several validation failures. - Removed unused message strings in reported rules. - Report artifact locations relative to the CMake home directory. - Use the recommended schema URL. - Stop reporting some invalid locations read from the top of the stack. cmMessenger no longer manages a SARIF log but stores a list of displayed messages. The SARIF log is constructed from displayed messages upon writing the file.
This commit is contained in:
@@ -138,6 +138,8 @@ add_library(
|
||||
cmCMakePresetsGraphReadJSONTestPresets.cxx
|
||||
cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
|
||||
cmCMakePresetsGraphResolve.cxx
|
||||
cmCMakeSarifLogger.h
|
||||
cmCMakeSarifLogger.cxx
|
||||
cmCMakeString.hxx
|
||||
cmCMakeString.cxx
|
||||
cmCommandLineArgument.h
|
||||
@@ -485,8 +487,8 @@ add_library(
|
||||
cmRST.h
|
||||
cmRuntimeDependencyArchive.cxx
|
||||
cmRuntimeDependencyArchive.h
|
||||
cmSarifLog.cxx
|
||||
cmSarifLog.h
|
||||
cmSarif.cxx
|
||||
cmSarif.h
|
||||
cmScriptGenerator.h
|
||||
cmScriptGenerator.cxx
|
||||
cmSourceFile.cxx
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/* 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/string_view>
|
||||
|
||||
#include "cmsys/FStream.hxx"
|
||||
|
||||
#include "cmListFileCache.h"
|
||||
#include "cmMessageType.h"
|
||||
#include "cmMessenger.h"
|
||||
#include "cmSarif.h"
|
||||
#include "cmState.h"
|
||||
#include "cmStringAlgorithms.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmValue.h"
|
||||
#include "cmVersionConfig.h"
|
||||
#include "cmake.h"
|
||||
|
||||
// CMake-specific SARIF helpers
|
||||
namespace {
|
||||
|
||||
constexpr char const* CMakeSarifOutputFlag = "CMAKE_EXPORT_SARIF";
|
||||
constexpr char const* DefaultSarifFile = ".cmake/sarif/cmake.sarif";
|
||||
|
||||
cm::optional<cmSarif::Location> GetLocationFromBacktrace(
|
||||
cmListFileBacktrace const& backtrace, cmake const& cm)
|
||||
{
|
||||
if (backtrace.Empty()) {
|
||||
return {};
|
||||
}
|
||||
cmListFileContext const& lfc = backtrace.Top();
|
||||
// Exclude frames with no real location: negative lines are deferred-call
|
||||
// placeholders, and LONG_MAX is the synthetic line used by variable_watch
|
||||
// callback dispatch. Neither is a meaningful source location.
|
||||
if (lfc.Line < 0 || lfc.Line == std::numeric_limits<long>::max()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
cmSarif::PhysicalLocation location;
|
||||
location.Artifact.Uri = lfc.FilePath;
|
||||
|
||||
// SARIF requests that paths are given relative to a logical base. Report
|
||||
// paths relative to the source dir / script working directory if possible.
|
||||
location.Artifact.UriBaseId = cm.GetHomeDirectory();
|
||||
std::string relative = cmSystemTools::RelativePath(
|
||||
location.Artifact.UriBaseId, location.Artifact.Uri);
|
||||
if (!relative.empty()) {
|
||||
location.Artifact.Uri = relative;
|
||||
}
|
||||
|
||||
if (lfc.Line != 0) {
|
||||
cmSarif::Region region;
|
||||
region.StartLine = lfc.Line;
|
||||
location.ArtifactRegion = region;
|
||||
}
|
||||
return cmSarif::Location{ location };
|
||||
}
|
||||
|
||||
cmSarif::Tool CreateCMakeTool()
|
||||
{
|
||||
cmSarif::ToolComponent cmDriver;
|
||||
cmDriver.Name = "CMake";
|
||||
cmDriver.Version = CMake_VERSION;
|
||||
|
||||
return cmSarif::Tool{ cmDriver };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
cm::string_view MessageRuleId(MessageType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MessageType::FATAL_ERROR:
|
||||
return "CMake.FatalError";
|
||||
case MessageType::INTERNAL_ERROR:
|
||||
return "CMake.InternalError";
|
||||
case MessageType::WARNING:
|
||||
return "CMake.Warning";
|
||||
case MessageType::MESSAGE:
|
||||
return "CMake.Message";
|
||||
case MessageType::LOG:
|
||||
default:
|
||||
return "CMake.Log";
|
||||
}
|
||||
}
|
||||
|
||||
cm::string_view MessageDisplayName(MessageType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MessageType::FATAL_ERROR:
|
||||
return "CMake Error";
|
||||
case MessageType::INTERNAL_ERROR:
|
||||
return "CMake Internal Error";
|
||||
case MessageType::WARNING:
|
||||
return "CMake Warning";
|
||||
case MessageType::MESSAGE:
|
||||
return "CMake Message";
|
||||
case MessageType::LOG:
|
||||
default:
|
||||
return "CMake Log";
|
||||
}
|
||||
}
|
||||
|
||||
cmSarif::ReportingDescriptor RuleForMessageType(MessageType type)
|
||||
{
|
||||
cmSarif::ReportingDescriptor rd;
|
||||
rd.Id = MessageRuleId(type);
|
||||
rd.Name = MessageDisplayName(type);
|
||||
return rd;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
cmCMakeSarifLogger::cmCMakeSarifLogger(cmake& cm)
|
||||
: CM(cm)
|
||||
{
|
||||
if (this->CM.GetState()->GetRole() == cmState::Role::Project) {
|
||||
cm.MarkCliAsUsed(CMakeSarifOutputFlag);
|
||||
}
|
||||
}
|
||||
|
||||
cmCMakeSarifLogger::~cmCMakeSarifLogger()
|
||||
{
|
||||
this->GenerateForRun();
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmCMakeSarifLogger::FileOutputPath() const
|
||||
{
|
||||
// If a SARIF path was specified via CLI, use it. Otherwise, check whether
|
||||
// logging is enabled via the project cache variable and use the default
|
||||
// path if so.
|
||||
if (cm::optional<std::string> specifiedPath = this->CM.GetSarifFilePath()) {
|
||||
return specifiedPath;
|
||||
}
|
||||
if (this->CM.GetState()->GetRole() == cmState::Role::Project &&
|
||||
this->CM.GetCacheDefinition(CMakeSarifOutputFlag).IsOn()) {
|
||||
return cmStrCat(this->CM.GetHomeOutputDirectory(), '/', DefaultSarifFile);
|
||||
}
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
bool cmCMakeSarifLogger::WriteFile(std::string const& path,
|
||||
bool createParentDirectories) const
|
||||
{
|
||||
if (createParentDirectories) {
|
||||
if (!cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(path))
|
||||
.IsSuccess()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cmsys::ofstream outputFile(path);
|
||||
if (!outputFile.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run object to build
|
||||
cmSarif::Run run;
|
||||
run.Tool = CreateCMakeTool();
|
||||
|
||||
// Helper to add rules to the run as encountered in results and get their
|
||||
// index for reporting
|
||||
std::unordered_map<cm::string_view, std::size_t> ruleIndices;
|
||||
auto use_rule = [&](MessageType t) {
|
||||
cm::string_view category_name = MessageRuleId(t);
|
||||
auto result = ruleIndices.emplace(category_name, 0);
|
||||
if (result.second) {
|
||||
result.first->second = run.Tool.Driver.Rules.size();
|
||||
run.Tool.Driver.Rules.emplace_back(RuleForMessageType(t));
|
||||
}
|
||||
return *result.first;
|
||||
};
|
||||
|
||||
cmMessenger const& messenger = *this->CM.GetMessenger();
|
||||
for (auto const& message : messenger.GetDisplayedMessages()) {
|
||||
std::pair<cm::string_view, std::size_t> ruleInfo = use_rule(message.Type);
|
||||
|
||||
cmSarif::Result result;
|
||||
result.RuleId = ruleInfo.first;
|
||||
result.RuleIndex = ruleInfo.second;
|
||||
result.Message = message.Text;
|
||||
result.Location = GetLocationFromBacktrace(message.Backtrace, this->CM);
|
||||
result.Level = SarifLevelFromMessageType(message.Type);
|
||||
|
||||
run.Results.emplace_back(std::move(result));
|
||||
}
|
||||
|
||||
return cmSarif::WriteLog(path, run);
|
||||
}
|
||||
|
||||
void cmCMakeSarifLogger::GenerateForRun() const
|
||||
{
|
||||
cm::optional<std::string> path = this->FileOutputPath();
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If using the default path within the build dir, ensure parents are created
|
||||
bool const createParents = !this->CM.GetSarifFilePath().has_value();
|
||||
if (!this->WriteFile(*path, createParents)) {
|
||||
cmSystemTools::Error(cmStrCat("Failed to write SARIF log to ", *path));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <cm/optional>
|
||||
|
||||
class cmake;
|
||||
|
||||
/// @brief Manages SARIF logging for a CMake run
|
||||
///
|
||||
/// Writes diagnostics collected during a CMake run to a SARIF log file if
|
||||
/// enabled by conditions.
|
||||
class cmCMakeSarifLogger final
|
||||
{
|
||||
public:
|
||||
cmCMakeSarifLogger(cmake& cm);
|
||||
~cmCMakeSarifLogger();
|
||||
|
||||
void GenerateForRun() const;
|
||||
|
||||
private:
|
||||
bool WriteFile(std::string const& path,
|
||||
bool createParentDirectories = false) const;
|
||||
cm::optional<std::string> FileOutputPath() const;
|
||||
|
||||
cmake const& CM;
|
||||
};
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
#if !defined(CMAKE_BOOTSTRAP)
|
||||
# include "cmsys/SystemInformation.hxx"
|
||||
|
||||
# include "cmSarifLog.h"
|
||||
#endif
|
||||
|
||||
#ifdef CMake_ENABLE_DEBUGGER
|
||||
@@ -178,7 +176,7 @@ void PrintCallStack(std::ostream& out, cmListFileBacktrace bt,
|
||||
} // anonymous namespace
|
||||
|
||||
void cmMessenger::IssueMessage(MessageType t, std::string const& text,
|
||||
cmListFileBacktrace const& backtrace) const
|
||||
cmListFileBacktrace const& backtrace)
|
||||
{
|
||||
this->DisplayMessage(t, cmDiagnostics::CMD_NONE, text, backtrace);
|
||||
}
|
||||
@@ -186,7 +184,7 @@ void cmMessenger::IssueMessage(MessageType t, std::string const& text,
|
||||
void cmMessenger::IssueDiagnostic(cmDiagnosticCategory category,
|
||||
std::string const& text,
|
||||
cmStateSnapshot const& fallbackContext,
|
||||
cmDiagnosticContext const& context) const
|
||||
cmDiagnosticContext const& context)
|
||||
{
|
||||
cmDiagnosticAction const action = [&] {
|
||||
if (context.HasState) {
|
||||
@@ -229,7 +227,7 @@ void cmMessenger::IssueDiagnostic(cmDiagnosticCategory category,
|
||||
void cmMessenger::DisplayMessage(MessageType type,
|
||||
cmDiagnosticCategory category,
|
||||
std::string const& text,
|
||||
cmListFileBacktrace const& backtrace) const
|
||||
cmListFileBacktrace const& backtrace)
|
||||
{
|
||||
std::ostringstream msg;
|
||||
|
||||
@@ -249,10 +247,9 @@ void cmMessenger::DisplayMessage(MessageType type,
|
||||
|
||||
displayMessage(type, category, msg);
|
||||
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
// Add message to SARIF logs
|
||||
this->SarifLog.LogMessage(type, text, backtrace);
|
||||
#endif
|
||||
// Add message to logs
|
||||
this->DisplayedMessages.emplace_back(
|
||||
Message{ type, category, backtrace, text });
|
||||
|
||||
#ifdef CMake_ENABLE_DEBUGGER
|
||||
if (DebuggerAdapter) {
|
||||
|
||||
+18
-14
@@ -7,6 +7,7 @@
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/optional>
|
||||
|
||||
@@ -15,10 +16,6 @@
|
||||
#include "cmListFileCache.h"
|
||||
#include "cmMessageType.h" // IWYU pragma: keep
|
||||
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
# include "cmSarifLog.h"
|
||||
#endif
|
||||
|
||||
class cmStateSnapshot;
|
||||
|
||||
#ifdef CMake_ENABLE_DEBUGGER
|
||||
@@ -32,22 +29,18 @@ class cmMessenger
|
||||
public:
|
||||
void IssueMessage(
|
||||
MessageType type, std::string const& text,
|
||||
cmListFileBacktrace const& backtrace = cmListFileBacktrace()) const;
|
||||
cmListFileBacktrace const& backtrace = cmListFileBacktrace());
|
||||
|
||||
void IssueDiagnostic(cmDiagnosticCategory category, std::string const& text,
|
||||
cmStateSnapshot const& fallbackContext,
|
||||
cmDiagnosticContext const& context = {}) const;
|
||||
cmDiagnosticContext const& context = {});
|
||||
|
||||
void DisplayMessage(MessageType type, cmDiagnosticCategory category,
|
||||
std::string const& text,
|
||||
cmListFileBacktrace const& backtrace) const;
|
||||
cmListFileBacktrace const& backtrace);
|
||||
|
||||
void SetTopSource(cm::optional<std::string> topSource);
|
||||
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
cmSarif::ResultsLog const& GetSarifResultsLog() const { return SarifLog; }
|
||||
#endif
|
||||
|
||||
// Print the top of a backtrace.
|
||||
void PrintBacktraceTitle(std::ostream& out,
|
||||
cmListFileBacktrace const& bt) const;
|
||||
@@ -59,12 +52,23 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
struct Message
|
||||
{
|
||||
MessageType Type;
|
||||
cmDiagnosticCategory Category;
|
||||
cmListFileBacktrace Backtrace;
|
||||
std::string Text;
|
||||
};
|
||||
|
||||
std::vector<Message> const& GetDisplayedMessages() const
|
||||
{
|
||||
return this->DisplayedMessages;
|
||||
}
|
||||
|
||||
private:
|
||||
cm::optional<std::string> TopSource;
|
||||
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
cmSarif::ResultsLog SarifLog;
|
||||
#endif
|
||||
std::vector<Message> DisplayedMessages;
|
||||
|
||||
#ifdef CMake_ENABLE_DEBUGGER
|
||||
std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebuggerAdapter;
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
#include "cmSarif.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
#include <cm3p/json/writer.h>
|
||||
|
||||
#include "cmsys/FStream.hxx"
|
||||
|
||||
namespace cmSarif {
|
||||
|
||||
constexpr char const* SpecVersion = "2.1.0";
|
||||
constexpr char const* SpecSchema =
|
||||
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/"
|
||||
"sarif-schema-2.1.0.json";
|
||||
|
||||
Json::Value GetJson(ResultSeverityLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case ResultSeverityLevel::Warning:
|
||||
return "warning";
|
||||
case ResultSeverityLevel::Error:
|
||||
return "error";
|
||||
case ResultSeverityLevel::Note:
|
||||
return "note";
|
||||
case ResultSeverityLevel::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value GetJson(ArtifactLocation const& artifactLocation)
|
||||
{
|
||||
Json::Value obj(Json::objectValue);
|
||||
obj["uri"] = artifactLocation.Uri;
|
||||
if (!artifactLocation.UriBaseId.empty()) {
|
||||
obj["uriBaseId"] = artifactLocation.UriBaseId;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
Json::Value GetJson(Region region)
|
||||
{
|
||||
Json::Value obj(Json::objectValue);
|
||||
obj["startLine"] = Json::Int64(region.StartLine);
|
||||
return obj;
|
||||
}
|
||||
|
||||
Json::Value GetJson(PhysicalLocation const& physicalLocation)
|
||||
{
|
||||
Json::Value obj(Json::objectValue);
|
||||
obj["artifactLocation"] = cmSarif::GetJson(physicalLocation.Artifact);
|
||||
if (physicalLocation.ArtifactRegion) {
|
||||
obj["region"] = cmSarif::GetJson(*physicalLocation.ArtifactRegion);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
Json::Value GetJson(Location const& location)
|
||||
{
|
||||
Json::Value obj(Json::objectValue);
|
||||
obj["physicalLocation"] = cmSarif::GetJson(location.Physical);
|
||||
return obj;
|
||||
}
|
||||
|
||||
Json::Value GetJson(ReportingDescriptor const& reportingDescriptor)
|
||||
{
|
||||
Json::Value rd(Json::objectValue);
|
||||
rd["id"] = reportingDescriptor.Id;
|
||||
if (reportingDescriptor.Name) {
|
||||
rd["name"] = *reportingDescriptor.Name;
|
||||
}
|
||||
return rd;
|
||||
}
|
||||
|
||||
Json::Value GetJson(Result const& result)
|
||||
{
|
||||
Json::Value resultJson(Json::objectValue);
|
||||
|
||||
if (result.Message) {
|
||||
resultJson["message"]["text"] = *result.Message;
|
||||
}
|
||||
|
||||
if (result.Level) {
|
||||
resultJson["level"] = cmSarif::GetJson(*result.Level);
|
||||
}
|
||||
|
||||
if (result.RuleId) {
|
||||
resultJson["ruleId"] = *result.RuleId;
|
||||
}
|
||||
if (result.RuleIndex) {
|
||||
resultJson["ruleIndex"] = Json::UInt64(*result.RuleIndex);
|
||||
}
|
||||
|
||||
if (result.Location) {
|
||||
resultJson["locations"][0] = cmSarif::GetJson(*result.Location);
|
||||
}
|
||||
|
||||
return resultJson;
|
||||
}
|
||||
|
||||
Json::Value GetJson(ToolComponent const& toolComponent)
|
||||
{
|
||||
Json::Value component(Json::objectValue);
|
||||
component["name"] = toolComponent.Name;
|
||||
component["version"] = toolComponent.Version;
|
||||
Json::Value rules(Json::arrayValue);
|
||||
for (auto const& rule : toolComponent.Rules) {
|
||||
rules.append(cmSarif::GetJson(rule));
|
||||
}
|
||||
component["rules"] = rules;
|
||||
return component;
|
||||
}
|
||||
|
||||
Json::Value GetJson(Tool const& tool)
|
||||
{
|
||||
Json::Value toolJson(Json::objectValue);
|
||||
toolJson["driver"] = cmSarif::GetJson(tool.Driver);
|
||||
return toolJson;
|
||||
}
|
||||
|
||||
Json::Value GetJson(Run const& run)
|
||||
{
|
||||
Json::Value runJson(Json::objectValue);
|
||||
runJson["tool"] = cmSarif::GetJson(run.Tool);
|
||||
Json::Value results(Json::arrayValue);
|
||||
for (auto const& result : run.Results) {
|
||||
results.append(cmSarif::GetJson(result));
|
||||
}
|
||||
runJson["results"] = results;
|
||||
return runJson;
|
||||
}
|
||||
|
||||
bool WriteLog(std::string const& path, cmSarif::Run const& run)
|
||||
{
|
||||
cmsys::ofstream outputFile(path.c_str());
|
||||
if (!outputFile.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Json::Value root(Json::objectValue);
|
||||
root["version"] = SpecVersion;
|
||||
root["$schema"] = SpecSchema;
|
||||
Json::Value runs(Json::arrayValue);
|
||||
runs.append(cmSarif::GetJson(run));
|
||||
root["runs"] = runs;
|
||||
|
||||
Json::StreamWriterBuilder builder;
|
||||
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
writer->write(root, &outputFile);
|
||||
outputFile.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace cmSarif
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/optional>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
|
||||
/// @brief Objects for building serializable SARIF logs
|
||||
namespace cmSarif {
|
||||
|
||||
/// @brief The severity level of a result in SARIF
|
||||
///
|
||||
/// The SARIF specification section 3.27.10 defines four levels of severity
|
||||
/// for results. It is a string property of a result rather than its own type.
|
||||
enum class ResultSeverityLevel
|
||||
{
|
||||
Warning,
|
||||
Error,
|
||||
Note,
|
||||
None,
|
||||
};
|
||||
|
||||
Json::Value GetJson(ResultSeverityLevel level);
|
||||
|
||||
/// @brief SARIF artifactLocation object (§3.4)
|
||||
struct ArtifactLocation
|
||||
{
|
||||
std::string Uri;
|
||||
std::string UriBaseId;
|
||||
};
|
||||
|
||||
Json::Value GetJson(ArtifactLocation const& artifactLocation);
|
||||
|
||||
/// @brief SARIF region object (§3.30)
|
||||
struct Region
|
||||
{
|
||||
long StartLine;
|
||||
};
|
||||
|
||||
Json::Value GetJson(Region region);
|
||||
|
||||
/// @brief SARIF physicalLocation object (§3.29)
|
||||
struct PhysicalLocation
|
||||
{
|
||||
ArtifactLocation Artifact;
|
||||
cm::optional<Region> ArtifactRegion;
|
||||
};
|
||||
|
||||
Json::Value GetJson(PhysicalLocation const& physicalLocation);
|
||||
|
||||
/// @brief SARIF location object (§3.28)
|
||||
struct Location
|
||||
{
|
||||
PhysicalLocation Physical;
|
||||
};
|
||||
|
||||
Json::Value GetJson(Location const& location);
|
||||
|
||||
/// @brief A result reported by a run of a static analysis tool
|
||||
///
|
||||
/// This is the data model for results in a SARIF log. Typically, a result only
|
||||
/// requires either a message or a rule index.
|
||||
struct Result
|
||||
{
|
||||
/// @brief The message text of the result (required if no rule index)
|
||||
cm::optional<std::string> Message;
|
||||
|
||||
/// @brief The location of the result (optional)
|
||||
cm::optional<cmSarif::Location> Location;
|
||||
|
||||
/// @brief The severity level of the result (optional)
|
||||
cm::optional<cmSarif::ResultSeverityLevel> Level;
|
||||
|
||||
/// @brief The rule ID of the result (optional)
|
||||
cm::optional<std::string> RuleId;
|
||||
|
||||
/// @brief The index of the rule in the log's rule array (optional)
|
||||
cm::optional<std::size_t> RuleIndex;
|
||||
};
|
||||
|
||||
Json::Value GetJson(Result const& result);
|
||||
|
||||
/// @brief A reporting descriptor provides information about an analysis result
|
||||
///
|
||||
/// Reporting descriptors (SARIF specification section 3.49) provide
|
||||
/// information about categories of reporting items and is used to define
|
||||
/// rules and taxa.
|
||||
struct ReportingDescriptor
|
||||
{
|
||||
std::string Id;
|
||||
cm::optional<std::string> Name;
|
||||
};
|
||||
|
||||
Json::Value GetJson(ReportingDescriptor const& reportingDescriptor);
|
||||
|
||||
struct ToolComponent
|
||||
{
|
||||
std::string Name;
|
||||
std::string Version;
|
||||
std::vector<ReportingDescriptor> Rules;
|
||||
};
|
||||
|
||||
Json::Value GetJson(ToolComponent const& toolComponent);
|
||||
|
||||
struct Tool
|
||||
{
|
||||
ToolComponent Driver;
|
||||
};
|
||||
|
||||
Json::Value GetJson(Tool const& tool);
|
||||
|
||||
struct Run
|
||||
{
|
||||
cmSarif::Tool Tool;
|
||||
std::vector<Result> Results;
|
||||
};
|
||||
|
||||
Json::Value GetJson(Run const& run);
|
||||
|
||||
bool WriteLog(std::string const& path, cmSarif::Run const& run);
|
||||
|
||||
} // namespace cmSarif
|
||||
@@ -1,368 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
#include "cmSarifLog.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
#include <cm3p/json/writer.h>
|
||||
|
||||
#include "cmsys/FStream.hxx"
|
||||
|
||||
#include "cmListFileCache.h"
|
||||
#include "cmMessageType.h"
|
||||
#include "cmState.h"
|
||||
#include "cmStringAlgorithms.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmValue.h"
|
||||
#include "cmVersionConfig.h"
|
||||
#include "cmake.h"
|
||||
|
||||
cmSarif::ResultsLog::ResultsLog()
|
||||
{
|
||||
// Add the known CMake rules
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.AuthorWarning")
|
||||
.Name("CMake Warning (dev)")
|
||||
.DefaultMessage("CMake Warning (dev): {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.Warning")
|
||||
.Name("CMake Warning")
|
||||
.DefaultMessage("CMake Warning: {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.DeprecationWarning")
|
||||
.Name("CMake Deprecation Warning")
|
||||
.DefaultMessage("CMake Deprecation Warning: {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.AuthorError")
|
||||
.Name("CMake Error (dev)")
|
||||
.DefaultMessage("CMake Error (dev): {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.FatalError")
|
||||
.Name("CMake Error")
|
||||
.DefaultMessage("CMake Error: {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(
|
||||
RuleBuilder("CMake.InternalError")
|
||||
.Name("CMake Internal Error")
|
||||
.DefaultMessage("CMake Internal Error (please report a bug): {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.DeprecationError")
|
||||
.Name("CMake Deprecation Error")
|
||||
.DefaultMessage("CMake Deprecation Error: {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.Message")
|
||||
.Name("CMake Message")
|
||||
.DefaultMessage("CMake Message: {0}")
|
||||
.Build());
|
||||
this->KnownRules.emplace(RuleBuilder("CMake.Log")
|
||||
.Name("CMake Log")
|
||||
.DefaultMessage("CMake Log: {0}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
void cmSarif::ResultsLog::Log(cmSarif::Result&& result) const
|
||||
{
|
||||
// The rule ID is optional, but if it is present, enable metadata output for
|
||||
// the rule by marking it as used
|
||||
if (result.RuleId) {
|
||||
std::size_t index = this->UseRule(*result.RuleId);
|
||||
result.RuleIndex = index;
|
||||
}
|
||||
|
||||
// Add the result to the log
|
||||
this->Results.emplace_back(result);
|
||||
}
|
||||
|
||||
void cmSarif::ResultsLog::LogMessage(
|
||||
MessageType t, std::string const& text,
|
||||
cmListFileBacktrace const& backtrace) const
|
||||
{
|
||||
// Add metadata to the result object
|
||||
// The CMake SARIF rules for messages all expect 1 string argument with the
|
||||
// message text
|
||||
Json::Value additionalProperties(Json::objectValue);
|
||||
Json::Value args(Json::arrayValue);
|
||||
args.append(text);
|
||||
additionalProperties["message"]["id"] = "default";
|
||||
additionalProperties["message"]["arguments"] = args;
|
||||
|
||||
// Create and log a result object
|
||||
// Rule indices are assigned when writing the final JSON output. Right now,
|
||||
// leave it as nullopt. The other optional fields are filled if available
|
||||
this->Log(cmSarif::Result{
|
||||
text, cmSarif::SourceFileLocation::FromBacktrace(backtrace),
|
||||
cmSarif::MessageSeverityLevel(t), cmSarif::MessageRuleId(t), cm::nullopt,
|
||||
additionalProperties });
|
||||
}
|
||||
|
||||
std::size_t cmSarif::ResultsLog::UseRule(std::string const& id) const
|
||||
{
|
||||
// Check if the rule is already in the index
|
||||
auto it = this->RuleToIndex.find(id);
|
||||
if (it != this->RuleToIndex.end()) {
|
||||
// The rule is already in use. Return the known index
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// This rule is not yet in the index, so check if it is recognized
|
||||
auto itKnown = this->KnownRules.find(id);
|
||||
if (itKnown == this->KnownRules.end()) {
|
||||
// The rule is not known. Add an empty rule to the known rules so that it
|
||||
// is included in the output
|
||||
this->KnownRules.emplace(RuleBuilder(id.c_str()).Build());
|
||||
}
|
||||
|
||||
// Since this is the first time the rule is used, enable it and add it to the
|
||||
// index
|
||||
std::size_t idx = this->EnabledRules.size();
|
||||
this->RuleToIndex[id] = idx;
|
||||
this->EnabledRules.emplace_back(id);
|
||||
return idx;
|
||||
}
|
||||
|
||||
cmSarif::ResultSeverityLevel cmSarif::MessageSeverityLevel(MessageType t)
|
||||
{
|
||||
switch (t) {
|
||||
case MessageType::WARNING:
|
||||
return ResultSeverityLevel::SARIF_WARNING;
|
||||
case MessageType::FATAL_ERROR:
|
||||
case MessageType::INTERNAL_ERROR:
|
||||
return ResultSeverityLevel::SARIF_ERROR;
|
||||
case MessageType::MESSAGE:
|
||||
case MessageType::LOG:
|
||||
return ResultSeverityLevel::SARIF_NOTE;
|
||||
default:
|
||||
return ResultSeverityLevel::SARIF_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
cm::optional<std::string> cmSarif::MessageRuleId(MessageType t)
|
||||
{
|
||||
switch (t) {
|
||||
case MessageType::WARNING:
|
||||
return "CMake.Warning";
|
||||
case MessageType::FATAL_ERROR:
|
||||
return "CMake.FatalError";
|
||||
case MessageType::INTERNAL_ERROR:
|
||||
return "CMake.InternalError";
|
||||
case MessageType::MESSAGE:
|
||||
return "CMake.Message";
|
||||
case MessageType::LOG:
|
||||
return "CMake.Log";
|
||||
default:
|
||||
return cm::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value cmSarif::Rule::GetJson() const
|
||||
{
|
||||
Json::Value rule(Json::objectValue);
|
||||
rule["id"] = this->Id;
|
||||
|
||||
if (this->Name) {
|
||||
rule["name"] = *this->Name;
|
||||
}
|
||||
if (this->FullDescription) {
|
||||
rule["fullDescription"]["text"] = *this->FullDescription;
|
||||
}
|
||||
if (this->DefaultMessage) {
|
||||
rule["messageStrings"]["default"]["text"] = *this->DefaultMessage;
|
||||
}
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
cmSarif::SourceFileLocation::SourceFileLocation(
|
||||
cmListFileBacktrace const& backtrace)
|
||||
{
|
||||
if (backtrace.Empty()) {
|
||||
throw std::runtime_error("Empty source file location");
|
||||
}
|
||||
|
||||
cmListFileContext const& lfc = backtrace.Top();
|
||||
this->Uri = lfc.FilePath;
|
||||
this->Line = lfc.Line;
|
||||
}
|
||||
|
||||
cm::optional<cmSarif::SourceFileLocation>
|
||||
cmSarif::SourceFileLocation::FromBacktrace(
|
||||
cmListFileBacktrace const& backtrace)
|
||||
{
|
||||
if (backtrace.Empty()) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
cmListFileContext const& lfc = backtrace.Top();
|
||||
if (lfc.Line <= 0 || lfc.FilePath.empty()) {
|
||||
return cm::nullopt;
|
||||
}
|
||||
|
||||
return cm::make_optional<cmSarif::SourceFileLocation>(backtrace);
|
||||
}
|
||||
|
||||
void cmSarif::ResultsLog::WriteJson(Json::Value& root) const
|
||||
{
|
||||
// Add SARIF metadata
|
||||
root["version"] = "2.1.0";
|
||||
root["$schema"] = "https://schemastore.azurewebsites.net/schemas/json/"
|
||||
"sarif-2.1.0-rtm.4.json";
|
||||
|
||||
// JSON object for the SARIF runs array
|
||||
Json::Value runs(Json::arrayValue);
|
||||
|
||||
// JSON object for the current (only) run
|
||||
Json::Value currentRun(Json::objectValue);
|
||||
|
||||
// Accumulate info about the reported rules
|
||||
Json::Value jsonRules(Json::arrayValue);
|
||||
for (auto const& ruleId : this->EnabledRules) {
|
||||
jsonRules.append(KnownRules.at(ruleId).GetJson());
|
||||
}
|
||||
|
||||
// Add info the driver for the current run (CMake)
|
||||
Json::Value driverTool(Json::objectValue);
|
||||
driverTool["name"] = "CMake";
|
||||
driverTool["version"] = CMake_VERSION;
|
||||
driverTool["rules"] = jsonRules;
|
||||
currentRun["tool"]["driver"] = driverTool;
|
||||
|
||||
runs.append(currentRun);
|
||||
|
||||
// Add all results
|
||||
Json::Value jsonResults(Json::arrayValue);
|
||||
for (auto const& res : this->Results) {
|
||||
Json::Value jsonResult(Json::objectValue);
|
||||
|
||||
if (res.Message) {
|
||||
jsonResult["message"]["text"] = *(res.Message);
|
||||
}
|
||||
|
||||
// If the result has a level, add it to the result
|
||||
if (res.Level) {
|
||||
switch (*res.Level) {
|
||||
case ResultSeverityLevel::SARIF_WARNING:
|
||||
jsonResult["level"] = "warning";
|
||||
break;
|
||||
case ResultSeverityLevel::SARIF_ERROR:
|
||||
jsonResult["level"] = "error";
|
||||
break;
|
||||
case ResultSeverityLevel::SARIF_NOTE:
|
||||
jsonResult["level"] = "note";
|
||||
break;
|
||||
case ResultSeverityLevel::SARIF_NONE:
|
||||
jsonResult["level"] = "none";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the result has a rule ID or index, add it to the result
|
||||
if (res.RuleId) {
|
||||
jsonResult["ruleId"] = *res.RuleId;
|
||||
}
|
||||
if (res.RuleIndex) {
|
||||
jsonResult["ruleIndex"] = Json::UInt64(*res.RuleIndex);
|
||||
}
|
||||
|
||||
if (res.Location) {
|
||||
jsonResult["locations"][0]["physicalLocation"]["artifactLocation"]
|
||||
["uri"] = (res.Location)->Uri;
|
||||
jsonResult["locations"][0]["physicalLocation"]["region"]["startLine"] =
|
||||
Json::Int64((res.Location)->Line);
|
||||
}
|
||||
|
||||
jsonResults.append(jsonResult);
|
||||
}
|
||||
|
||||
currentRun["results"] = jsonResults;
|
||||
runs[0] = currentRun;
|
||||
root["runs"] = runs;
|
||||
}
|
||||
|
||||
cmSarif::LogFileWriter::~LogFileWriter()
|
||||
{
|
||||
// If the file has not been written yet, try to finalize it
|
||||
if (!this->FileWritten) {
|
||||
// Try to write and check the result
|
||||
if (this->TryWrite() == WriteResult::FAILURE) {
|
||||
// If the result is `FAILURE`, it means the write condition is true but
|
||||
// the file still wasn't written. This is an error.
|
||||
cmSystemTools::Error("Failed to write SARIF log to " + this->FilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cmSarif::LogFileWriter::EnsureFileValid()
|
||||
{
|
||||
// First, ensure directory exists
|
||||
std::string const dir = cmSystemTools::GetFilenamePath(this->FilePath);
|
||||
if (!cmSystemTools::FileIsDirectory(dir)) {
|
||||
if (!this->CreateDirectories ||
|
||||
!cmSystemTools::MakeDirectory(dir).IsSuccess()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the file for writing
|
||||
cmsys::ofstream outputFile(this->FilePath.c_str());
|
||||
if (!outputFile.good()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
cmSarif::LogFileWriter::WriteResult cmSarif::LogFileWriter::TryWrite()
|
||||
{
|
||||
// Check that SARIF logging is enabled
|
||||
if (!this->WriteCondition || !this->WriteCondition()) {
|
||||
return WriteResult::SKIPPED;
|
||||
}
|
||||
|
||||
// Open the file
|
||||
if (!this->EnsureFileValid()) {
|
||||
return WriteResult::FAILURE;
|
||||
}
|
||||
cmsys::ofstream outputFile(this->FilePath.c_str());
|
||||
|
||||
// The file is available, so proceed to write the log
|
||||
|
||||
// Assemble the SARIF JSON from the results in the log
|
||||
Json::Value root(Json::objectValue);
|
||||
this->Log.WriteJson(root);
|
||||
|
||||
// Serialize the JSON to the file
|
||||
Json::StreamWriterBuilder builder;
|
||||
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
|
||||
writer->write(root, &outputFile);
|
||||
outputFile.close();
|
||||
|
||||
this->FileWritten = true;
|
||||
return WriteResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool cmSarif::LogFileWriter::ConfigureForCMakeRun(cmake& cm)
|
||||
{
|
||||
// If an explicit SARIF output path has been provided, set and check it
|
||||
if (cm::optional<std::string> sarifFilePath = cm.GetSarifFilePath()) {
|
||||
this->SetPath(*sarifFilePath);
|
||||
if (!this->EnsureFileValid()) {
|
||||
cmSystemTools::Error(
|
||||
cmStrCat("Invalid SARIF output file path: ", *sarifFilePath));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The write condition is checked immediately before writing the file, which
|
||||
// allows projects to enable SARIF diagnostics by setting a cache variable
|
||||
// and have it take effect for the current run.
|
||||
this->SetWriteCondition([&cm]() {
|
||||
// The command-line option can be used to set an explicit path, but in
|
||||
// normal mode, the project variable `CMAKE_EXPORT_SARIF` can also enable
|
||||
// SARIF logging.
|
||||
return cm.GetSarifFilePath().has_value() ||
|
||||
(cm.GetState()->GetRole() == cmState::Role::Project &&
|
||||
cm.GetCacheDefinition(cmSarif::PROJECT_SARIF_FILE_VARIABLE).IsOn());
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/optional>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
|
||||
class cmake;
|
||||
class cmListFileBacktrace;
|
||||
enum class MessageType;
|
||||
|
||||
/// @brief CMake support for SARIF logging
|
||||
namespace cmSarif {
|
||||
|
||||
constexpr char const* PROJECT_SARIF_FILE_VARIABLE = "CMAKE_EXPORT_SARIF";
|
||||
|
||||
constexpr char const* PROJECT_DEFAULT_SARIF_FILE = ".cmake/sarif/cmake.sarif";
|
||||
|
||||
/// @brief The severity level of a result in SARIF
|
||||
///
|
||||
/// The SARIF specification section 3.27.10 defines four levels of severity
|
||||
/// for results.
|
||||
enum class ResultSeverityLevel
|
||||
{
|
||||
SARIF_WARNING,
|
||||
SARIF_ERROR,
|
||||
SARIF_NOTE,
|
||||
SARIF_NONE,
|
||||
};
|
||||
|
||||
/// @brief A location in a source file logged with a SARIF result
|
||||
struct SourceFileLocation
|
||||
{
|
||||
std::string Uri;
|
||||
long Line = 0;
|
||||
|
||||
/// @brief Construct a SourceFileLocation at the top of the call stack
|
||||
SourceFileLocation(cmListFileBacktrace const& backtrace);
|
||||
|
||||
/// @brief Get the SourceFileLocation from the top of a call stack, if any
|
||||
/// @return The location or nullopt if the call stack is empty or is missing
|
||||
/// location information
|
||||
static cm::optional<SourceFileLocation> FromBacktrace(
|
||||
cmListFileBacktrace const& backtrace);
|
||||
};
|
||||
|
||||
/// @brief A result defined by SARIF reported by a CMake run
|
||||
///
|
||||
/// This is the data model for results in a SARIF log. Typically, a result only
|
||||
/// requires either a message or a rule index. The most common properties are
|
||||
/// named in this struct, but arbitrary metadata can be added to the result
|
||||
/// using the additionalProperties field.
|
||||
struct Result
|
||||
{
|
||||
/// @brief The message text of the result (required if no rule index)
|
||||
cm::optional<std::string> Message;
|
||||
|
||||
/// @brief The location of the result (optional)
|
||||
cm::optional<cmSarif::SourceFileLocation> Location;
|
||||
|
||||
/// @brief The severity level of the result (optional)
|
||||
cm::optional<cmSarif::ResultSeverityLevel> Level;
|
||||
|
||||
/// @brief The rule ID of the result (optional)
|
||||
cm::optional<std::string> RuleId;
|
||||
|
||||
/// @brief The index of the rule in the log's rule array (optional)
|
||||
cm::optional<std::size_t> RuleIndex;
|
||||
|
||||
/// @brief Additional JSON properties for the result (optional)
|
||||
///
|
||||
/// The additional properties should be merged into the result object when it
|
||||
/// is written to the SARIF log.
|
||||
Json::Value AdditionalProperties;
|
||||
};
|
||||
|
||||
/// @brief A SARIF reporting rule
|
||||
///
|
||||
/// A rule in SARIF is described by a reportingDescriptor object (SARIF
|
||||
/// specification section 3.49). The only property required for a rule is the
|
||||
/// ID property. The ID is normally an opaque string that identifies a rule
|
||||
/// applicable to a class of results. The other included properties are
|
||||
/// optional but recommended for rules reported by CMake.
|
||||
struct Rule
|
||||
{
|
||||
/// @brief The ID of the rule. Required by SARIF
|
||||
std::string Id;
|
||||
|
||||
/// @brief The end-user name of the rule (optional)
|
||||
cm::optional<std::string> Name;
|
||||
|
||||
/// @brief The extended description of the rule (optional)
|
||||
cm::optional<std::string> FullDescription;
|
||||
|
||||
/// @brief The default message for the rule (optional)
|
||||
cm::optional<std::string> DefaultMessage;
|
||||
|
||||
/// @brief Get the JSON representation of this rule
|
||||
Json::Value GetJson() const;
|
||||
};
|
||||
|
||||
/// @brief A builder for SARIF rules
|
||||
///
|
||||
/// `Rule` is a data model for SARIF rules. Known rules are usually initialized
|
||||
/// manually by field. Using a builder makes initialization more readable and
|
||||
/// prevents issues with reordering and optional fields.
|
||||
class RuleBuilder
|
||||
{
|
||||
public:
|
||||
/// @brief Construct a new rule builder for a rule with the given ID
|
||||
RuleBuilder(char const* id) { this->NewRule.Id = id; }
|
||||
|
||||
/// @brief Set the name of the rule
|
||||
RuleBuilder& Name(std::string name)
|
||||
{
|
||||
this->NewRule.Name = std::move(name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Set the full description of the rule
|
||||
RuleBuilder& FullDescription(std::string fullDescription)
|
||||
{
|
||||
this->NewRule.FullDescription = std::move(fullDescription);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Set the default message for the rule
|
||||
RuleBuilder& DefaultMessage(std::string defaultMessage)
|
||||
{
|
||||
this->NewRule.DefaultMessage = std::move(defaultMessage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief Build the rule
|
||||
std::pair<std::string, Rule> Build() const
|
||||
{
|
||||
return std::make_pair(this->NewRule.Id, this->NewRule);
|
||||
}
|
||||
|
||||
private:
|
||||
Rule NewRule;
|
||||
};
|
||||
|
||||
/// @brief Get the SARIF severity level of a CMake message type
|
||||
ResultSeverityLevel MessageSeverityLevel(MessageType t);
|
||||
|
||||
/// @brief Get the SARIF rule ID of a CMake message type
|
||||
/// @return The rule ID or nullopt if the message type is unrecognized
|
||||
///
|
||||
/// The rule ID is a string assigned to SARIF results to identify the category
|
||||
/// of the result. CMake maps messages to rules based on the message type.
|
||||
/// CMake's rules are of the form "CMake.<MessageType>".
|
||||
cm::optional<std::string> MessageRuleId(MessageType t);
|
||||
|
||||
/// @brief A log for reporting results in the SARIF format
|
||||
class ResultsLog
|
||||
{
|
||||
public:
|
||||
ResultsLog();
|
||||
|
||||
/// @brief Log a result of this run to the SARIF output
|
||||
void Log(cmSarif::Result&& result) const;
|
||||
|
||||
/// @brief Log a result from a CMake message with a source file location
|
||||
/// @param t The type of the message, which corresponds to the level and rule
|
||||
/// of the result
|
||||
/// @param text The contents of the message
|
||||
/// @param backtrace The call stack where the message originated (may be
|
||||
/// empty)
|
||||
void LogMessage(MessageType t, std::string const& text,
|
||||
cmListFileBacktrace const& backtrace) const;
|
||||
|
||||
/// @brief Write this SARIF log to an empty JSON object
|
||||
/// @param[out] root The JSON object to write to
|
||||
void WriteJson(Json::Value& root) const;
|
||||
|
||||
private:
|
||||
// Private methods
|
||||
|
||||
// Log that a rule was used and should be included in the output. Returns the
|
||||
// index of the rule in the log
|
||||
std::size_t UseRule(std::string const& id) const;
|
||||
|
||||
// Private data
|
||||
// All data is mutable since log results are often added in const methods
|
||||
|
||||
// All results added chronologically
|
||||
mutable std::vector<cmSarif::Result> Results;
|
||||
|
||||
// Mapping of rule IDs to rule indices in the log.
|
||||
// In SARIF, rule metadata is typically only included if the rule is
|
||||
// referenced. The indices are unique to one log output and vary
|
||||
// depending on when the rule was first encountered.
|
||||
mutable std::unordered_map<std::string, std::size_t> RuleToIndex;
|
||||
|
||||
// Rules that will be added to the log in order of appearance
|
||||
mutable std::vector<std::string> EnabledRules;
|
||||
|
||||
// All known rules that could be included in a log
|
||||
mutable std::unordered_map<std::string, Rule> KnownRules;
|
||||
};
|
||||
|
||||
/// @brief Writes contents of a `cmSarif::ResultsLog` to a file
|
||||
///
|
||||
/// The log file writer is a helper class that writes the contents of a
|
||||
/// `cmSarif::ResultsLog` upon destruction if a condition (e.g. project
|
||||
/// variable is enabled) is met.
|
||||
class LogFileWriter
|
||||
{
|
||||
public:
|
||||
/// @brief Create a new, disabled log file writer
|
||||
///
|
||||
/// The returned writer will not write anything until the path generator
|
||||
/// and write condition are set. If the log has not been written when the
|
||||
/// object is being destroyed, the destructor will write the log if the
|
||||
/// condition is met and a valid path is available.
|
||||
LogFileWriter(ResultsLog const& log)
|
||||
: Log(log)
|
||||
{
|
||||
}
|
||||
|
||||
/// @brief Configure a log file writer for a CMake run
|
||||
///
|
||||
/// CMake should write a SARIF log if the project variable
|
||||
/// `CMAKE_EXPORT_SARIF` is `ON` or if the `--sarif-output=<path>` command
|
||||
/// line option is set. The writer will be configured to respond to these
|
||||
/// conditions.
|
||||
///
|
||||
/// This does not configure a default path, so one must be set once it is
|
||||
/// known that we're in normal mode if none was explicitly provided.
|
||||
bool ConfigureForCMakeRun(cmake& cm);
|
||||
|
||||
~LogFileWriter();
|
||||
|
||||
/// @brief Check if a valid path is set by opening the output file
|
||||
/// @return True if the file can be opened for writing
|
||||
bool EnsureFileValid();
|
||||
|
||||
/// @brief The possible outcomes of trying to write the log file
|
||||
enum class WriteResult
|
||||
{
|
||||
SUCCESS, ///< File written with no issues
|
||||
FAILURE, ///< Error encountered while writing the file
|
||||
SKIPPED, ///< Writing was skipped due to false write condition
|
||||
};
|
||||
|
||||
/// @brief Try to write the log file and return `true` if it was written
|
||||
///
|
||||
/// Check the write condition and path generator to determine if the log
|
||||
/// file should be written.
|
||||
WriteResult TryWrite();
|
||||
|
||||
/// @brief Set a lambda to check if the log file should be written
|
||||
void SetWriteCondition(std::function<bool()> const& checkConditionCallback)
|
||||
{
|
||||
this->WriteCondition = checkConditionCallback;
|
||||
}
|
||||
|
||||
/// @brief Set the output file path, optionally creating parent directories
|
||||
///
|
||||
/// The settings will apply when the log file is written. If the output
|
||||
/// file should be checked earlier, use `CheckFileValidity`.
|
||||
void SetPath(std::string const& path, bool createParentDirectories = false)
|
||||
{
|
||||
this->FilePath = path;
|
||||
this->CreateDirectories = createParentDirectories;
|
||||
}
|
||||
|
||||
private:
|
||||
ResultsLog const& Log;
|
||||
std::function<bool()> WriteCondition;
|
||||
std::string FilePath;
|
||||
bool CreateDirectories = false;
|
||||
bool FileWritten = false;
|
||||
};
|
||||
|
||||
} // namespace cmSarif
|
||||
+2
-21
@@ -84,6 +84,7 @@
|
||||
# include <cm3p/json/writer.h>
|
||||
|
||||
# include "cmCMakePresetsArgs.h"
|
||||
# include "cmCMakeSarifLogger.h"
|
||||
# include "cmConfigureLog.h"
|
||||
# include "cmFileAPI.h"
|
||||
# include "cmGraphVizWriter.h"
|
||||
@@ -91,7 +92,6 @@
|
||||
# include "cmInstrumentationInterrupt.h"
|
||||
# include "cmInstrumentationQuery.h"
|
||||
# include "cmMakefileProfilingData.h"
|
||||
# include "cmSarifLog.h"
|
||||
# include "cmVariableWatch.h"
|
||||
#endif
|
||||
|
||||
@@ -3076,11 +3076,7 @@ int cmake::Run(std::vector<std::string> const& args, bool noconfigure)
|
||||
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
// Configure the SARIF log for the current run
|
||||
cmSarif::LogFileWriter sarifLogFileWriter(
|
||||
this->GetMessenger()->GetSarifResultsLog());
|
||||
if (!sarifLogFileWriter.ConfigureForCMakeRun(*this)) {
|
||||
return -1;
|
||||
}
|
||||
cmCMakeSarifLogger sarifLogger(*this);
|
||||
|
||||
this->VariableWatch->AddWatch("CMAKE_WARN_DEPRECATED", cmDeprecatedWatch);
|
||||
this->VariableWatch->AddWatch("CMAKE_ERROR_DEPRECATED", cmDeprecatedWatch);
|
||||
@@ -3112,16 +3108,6 @@ int cmake::Run(std::vector<std::string> const& args, bool noconfigure)
|
||||
cmSystemTools::Error("Error executing cmake::LoadCache(). Aborting.\n");
|
||||
return -1;
|
||||
}
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
// If no SARIF file has been explicitly specified, use the default path
|
||||
if (!this->SarifFileOutput) {
|
||||
// If no output file is specified, use the default path
|
||||
// Enable parent directory creation for the default path
|
||||
sarifLogFileWriter.SetPath(cmStrCat(this->GetHomeOutputDirectory(), '/',
|
||||
cmSarif::PROJECT_DEFAULT_SARIF_FILE),
|
||||
true);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
if (this->FreshCache) {
|
||||
cmSystemTools::Error("--fresh allowed only when configuring a project");
|
||||
@@ -3156,11 +3142,6 @@ int cmake::Run(std::vector<std::string> const& args, bool noconfigure)
|
||||
return this->HasScriptModeExitCode() ? this->GetScriptModeExitCode() : 0;
|
||||
}
|
||||
|
||||
#ifndef CMAKE_BOOTSTRAP
|
||||
// CMake only responds to the SARIF variable in normal mode
|
||||
this->MarkCliAsUsed(cmSarif::PROJECT_SARIF_FILE_VARIABLE);
|
||||
#endif
|
||||
|
||||
// If MAKEFLAGS are given in the environment, remove the environment
|
||||
// variable. This will prevent try-compile from succeeding when it
|
||||
// should fail (if "-i" is an option). We cannot simply test
|
||||
|
||||
@@ -1,67 +1,64 @@
|
||||
{
|
||||
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
|
||||
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
|
||||
"runs": [
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"level": "warning",
|
||||
"locations": [
|
||||
"results": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "PATH:<SOURCE_DIR>/GenerateSarifResults.cmake"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": {
|
||||
"text": "Example warning message"
|
||||
},
|
||||
"ruleId": "CMake.Warning",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "PATH:<SOURCE_DIR>/GenerateSarifResults.cmake"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 5
|
||||
"level": "warning",
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "GenerateSarifResults.cmake",
|
||||
"uriBaseId": "PATH:<SOURCE_DIR>"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": {
|
||||
"text": "A second example warning message"
|
||||
],
|
||||
"message": {
|
||||
"text": "Example warning message"
|
||||
},
|
||||
"ruleId": "CMake.Warning",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
"ruleId": "CMake.Warning",
|
||||
"ruleIndex": 0
|
||||
}
|
||||
],
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "CMake",
|
||||
"rules": [
|
||||
{
|
||||
"id": "CMake.Warning",
|
||||
"messageStrings": {
|
||||
"default": {
|
||||
"text": "CMake Warning: {0}"
|
||||
}
|
||||
},
|
||||
"name": "CMake Warning"
|
||||
"level": "warning",
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "GenerateSarifResults.cmake",
|
||||
"uriBaseId": "PATH:<SOURCE_DIR>"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": {
|
||||
"text": "A second example warning message"
|
||||
},
|
||||
"ruleId": "CMake.Warning",
|
||||
"ruleIndex": 0
|
||||
}
|
||||
],
|
||||
"version": "<IGNORE>"
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "CMake",
|
||||
"rules": [
|
||||
{
|
||||
"id": "CMake.Warning",
|
||||
"name": "CMake Warning"
|
||||
}
|
||||
],
|
||||
"version": "<IGNORE>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "2.1.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
|
||||
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
|
||||
"runs": [
|
||||
{
|
||||
"results": [
|
||||
@@ -9,7 +9,8 @@
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "PATH:<SOURCE_DIR>/ProjectFatalError.cmake"
|
||||
"uri": "ProjectFatalError.cmake",
|
||||
"uriBaseId": "PATH:<SOURCE_DIR>"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 1
|
||||
@@ -30,11 +31,6 @@
|
||||
"rules": [
|
||||
{
|
||||
"id": "CMake.FatalError",
|
||||
"messageStrings": {
|
||||
"default": {
|
||||
"text": "CMake Error: {0}"
|
||||
}
|
||||
},
|
||||
"name": "CMake Error"
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user