
``` git grep -lz 'Copyright.txt or https://cmake.org/licensing ' | while IFS= read -r -d $'\0' f ; do sed -i '/Copyright.txt or https:\/\/cmake.org\/licensing / { s/Copyright.txt/LICENSE.rst/ }' "$f" ; done ```
384 lines
12 KiB
C++
384 lines
12 KiB
C++
/* 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 <cm/filesystem>
|
|
|
|
#include <cm3p/json/value.h>
|
|
#include <cm3p/json/writer.h>
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
|
|
#include "cmListFileCache.h"
|
|
#include "cmMessageType.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::AUTHOR_WARNING:
|
|
case MessageType::WARNING:
|
|
case MessageType::DEPRECATION_WARNING:
|
|
return ResultSeverityLevel::SARIF_WARNING;
|
|
case MessageType::AUTHOR_ERROR:
|
|
case MessageType::FATAL_ERROR:
|
|
case MessageType::INTERNAL_ERROR:
|
|
case MessageType::DEPRECATION_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::AUTHOR_WARNING:
|
|
return "CMake.AuthorWarning";
|
|
case MessageType::WARNING:
|
|
return "CMake.Warning";
|
|
case MessageType::DEPRECATION_WARNING:
|
|
return "CMake.DeprecationWarning";
|
|
case MessageType::AUTHOR_ERROR:
|
|
return "CMake.AuthorError";
|
|
case MessageType::FATAL_ERROR:
|
|
return "CMake.FatalError";
|
|
case MessageType::INTERNAL_ERROR:
|
|
return "CMake.InternalError";
|
|
case MessageType::DEPRECATION_ERROR:
|
|
return "CMake.DeprecationError";
|
|
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.generic_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cmSarif::LogFileWriter::EnsureFileValid()
|
|
{
|
|
// First, ensure directory exists
|
|
cm::filesystem::path dir = this->FilePath.parent_path();
|
|
if (!cmSystemTools::FileIsDirectory(dir.generic_string())) {
|
|
if (!this->CreateDirectories ||
|
|
!cmSystemTools::MakeDirectory(dir.generic_string()).IsSuccess()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Open the file for writing
|
|
cmsys::ofstream outputFile(this->FilePath.generic_string().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.generic_string().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
|
|
cm::optional<std::string> sarifFilePath = cm.GetSarifFilePath();
|
|
if (sarifFilePath) {
|
|
this->SetPath(cm::filesystem::path(*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.GetWorkingMode() == cmake::NORMAL_MODE &&
|
|
cm.GetCacheDefinition(cmSarif::PROJECT_SARIF_FILE_VARIABLE).IsOn());
|
|
});
|
|
|
|
return true;
|
|
}
|