Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmSarifLog.h
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
    file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#pragma once
4
5
#include <cstddef>
6
#include <functional>
7
#include <string>
8
#include <unordered_map>
9
#include <utility>
10
#include <vector>
11
12
#include <cm/optional>
13
14
#include <cm3p/json/value.h>
15
16
class cmake;
17
class cmListFileBacktrace;
18
enum class MessageType;
19
20
/// @brief CMake support for SARIF logging
21
namespace cmSarif {
22
23
constexpr char const* PROJECT_SARIF_FILE_VARIABLE = "CMAKE_EXPORT_SARIF";
24
25
constexpr char const* PROJECT_DEFAULT_SARIF_FILE = ".cmake/sarif/cmake.sarif";
26
27
/// @brief The severity level of a result in SARIF
28
///
29
/// The SARIF specification section 3.27.10 defines four levels of severity
30
/// for results.
31
enum class ResultSeverityLevel
32
{
33
  SARIF_WARNING,
34
  SARIF_ERROR,
35
  SARIF_NOTE,
36
  SARIF_NONE,
37
};
38
39
/// @brief A location in a source file logged with a SARIF result
40
struct SourceFileLocation
41
{
42
  std::string Uri;
43
  long Line = 0;
44
45
  /// @brief Construct a SourceFileLocation at the top of the call stack
46
  SourceFileLocation(cmListFileBacktrace const& backtrace);
47
48
  /// @brief Get the SourceFileLocation from the top of a call stack, if any
49
  /// @return The location or nullopt if the call stack is empty or is missing
50
  /// location information
51
  static cm::optional<SourceFileLocation> FromBacktrace(
52
    cmListFileBacktrace const& backtrace);
53
};
54
55
/// @brief A result defined by SARIF reported by a CMake run
56
///
57
/// This is the data model for results in a SARIF log. Typically, a result only
58
/// requires either a message or a rule index. The most common properties are
59
/// named in this struct, but arbitrary metadata can be added to the result
60
/// using the additionalProperties field.
61
struct Result
62
{
63
  /// @brief The message text of the result (required if no rule index)
64
  cm::optional<std::string> Message;
65
66
  /// @brief The location of the result (optional)
67
  cm::optional<cmSarif::SourceFileLocation> Location;
68
69
  /// @brief The severity level of the result (optional)
70
  cm::optional<cmSarif::ResultSeverityLevel> Level;
71
72
  /// @brief The rule ID of the result (optional)
73
  cm::optional<std::string> RuleId;
74
75
  /// @brief The index of the rule in the log's rule array (optional)
76
  cm::optional<std::size_t> RuleIndex;
77
78
  /// @brief Additional JSON properties for the result (optional)
79
  ///
80
  /// The additional properties should be merged into the result object when it
81
  /// is written to the SARIF log.
82
  Json::Value AdditionalProperties;
83
};
84
85
/// @brief A SARIF reporting rule
86
///
87
/// A rule in SARIF is described by a reportingDescriptor object (SARIF
88
/// specification section 3.49). The only property required for a rule is the
89
/// ID property. The ID is normally an opaque string that identifies a rule
90
/// applicable to a class of results. The other included properties are
91
/// optional but recommended for rules reported by CMake.
92
struct Rule
93
{
94
  /// @brief The ID of the rule. Required by SARIF
95
  std::string Id;
96
97
  /// @brief The end-user name of the rule (optional)
98
  cm::optional<std::string> Name;
99
100
  /// @brief The extended description of the rule (optional)
101
  cm::optional<std::string> FullDescription;
102
103
  /// @brief The default message for the rule (optional)
104
  cm::optional<std::string> DefaultMessage;
105
106
  /// @brief Get the JSON representation of this rule
107
  Json::Value GetJson() const;
108
};
109
110
/// @brief A builder for SARIF rules
111
///
112
/// `Rule` is a data model for SARIF rules. Known rules are usually initialized
113
/// manually by field. Using a builder makes initialization more readable and
114
/// prevents issues with reordering and optional fields.
115
class RuleBuilder
116
{
117
public:
118
  /// @brief Construct a new rule builder for a rule with the given ID
119
315
  RuleBuilder(char const* id) { this->NewRule.Id = id; }
120
121
  /// @brief Set the name of the rule
122
  RuleBuilder& Name(std::string name)
123
315
  {
124
315
    this->NewRule.Name = std::move(name);
125
315
    return *this;
126
315
  }
127
128
  /// @brief Set the full description of the rule
129
  RuleBuilder& FullDescription(std::string fullDescription)
130
0
  {
131
0
    this->NewRule.FullDescription = std::move(fullDescription);
132
0
    return *this;
133
0
  }
134
135
  /// @brief Set the default message for the rule
136
  RuleBuilder& DefaultMessage(std::string defaultMessage)
137
315
  {
138
315
    this->NewRule.DefaultMessage = std::move(defaultMessage);
139
315
    return *this;
140
315
  }
141
142
  /// @brief Build the rule
143
  std::pair<std::string, Rule> Build() const
144
315
  {
145
315
    return std::make_pair(this->NewRule.Id, this->NewRule);
146
315
  }
147
148
private:
149
  Rule NewRule;
150
};
151
152
/// @brief Get the SARIF severity level of a CMake message type
153
ResultSeverityLevel MessageSeverityLevel(MessageType t);
154
155
/// @brief Get the SARIF rule ID of a CMake message type
156
/// @return The rule ID or nullopt if the message type is unrecognized
157
///
158
/// The rule ID is a string assigned to SARIF results to identify the category
159
/// of the result. CMake maps messages to rules based on the message type.
160
/// CMake's rules are of the form "CMake.<MessageType>".
161
cm::optional<std::string> MessageRuleId(MessageType t);
162
163
/// @brief A log for reporting results in the SARIF format
164
class ResultsLog
165
{
166
public:
167
  ResultsLog();
168
169
  /// @brief Log a result of this run to the SARIF output
170
  void Log(cmSarif::Result&& result) const;
171
172
  /// @brief Log a result from a CMake message with a source file location
173
  /// @param t The type of the message, which corresponds to the level and rule
174
  /// of the result
175
  /// @param text The contents of the message
176
  /// @param backtrace The call stack where the message originated (may be
177
  /// empty)
178
  void LogMessage(MessageType t, std::string const& text,
179
                  cmListFileBacktrace const& backtrace) const;
180
181
  /// @brief Write this SARIF log to an empty JSON object
182
  /// @param[out] root The JSON object to write to
183
  void WriteJson(Json::Value& root) const;
184
185
private:
186
  // Private methods
187
188
  // Log that a rule was used and should be included in the output. Returns the
189
  // index of the rule in the log
190
  std::size_t UseRule(std::string const& id) const;
191
192
  // Private data
193
  // All data is mutable since log results are often added in const methods
194
195
  // All results added chronologically
196
  mutable std::vector<cmSarif::Result> Results;
197
198
  // Mapping of rule IDs to rule indices in the log.
199
  // In SARIF, rule metadata is typically only included if the rule is
200
  // referenced. The indices are unique to one log output and vary
201
  // depending on when the rule was first encountered.
202
  mutable std::unordered_map<std::string, std::size_t> RuleToIndex;
203
204
  // Rules that will be added to the log in order of appearance
205
  mutable std::vector<std::string> EnabledRules;
206
207
  // All known rules that could be included in a log
208
  mutable std::unordered_map<std::string, Rule> KnownRules;
209
};
210
211
/// @brief Writes contents of a `cmSarif::ResultsLog` to a file
212
///
213
/// The log file writer is a helper class that writes the contents of a
214
/// `cmSarif::ResultsLog` upon destruction if a condition (e.g. project
215
/// variable is enabled) is met.
216
class LogFileWriter
217
{
218
public:
219
  /// @brief Create a new, disabled log file writer
220
  ///
221
  /// The returned writer will not write anything until the path generator
222
  /// and write condition are set. If the log has not been written when the
223
  /// object is being destroyed, the destructor will write the log if the
224
  /// condition is met and a valid path is available.
225
  LogFileWriter(ResultsLog const& log)
226
1
    : Log(log)
227
1
  {
228
1
  }
229
230
  /// @brief Configure a log file writer for a CMake run
231
  ///
232
  /// CMake should write a SARIF log if the project variable
233
  /// `CMAKE_EXPORT_SARIF` is `ON` or if the `--sarif-output=<path>` command
234
  /// line option is set. The writer will be configured to respond to these
235
  /// conditions.
236
  ///
237
  /// This does not configure a default path, so one must be set once it is
238
  /// known that we're in normal mode if none was explicitly provided.
239
  bool ConfigureForCMakeRun(cmake& cm);
240
241
  ~LogFileWriter();
242
243
  /// @brief Check if a valid path is set by opening the output file
244
  /// @return True if the file can be opened for writing
245
  bool EnsureFileValid();
246
247
  /// @brief The possible outcomes of trying to write the log file
248
  enum class WriteResult
249
  {
250
    SUCCESS, ///< File written with no issues
251
    FAILURE, ///< Error encountered while writing the file
252
    SKIPPED, ///< Writing was skipped due to false write condition
253
  };
254
255
  /// @brief Try to write the log file and return `true` if it was written
256
  ///
257
  /// Check the write condition and path generator to determine if the log
258
  /// file should be written.
259
  WriteResult TryWrite();
260
261
  /// @brief Set a lambda to check if the log file should be written
262
  void SetWriteCondition(std::function<bool()> const& checkConditionCallback)
263
1
  {
264
1
    this->WriteCondition = checkConditionCallback;
265
1
  }
266
267
  /// @brief Set the output file path, optionally creating parent directories
268
  ///
269
  /// The settings will apply when the log file is written. If the output
270
  /// file should be checked earlier, use `CheckFileValidity`.
271
  void SetPath(std::string const& path, bool createParentDirectories = false)
272
0
  {
273
0
    this->FilePath = path;
274
0
    this->CreateDirectories = createParentDirectories;
275
0
  }
276
277
private:
278
  ResultsLog const& Log;
279
  std::function<bool()> WriteCondition;
280
  std::string FilePath;
281
  bool CreateDirectories = false;
282
  bool FileWritten = false;
283
};
284
285
} // namespace cmSarif