Skip to content

Commit 01b2618

Browse files
committed
Split checker patch for 18.x
1 parent 3be2122 commit 01b2618

File tree

4 files changed

+311
-282
lines changed

4 files changed

+311
-282
lines changed

llvm/18.x

Submodule 18.x updated from 6a38e64 to 10c16f1

llvm/19.x

Submodule 19.x updated from 379ea04 to 39477c4
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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

Comments
 (0)