/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 |