|
| 1 | +From 74b9c97550a4fd100fc01a317891b90c502db8e9 Mon Sep 17 00:00:00 2001 |
| 2 | +From: June Rhodes < [email protected]> |
| 3 | +Date: Thu, 19 Feb 2026 18:42:30 +1100 |
| 4 | +Subject: [PATCH] Implement checker API for advanced analysis |
| 5 | + |
| 6 | +--- |
| 7 | + .../clang/Basic/DiagnosticCommonKinds.td | 2 + |
| 8 | + clang/lib/Frontend/ClangRulesets.cpp | 138 +++++++++++++++++- |
| 9 | + .../Frontend/ClangRulesetsCheckerBase.impl.h | 42 ++++++ |
| 10 | + 3 files changed, 174 insertions(+), 8 deletions(-) |
| 11 | + create mode 100644 clang/lib/Frontend/ClangRulesetsCheckerBase.impl.h |
| 12 | + |
| 13 | +diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td |
| 14 | +index 8bd5ffc219d6..c4f6ebe490a5 100644 |
| 15 | +--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td |
| 16 | ++++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td |
| 17 | +@@ -426,6 +426,8 @@ def err_clangrules_ruleset_severity_is_notset : Error<"ruleset '%0' severity mus |
| 18 | + def err_clangrules_rule_name_conflict : Error<"namespaced rule name '%0' is already declared in another .clang-rules file; try using a different namespace to avoid conflicts">; |
| 19 | + def err_clangrules_rule_missing : Error<"namespaced ruleset '%0' requested namespaced rule '%1' but it wasn't known at runtime; make sure it is declared in either the same .clang-rules file, or in a .clang-rules file in a parent directory">; |
| 20 | + def err_clangrules_rule_matcher_parse_failure : Error<"namespaced rule '%0' matcher failed to parse: %1">; |
| 21 | ++def err_clangrules_rule_checker_not_found : Error<"namespaced rule '%0' checker is unknown: %1">; |
| 22 | ++def err_clangrules_rule_not_one_matcher_or_checker : Error<"namespaced rule '%0' must have exactly one of 'Matcher' and 'Checker'">; |
| 23 | + |
| 24 | + // API notes |
| 25 | + def err_apinotes_message : Error<"%0">; |
| 26 | +diff --git a/clang/lib/Frontend/ClangRulesets.cpp b/clang/lib/Frontend/ClangRulesets.cpp |
| 27 | +index 7f263c91bd32..d413e7fbbbac 100644 |
| 28 | +--- a/clang/lib/Frontend/ClangRulesets.cpp |
| 29 | ++++ b/clang/lib/Frontend/ClangRulesets.cpp |
| 30 | +@@ -17,6 +17,8 @@ |
| 31 | + #include "llvm/Support/Windows/WindowsSupport.h" |
| 32 | + #endif |
| 33 | + |
| 34 | ++#include "ClangRulesetsCheckerBase.impl.h" |
| 35 | ++ |
| 36 | + using namespace clang; |
| 37 | + |
| 38 | + #define RULESET_ENABLE_TIMING 0 |
| 39 | +@@ -238,6 +240,9 @@ struct ClangRulesRule { |
| 40 | + /// The Clang AST matcher to evaluate against top-level declarations in the |
| 41 | + /// AST. |
| 42 | + std::string Matcher; |
| 43 | ++ /// The Clang rulesets custom checker that should be used for this rule |
| 44 | ++ /// instead of the matcher. |
| 45 | ++ std::string Checker; |
| 46 | + /// Specifies the traversal mode for the AST matcher. |
| 47 | + ClangRulesTraversalMode TraversalMode; |
| 48 | + /// The compiler diagnostic message to emit at the callsite when this static |
| 49 | +@@ -253,6 +258,9 @@ struct ClangRulesRule { |
| 50 | + /// The runtime matcher value after the matcher expression has been parsed and |
| 51 | + /// loaded by Clang. |
| 52 | + std::optional<clang::ast_matchers::internal::DynTypedMatcher> MatcherParsed; |
| 53 | ++ /// The runtime checker value after the checker parameter has been parsed and |
| 54 | ++ /// loaded by Clang. |
| 55 | ++ std::optional<ClangRulesetsCheckerBase*> CheckerParsed; |
| 56 | + /// If true, this rule only applies when the compilation triple targets |
| 57 | + /// Windows. This can be used for rules which match on Windows-specific AST |
| 58 | + /// nodes (such as '__declspec(dllexport)'). |
| 59 | +@@ -400,11 +408,12 @@ struct ScalarEnumerationTraits< |
| 60 | + template <> struct MappingTraits<clang::rulesets::config::ClangRulesRule> { |
| 61 | + static void mapping(IO &IO, clang::rulesets::config::ClangRulesRule &Rule) { |
| 62 | + IO.mapRequired("Name", Rule.Name); |
| 63 | +- IO.mapRequired("Matcher", Rule.Matcher); |
| 64 | ++ IO.mapOptional("Matcher", Rule.Matcher); |
| 65 | ++ IO.mapOptional("Checker", Rule.Checker); |
| 66 | + IO.mapOptional("TraversalMode", Rule.TraversalMode, |
| 67 | + clang::rulesets::config::ClangRulesTraversalMode::CRTM_AsIs); |
| 68 | +- IO.mapRequired("ErrorMessage", Rule.ErrorMessage); |
| 69 | +- IO.mapRequired("Callsite", Rule.Callsite); |
| 70 | ++ IO.mapOptional("ErrorMessage", Rule.ErrorMessage); |
| 71 | ++ IO.mapOptional("Callsite", Rule.Callsite); |
| 72 | + IO.mapOptional("Hints", Rule.Hints); |
| 73 | + IO.mapOptional("WindowsOnly", Rule.WindowsOnly, false); |
| 74 | + IO.mapOptional("Debug", Rule.Debug, false); |
| 75 | +@@ -477,6 +486,22 @@ template <> struct MappingTraits<clang::rulesets::config::ClangRules> { |
| 76 | + |
| 77 | + namespace clang::rulesets { |
| 78 | + |
| 79 | ++template<typename T> |
| 80 | ++static std::pair<std::string, std::unique_ptr<ClangRulesetsCheckerBase>> createCheckerDefinition() { |
| 81 | ++ |
| 82 | ++} |
| 83 | ++ |
| 84 | ++static std::map<std::string, std::unique_ptr<ClangRulesetsCheckerBase>> CheckerDefinitions; |
| 85 | ++ |
| 86 | ++template<typename T> |
| 87 | ++class CheckerDefinitionRegister { |
| 88 | ++public: |
| 89 | ++ CheckerDefinitionRegister() { |
| 90 | ++ T* Instance = new T(); |
| 91 | ++ CheckerDefinitions[Instance->getName()] = std::unique_ptr<ClangRulesetsCheckerBase>(Instance); |
| 92 | ++ } |
| 93 | ++}; |
| 94 | ++ |
| 95 | + struct ClangRulesetsEffectiveRule { |
| 96 | + // Pointer to memory inside a loaded config::ClangRules. |
| 97 | + config::ClangRulesRule *Rule; |
| 98 | +@@ -1271,6 +1296,7 @@ private: |
| 99 | + } |
| 100 | + |
| 101 | + // Attempt to parse the matcher expression. |
| 102 | ++ if (!Rule.Matcher.empty()) |
| 103 | + { |
| 104 | + clang::ast_matchers::dynamic::Diagnostics ParseDiag; |
| 105 | + llvm::StringRef MatcherRef(Rule.Matcher); |
| 106 | +@@ -1295,6 +1321,31 @@ private: |
| 107 | + // MatcherParsed in this case. |
| 108 | + } |
| 109 | + } |
| 110 | ++ |
| 111 | ++ // Attempt to parse the checker value. |
| 112 | ++ if (!Rule.Checker.empty()) { |
| 113 | ++ if (auto FoundChecker = CheckerDefinitions.find(Rule.Checker); FoundChecker != CheckerDefinitions.end()) { |
| 114 | ++ Rule.CheckerParsed = FoundChecker->second.get(); |
| 115 | ++ } else { |
| 116 | ++ SrcMgr.getDiagnostics().Report( |
| 117 | ++ SrcMgr.getLocForStartOfFile(FileID), |
| 118 | ++ diag::err_clangrules_rule_checker_not_found) |
| 119 | ++ << NamespacedName << Rule.Checker; |
| 120 | ++ StillValid = false; |
| 121 | ++ continue; |
| 122 | ++ } |
| 123 | ++ } |
| 124 | ++ |
| 125 | ++ // Make sure we either have a matcher or checker. |
| 126 | ++ if ((!Rule.MatcherParsed.has_value() && !Rule.CheckerParsed.has_value()) || |
| 127 | ++ (Rule.MatcherParsed.has_value() && Rule.CheckerParsed.has_value())) { |
| 128 | ++ SrcMgr.getDiagnostics().Report( |
| 129 | ++ SrcMgr.getLocForStartOfFile(FileID), |
| 130 | ++ diag::err_clangrules_rule_not_one_matcher_or_checker) |
| 131 | ++ << NamespacedName; |
| 132 | ++ StillValid = false; |
| 133 | ++ continue; |
| 134 | ++ } |
| 135 | + } |
| 136 | + |
| 137 | + // Go through rulesets and namespace both their name and any unprefixed |
| 138 | +@@ -1546,11 +1597,20 @@ private: |
| 139 | + // and generate diagnostic IDs so that code can use pragmas to control |
| 140 | + // them. |
| 141 | + for (const auto &EffectiveRule : EffectiveConfig->EffectiveRules) { |
| 142 | +- this->CI.getDiagnostics().getDiagnosticIDs()->getCustomDiagID( |
| 143 | +- convertDiagnosticLevel(EffectiveRule.second.Severity), |
| 144 | +- (EffectiveRule.second.Rule->ErrorMessage + " [-W" + |
| 145 | +- EffectiveRule.second.Rule->Name + "]"), |
| 146 | +- EffectiveRule.second.Rule->Name); |
| 147 | ++ if (EffectiveRule.second.Rule->Checker.empty()) { |
| 148 | ++ this->CI.getDiagnostics().getDiagnosticIDs()->getCustomDiagID( |
| 149 | ++ convertDiagnosticLevel(EffectiveRule.second.Severity), |
| 150 | ++ (EffectiveRule.second.Rule->ErrorMessage + " [-W" + |
| 151 | ++ EffectiveRule.second.Rule->Name + "]"), |
| 152 | ++ EffectiveRule.second.Rule->Name); |
| 153 | ++ } else { |
| 154 | ++ // Checkers always take the message as an argument to the diagnostic via %0. |
| 155 | ++ this->CI.getDiagnostics().getDiagnosticIDs()->getCustomDiagID( |
| 156 | ++ convertDiagnosticLevel(EffectiveRule.second.Severity), |
| 157 | ++ ("%0 [-W" + |
| 158 | ++ EffectiveRule.second.Rule->Name + "]"), |
| 159 | ++ EffectiveRule.second.Rule->Name); |
| 160 | ++ } |
| 161 | + } |
| 162 | + |
| 163 | + // Otherwise, this is the effective config for this directory. |
| 164 | +@@ -1585,6 +1645,7 @@ private: |
| 165 | + |
| 166 | + class InstantiatedMatcher { |
| 167 | + private: |
| 168 | ++ |
| 169 | + class InstantiatedMatcherCallback |
| 170 | + : public clang::ast_matchers::MatchFinder::MatchCallback { |
| 171 | + private: |
| 172 | +@@ -1672,6 +1733,58 @@ private: |
| 173 | + } |
| 174 | + }; |
| 175 | + |
| 176 | ++ class InstantiatedCheckerCallback |
| 177 | ++ : public clang::ast_matchers::MatchFinder::MatchCallback { |
| 178 | ++ private: |
| 179 | ++ llvm::sys::SmartMutex<true> &Mutex; |
| 180 | ++ ASTContext &AST; |
| 181 | ++ const ClangRulesetsEffectiveRule &EffectiveRule; |
| 182 | ++ |
| 183 | ++ public: |
| 184 | ++ InstantiatedCheckerCallback( |
| 185 | ++ llvm::sys::SmartMutex<true> &InMutex, ASTContext &InAST, |
| 186 | ++ const ClangRulesetsEffectiveRule &InEffectiveRule) |
| 187 | ++ : Mutex(InMutex), AST(InAST), EffectiveRule(InEffectiveRule){}; |
| 188 | ++ |
| 189 | ++ virtual void run(const clang::ast_matchers::MatchFinder::MatchResult |
| 190 | ++ &Result) override { |
| 191 | ++ this->EffectiveRule.Rule->CheckerParsed.value()->processMatchResult( |
| 192 | ++ this->AST, |
| 193 | ++ Result, |
| 194 | ++ [this](const std::vector<ClangRulesetsCheckerDiagnostic>& Diagnostics) { |
| 195 | ++ if (Diagnostics.size() == 0) { |
| 196 | ++ return; |
| 197 | ++ } |
| 198 | ++ |
| 199 | ++ // Obtain lock. |
| 200 | ++ this->Mutex.lock(); |
| 201 | ++ |
| 202 | ++ // Emit reported diagnostics. |
| 203 | ++ for (const auto& Diagnostic : Diagnostics) { |
| 204 | ++ if (!Diagnostic.IsNote) { |
| 205 | ++ clang::DiagnosticIDs::Level DiagnosticLevel = |
| 206 | ++ convertDiagnosticLevel(this->EffectiveRule.Severity); |
| 207 | ++ auto CallsiteDiagID = |
| 208 | ++ this->AST.getDiagnostics().getDiagnosticIDs()->getExistingCustomDiagID( |
| 209 | ++ this->EffectiveRule.Rule->Name, DiagnosticLevel); |
| 210 | ++ assert( |
| 211 | ++ CallsiteDiagID |
| 212 | ++ .has_value() /* expected diagnostics to have been created */); |
| 213 | ++ this->AST.getDiagnostics().Report(Diagnostic.Location, CallsiteDiagID.value()) << Diagnostic.Message; |
| 214 | ++ } else { |
| 215 | ++ auto HintDiagID = |
| 216 | ++ this->AST.getDiagnostics().getDiagnosticIDs()->getCustomDiagID( |
| 217 | ++ clang::DiagnosticIDs::Note, Diagnostic.Message); |
| 218 | ++ this->AST.getDiagnostics().Report(Diagnostic.Location, HintDiagID); |
| 219 | ++ } |
| 220 | ++ } |
| 221 | ++ |
| 222 | ++ // Release lock. |
| 223 | ++ this->Mutex.unlock(); |
| 224 | ++ }); |
| 225 | ++ } |
| 226 | ++ }; |
| 227 | ++ |
| 228 | + std::unique_ptr<ast_matchers::MatchFinder> Finder; |
| 229 | + llvm::DenseMap<const ClangRulesetsEffectiveRule *, |
| 230 | + clang::ast_matchers::MatchFinder::MatchCallback *> |
| 231 | +@@ -1703,6 +1816,15 @@ private: |
| 232 | + this->Finder->addDynamicMatcher(*Rule->MatcherParsed, Callback); |
| 233 | + this->Callbacks[&EffectiveRule] = Callback; |
| 234 | + } |
| 235 | ++ else if (Rule->CheckerParsed.has_value()) { |
| 236 | ++ auto *Callback = new InstantiatedCheckerCallback(this->Mutex, this->AST, |
| 237 | ++ EffectiveRule); |
| 238 | ++ RULESET_TRACE_RULESET("Adding dynamic checker to finder.\n"); |
| 239 | ++ Rule->CheckerParsed.value()->registerMatcherWithFinder( |
| 240 | ++ *this->Finder, |
| 241 | ++ Callback); |
| 242 | ++ this->Callbacks[&EffectiveRule] = Callback; |
| 243 | ++ } |
| 244 | + } |
| 245 | + |
| 246 | + void match(const OptionalFileEntryRef &FileEntry, clang::Decl *Decl) { |
| 247 | +diff --git a/clang/lib/Frontend/ClangRulesetsCheckerBase.impl.h b/clang/lib/Frontend/ClangRulesetsCheckerBase.impl.h |
| 248 | +new file mode 100644 |
| 249 | +index 000000000000..808e91a9d414 |
| 250 | +--- /dev/null |
| 251 | ++++ b/clang/lib/Frontend/ClangRulesetsCheckerBase.impl.h |
| 252 | +@@ -0,0 +1,42 @@ |
| 253 | ++#pragma once |
| 254 | ++ |
| 255 | ++#include "clang/AST/ASTConsumer.h" |
| 256 | ++#include "clang/Frontend/CompilerInstance.h" |
| 257 | ++ |
| 258 | ++namespace clang::rulesets { |
| 259 | ++ |
| 260 | ++struct ClangRulesetsCheckerDiagnostic { |
| 261 | ++public: |
| 262 | ++ ClangRulesetsCheckerDiagnostic( |
| 263 | ++ const clang::SourceLocation& InLocation, |
| 264 | ++ bool InIsNote, |
| 265 | ++ llvm::StringRef InMessage) |
| 266 | ++ : Location(InLocation) |
| 267 | ++ , IsNote(InIsNote) |
| 268 | ++ , Message(InMessage) |
| 269 | ++ { |
| 270 | ++ } |
| 271 | ++ |
| 272 | ++ clang::SourceLocation Location; |
| 273 | ++ bool IsNote; |
| 274 | ++ llvm::StringRef Message; |
| 275 | ++}; |
| 276 | ++ |
| 277 | ++using ClangRulesetsCheckerReportDiagnostics = std::function<void( |
| 278 | ++ const std::vector<ClangRulesetsCheckerDiagnostic>& Diagnostics)>; |
| 279 | ++ |
| 280 | ++class ClangRulesetsCheckerBase { |
| 281 | ++public: |
| 282 | ++ virtual ~ClangRulesetsCheckerBase() = default; |
| 283 | ++ |
| 284 | ++ virtual std::string getName() const = 0; |
| 285 | ++ |
| 286 | ++ virtual void registerMatcherWithFinder(clang::ast_matchers::MatchFinder& Finder, clang::ast_matchers::MatchFinder::MatchCallback *Action) const = 0; |
| 287 | ++ |
| 288 | ++ virtual void processMatchResult( |
| 289 | ++ ASTContext &AST, |
| 290 | ++ const clang::ast_matchers::MatchFinder::MatchResult &Result, |
| 291 | ++ const ClangRulesetsCheckerReportDiagnostics& ReportDiagnostics) const = 0; |
| 292 | ++}; |
| 293 | ++ |
| 294 | ++} // namespace clang::rulesets |
| 295 | +-- |
| 296 | +2.50.1.windows.1 |
| 297 | + |
0 commit comments