xref: /llvm-project/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp (revision ae9bf17697d2245be707e93125f18d09eaf77aa9)
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(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((NamePrefix + LocalName).str());
59   const auto &Iter = CheckOptions.find((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((NamePrefix + LocalName).str());
77     Collector->insert(LocalName);
78   }
79   auto IterLocal = Options.find((NamePrefix + LocalName).str());
80   auto IterGlobal = Options.find(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(LocalName))
85     Context->configurationDiag(
86         "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(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(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(10, 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(*ValueOr, NamePrefix + LocalName))
123       return Result;
124     diagnoseBadBooleanOption(NamePrefix + LocalName, *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(CheckOptions, NamePrefix, LocalName, Context);
133   if (Iter != CheckOptions.end()) {
134     if (auto Result = getAsBool(Iter->getValue().Value, Iter->getKey()))
135       return Result;
136     diagnoseBadBooleanOption(Iter->getKey(), 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, llvm::itostr(Value));
151 }
152 
153 void ClangTidyCheck::OptionsView::storeUnsigned(
154     ClangTidyOptions::OptionMap &Options, StringRef LocalName,
155     uint64_t Value) const {
156   store(Options, LocalName, llvm::utostr(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 ? 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((NamePrefix + LocalName).str());
172   auto Iter = CheckGlobal ? findPriorityOption(CheckOptions, NamePrefix,
173                                                LocalName, Context)
174                           : CheckOptions.find((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(NameAndEnum.second)) {
186       Closest = NameAndEnum.second;
187       EditDistance = 0;
188       continue;
189     }
190     unsigned Distance =
191         Value.edit_distance(NameAndEnum.second, true, EditDistance);
192     if (Distance < EditDistance) {
193       EditDistance = Distance;
194       Closest = NameAndEnum.second;
195     }
196   }
197   if (EditDistance < 3)
198     diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value, Closest);
199   else
200     diagnoseBadEnumOption(Iter->getKey(), 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(ConfigWarning)
212       << Unparsed << Lookup.toStringRef(Buffer) << 1;
213 }
214 
215 void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption(
216     const Twine &Lookup, StringRef Unparsed) const {
217   SmallString<64> Buffer;
218   Context->configurationDiag(ConfigWarning)
219       << Unparsed << Lookup.toStringRef(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(ConfigWarning)
226               << Unparsed << Lookup.toStringRef(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(Default);
236 }
237 
238 StringRef
239 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
240                                               StringRef Default) const {
241   return getLocalOrGlobal(LocalName).value_or(Default);
242 }
243 } // namespace clang::tidy
244