1 | //===--- ClangTidyCheck.cpp - clang-tidy ------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "ClangTidyCheck.h" |
10 | #include "llvm/ADT/StringRef.h" |
11 | #include "llvm/ADT/StringSet.h" |
12 | #include "llvm/Support/YAMLParser.h" |
13 | #include <optional> |
14 | #include <string> |
15 | |
16 | namespace clang::tidy { |
17 | |
18 | ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context) |
19 | : CheckName(CheckName), Context(Context), |
20 | Options(CheckName, Context->getOptions().CheckOptions, Context) { |
21 | assert(Context != nullptr); |
22 | assert(!CheckName.empty()); |
23 | } |
24 | |
25 | DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, |
26 | StringRef Description, |
27 | DiagnosticIDs::Level Level) { |
28 | return Context->diag(CheckName, Loc, Description, Level); |
29 | } |
30 | |
31 | DiagnosticBuilder ClangTidyCheck::diag(StringRef Description, |
32 | DiagnosticIDs::Level Level) { |
33 | return Context->diag(CheckName, Description, Level); |
34 | } |
35 | |
36 | DiagnosticBuilder |
37 | ClangTidyCheck::configurationDiag(StringRef Description, |
38 | DiagnosticIDs::Level Level) const { |
39 | return Context->configurationDiag(Message: Description, Level); |
40 | } |
41 | |
42 | void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) { |
43 | // For historical reasons, checks don't implement the MatchFinder run() |
44 | // callback directly. We keep the run()/check() distinction to avoid interface |
45 | // churn, and to allow us to add cross-cutting logic in the future. |
46 | check(Result); |
47 | } |
48 | |
49 | ClangTidyCheck::OptionsView::OptionsView( |
50 | StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions, |
51 | ClangTidyContext *Context) |
52 | : NamePrefix((CheckName + "." ).str()), CheckOptions(CheckOptions), |
53 | Context(Context) {} |
54 | |
55 | std::optional<StringRef> |
56 | ClangTidyCheck::OptionsView::get(StringRef LocalName) const { |
57 | if (Context->getOptionsCollector()) |
58 | Context->getOptionsCollector()->insert(key: (NamePrefix + LocalName).str()); |
59 | const auto &Iter = CheckOptions.find(Key: (NamePrefix + LocalName).str()); |
60 | if (Iter != CheckOptions.end()) |
61 | return StringRef(Iter->getValue().Value); |
62 | return std::nullopt; |
63 | } |
64 | |
65 | static const llvm::StringSet<> DeprecatedGlobalOptions{ |
66 | "StrictMode" , |
67 | "IgnoreMacros" , |
68 | }; |
69 | |
70 | static ClangTidyOptions::OptionMap::const_iterator |
71 | findPriorityOption(const ClangTidyOptions::OptionMap &Options, |
72 | StringRef NamePrefix, StringRef LocalName, |
73 | ClangTidyContext *Context) { |
74 | llvm::StringSet<> *Collector = Context->getOptionsCollector(); |
75 | if (Collector) { |
76 | Collector->insert(key: (NamePrefix + LocalName).str()); |
77 | Collector->insert(key: LocalName); |
78 | } |
79 | auto IterLocal = Options.find(Key: (NamePrefix + LocalName).str()); |
80 | auto IterGlobal = Options.find(Key: LocalName); |
81 | // FIXME: temporary solution for deprecation warnings, should be removed |
82 | // after 22.x. Warn configuration deps on deprecation global options. |
83 | if (IterLocal == Options.end() && IterGlobal != Options.end() && |
84 | DeprecatedGlobalOptions.contains(key: LocalName)) |
85 | Context->configurationDiag( |
86 | Message: "global option '%0' is deprecated, please use '%1%0' instead." ) |
87 | << LocalName << NamePrefix; |
88 | if (IterLocal == Options.end()) |
89 | return IterGlobal; |
90 | if (IterGlobal == Options.end()) |
91 | return IterLocal; |
92 | if (IterLocal->getValue().Priority >= IterGlobal->getValue().Priority) |
93 | return IterLocal; |
94 | return IterGlobal; |
95 | } |
96 | |
97 | std::optional<StringRef> |
98 | ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const { |
99 | auto Iter = findPriorityOption(Options: CheckOptions, NamePrefix, LocalName, Context); |
100 | if (Iter != CheckOptions.end()) |
101 | return StringRef(Iter->getValue().Value); |
102 | return std::nullopt; |
103 | } |
104 | |
105 | static std::optional<bool> getAsBool(StringRef Value, |
106 | const llvm::Twine &LookupName) { |
107 | |
108 | if (std::optional<bool> Parsed = llvm::yaml::parseBool(S: Value)) |
109 | return Parsed; |
110 | // To maintain backwards compatability, we support parsing numbers as |
111 | // booleans, even though its not supported in YAML. |
112 | long long Number = 0; |
113 | if (!Value.getAsInteger(Radix: 10, Result&: Number)) |
114 | return Number != 0; |
115 | return std::nullopt; |
116 | } |
117 | |
118 | template <> |
119 | std::optional<bool> |
120 | ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const { |
121 | if (std::optional<StringRef> ValueOr = get(LocalName)) { |
122 | if (auto Result = getAsBool(Value: *ValueOr, LookupName: NamePrefix + LocalName)) |
123 | return Result; |
124 | diagnoseBadBooleanOption(Lookup: NamePrefix + LocalName, Unparsed: *ValueOr); |
125 | } |
126 | return std::nullopt; |
127 | } |
128 | |
129 | template <> |
130 | std::optional<bool> |
131 | ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const { |
132 | auto Iter = findPriorityOption(Options: CheckOptions, NamePrefix, LocalName, Context); |
133 | if (Iter != CheckOptions.end()) { |
134 | if (auto Result = getAsBool(Value: Iter->getValue().Value, LookupName: Iter->getKey())) |
135 | return Result; |
136 | diagnoseBadBooleanOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value); |
137 | } |
138 | return std::nullopt; |
139 | } |
140 | |
141 | void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options, |
142 | StringRef LocalName, |
143 | StringRef Value) const { |
144 | Options[(NamePrefix + LocalName).str()] = Value; |
145 | } |
146 | |
147 | void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options, |
148 | StringRef LocalName, |
149 | int64_t Value) const { |
150 | store(Options, LocalName, Value: llvm::itostr(X: Value)); |
151 | } |
152 | |
153 | void ClangTidyCheck::OptionsView::storeUnsigned( |
154 | ClangTidyOptions::OptionMap &Options, StringRef LocalName, |
155 | uint64_t Value) const { |
156 | store(Options, LocalName, Value: llvm::utostr(X: Value)); |
157 | } |
158 | |
159 | template <> |
160 | void ClangTidyCheck::OptionsView::store<bool>( |
161 | ClangTidyOptions::OptionMap &Options, StringRef LocalName, |
162 | bool Value) const { |
163 | store(Options, LocalName, Value: Value ? StringRef("true" ) : StringRef("false" )); |
164 | } |
165 | |
166 | std::optional<int64_t> |
167 | ClangTidyCheck::OptionsView::getEnumInt(StringRef LocalName, |
168 | ArrayRef<NameAndValue> Mapping, |
169 | bool CheckGlobal) const { |
170 | if (!CheckGlobal && Context->getOptionsCollector()) |
171 | Context->getOptionsCollector()->insert(key: (NamePrefix + LocalName).str()); |
172 | auto Iter = CheckGlobal ? findPriorityOption(Options: CheckOptions, NamePrefix, |
173 | LocalName, Context) |
174 | : CheckOptions.find(Key: (NamePrefix + LocalName).str()); |
175 | if (Iter == CheckOptions.end()) |
176 | return std::nullopt; |
177 | |
178 | StringRef Value = Iter->getValue().Value; |
179 | StringRef Closest; |
180 | unsigned EditDistance = 3; |
181 | for (const auto &NameAndEnum : Mapping) { |
182 | if (Value == NameAndEnum.second) { |
183 | return NameAndEnum.first; |
184 | } |
185 | if (Value.equals_insensitive(RHS: NameAndEnum.second)) { |
186 | Closest = NameAndEnum.second; |
187 | EditDistance = 0; |
188 | continue; |
189 | } |
190 | unsigned Distance = |
191 | Value.edit_distance(Other: NameAndEnum.second, AllowReplacements: true, MaxEditDistance: EditDistance); |
192 | if (Distance < EditDistance) { |
193 | EditDistance = Distance; |
194 | Closest = NameAndEnum.second; |
195 | } |
196 | } |
197 | if (EditDistance < 3) |
198 | diagnoseBadEnumOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value, Suggestion: Closest); |
199 | else |
200 | diagnoseBadEnumOption(Lookup: Iter->getKey(), Unparsed: Iter->getValue().Value); |
201 | return std::nullopt; |
202 | } |
203 | |
204 | static constexpr llvm::StringLiteral ConfigWarning( |
205 | "invalid configuration value '%0' for option '%1'%select{|; expected a " |
206 | "bool|; expected an integer|; did you mean '%3'?}2" ); |
207 | |
208 | void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption( |
209 | const Twine &Lookup, StringRef Unparsed) const { |
210 | SmallString<64> Buffer; |
211 | Context->configurationDiag(Message: ConfigWarning) |
212 | << Unparsed << Lookup.toStringRef(Out&: Buffer) << 1; |
213 | } |
214 | |
215 | void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption( |
216 | const Twine &Lookup, StringRef Unparsed) const { |
217 | SmallString<64> Buffer; |
218 | Context->configurationDiag(Message: ConfigWarning) |
219 | << Unparsed << Lookup.toStringRef(Out&: Buffer) << 2; |
220 | } |
221 | |
222 | void ClangTidyCheck::OptionsView::diagnoseBadEnumOption( |
223 | const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const { |
224 | SmallString<64> Buffer; |
225 | auto Diag = Context->configurationDiag(Message: ConfigWarning) |
226 | << Unparsed << Lookup.toStringRef(Out&: Buffer); |
227 | if (Suggestion.empty()) |
228 | Diag << 0; |
229 | else |
230 | Diag << 3 << Suggestion; |
231 | } |
232 | |
233 | StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName, |
234 | StringRef Default) const { |
235 | return get(LocalName).value_or(u&: Default); |
236 | } |
237 | |
238 | StringRef |
239 | ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName, |
240 | StringRef Default) const { |
241 | return getLocalOrGlobal(LocalName).value_or(u&: Default); |
242 | } |
243 | } // namespace clang::tidy |
244 | |