| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #pragma once |
| |
| #include <cstddef> |
| #include <functional> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include <cm/filesystem> |
| #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 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(cm::filesystem::path const& path, |
| bool createParentDirectories = false) |
| { |
| this->FilePath = path; |
| this->CreateDirectories = createParentDirectories; |
| } |
| |
| private: |
| ResultsLog const& Log; |
| std::function<bool()> WriteCondition; |
| cm::filesystem::path FilePath; |
| bool CreateDirectories = false; |
| bool FileWritten = false; |
| }; |
| |
| } // namespace cmSarif |