File: cmSarifLog.cxx

package info (click to toggle)
cmake 4.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 147,412 kB
  • sloc: ansic: 403,924; cpp: 290,826; sh: 4,091; python: 3,357; yacc: 3,106; lex: 1,189; f90: 532; asm: 471; lisp: 375; cs: 270; java: 266; fortran: 230; perl: 217; objc: 215; xml: 198; makefile: 98; javascript: 83; pascal: 63; tcl: 55; php: 25; ruby: 22
file content (383 lines) | stat: -rw-r--r-- 12,295 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
/* 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;
}