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/SmallString.h"
11 #include "llvm/ADT/StringRef.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/YAMLParser.h"
14 
15 namespace clang {
16 namespace tidy {
17 
ClangTidyCheck(StringRef CheckName,ClangTidyContext * Context)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 
diag(SourceLocation Loc,StringRef Message,DiagnosticIDs::Level Level)25 DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, StringRef Message,
26                                        DiagnosticIDs::Level Level) {
27   return Context->diag(CheckName, Loc, Message, Level);
28 }
29 
diag(StringRef Message,DiagnosticIDs::Level Level)30 DiagnosticBuilder ClangTidyCheck::diag(StringRef Message,
31                                        DiagnosticIDs::Level Level) {
32   return Context->diag(CheckName, Message, Level);
33 }
34 
35 DiagnosticBuilder
configurationDiag(StringRef Description,DiagnosticIDs::Level Level) const36 ClangTidyCheck::configurationDiag(StringRef Description,
37                                   DiagnosticIDs::Level Level) const {
38   return Context->configurationDiag(Description, Level);
39 }
40 
run(const ast_matchers::MatchFinder::MatchResult & Result)41 void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) {
42   // For historical reasons, checks don't implement the MatchFinder run()
43   // callback directly. We keep the run()/check() distinction to avoid interface
44   // churn, and to allow us to add cross-cutting logic in the future.
45   check(Result);
46 }
47 
OptionsView(StringRef CheckName,const ClangTidyOptions::OptionMap & CheckOptions,ClangTidyContext * Context)48 ClangTidyCheck::OptionsView::OptionsView(
49     StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions,
50     ClangTidyContext *Context)
51     : NamePrefix((CheckName + ".").str()), CheckOptions(CheckOptions),
52       Context(Context) {}
53 
54 llvm::Optional<StringRef>
get(StringRef LocalName) const55 ClangTidyCheck::OptionsView::get(StringRef LocalName) const {
56   if (Context->getOptionsCollector())
57     Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());
58   const auto &Iter = CheckOptions.find((NamePrefix + LocalName).str());
59   if (Iter != CheckOptions.end())
60     return StringRef(Iter->getValue().Value);
61   return None;
62 }
63 
64 static ClangTidyOptions::OptionMap::const_iterator
findPriorityOption(const ClangTidyOptions::OptionMap & Options,StringRef NamePrefix,StringRef LocalName,llvm::StringSet<> * Collector)65 findPriorityOption(const ClangTidyOptions::OptionMap &Options,
66                    StringRef NamePrefix, StringRef LocalName,
67                    llvm::StringSet<> *Collector) {
68   if (Collector) {
69     Collector->insert((NamePrefix + LocalName).str());
70     Collector->insert(LocalName);
71   }
72   auto IterLocal = Options.find((NamePrefix + LocalName).str());
73   auto IterGlobal = Options.find(LocalName);
74   if (IterLocal == Options.end())
75     return IterGlobal;
76   if (IterGlobal == Options.end())
77     return IterLocal;
78   if (IterLocal->getValue().Priority >= IterGlobal->getValue().Priority)
79     return IterLocal;
80   return IterGlobal;
81 }
82 
83 llvm::Optional<StringRef>
getLocalOrGlobal(StringRef LocalName) const84 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const {
85   auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName,
86                                  Context->getOptionsCollector());
87   if (Iter != CheckOptions.end())
88     return StringRef(Iter->getValue().Value);
89   return None;
90 }
91 
getAsBool(StringRef Value,const llvm::Twine & LookupName)92 static Optional<bool> getAsBool(StringRef Value,
93                                 const llvm::Twine &LookupName) {
94 
95   if (llvm::Optional<bool> Parsed = llvm::yaml::parseBool(Value))
96     return *Parsed;
97   // To maintain backwards compatability, we support parsing numbers as
98   // booleans, even though its not supported in YAML.
99   long long Number;
100   if (!Value.getAsInteger(10, Number))
101     return Number != 0;
102   return None;
103 }
104 
105 template <>
106 llvm::Optional<bool>
get(StringRef LocalName) const107 ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const {
108   if (llvm::Optional<StringRef> ValueOr = get(LocalName)) {
109     if (auto Result = getAsBool(*ValueOr, NamePrefix + LocalName))
110       return Result;
111     diagnoseBadBooleanOption(NamePrefix + LocalName, *ValueOr);
112   }
113   return None;
114 }
115 
116 template <>
117 llvm::Optional<bool>
getLocalOrGlobal(StringRef LocalName) const118 ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const {
119   auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName,
120                                  Context->getOptionsCollector());
121   if (Iter != CheckOptions.end()) {
122     if (auto Result = getAsBool(Iter->getValue().Value, Iter->getKey()))
123       return Result;
124     diagnoseBadBooleanOption(Iter->getKey(), Iter->getValue().Value);
125   }
126   return None;
127 }
128 
store(ClangTidyOptions::OptionMap & Options,StringRef LocalName,StringRef Value) const129 void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
130                                         StringRef LocalName,
131                                         StringRef Value) const {
132   Options[(NamePrefix + LocalName).str()] = Value;
133 }
134 
storeInt(ClangTidyOptions::OptionMap & Options,StringRef LocalName,int64_t Value) const135 void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options,
136                                            StringRef LocalName,
137                                            int64_t Value) const {
138   store(Options, LocalName, llvm::itostr(Value));
139 }
140 
141 template <>
store(ClangTidyOptions::OptionMap & Options,StringRef LocalName,bool Value) const142 void ClangTidyCheck::OptionsView::store<bool>(
143     ClangTidyOptions::OptionMap &Options, StringRef LocalName,
144     bool Value) const {
145   store(Options, LocalName, Value ? StringRef("true") : StringRef("false"));
146 }
147 
getEnumInt(StringRef LocalName,ArrayRef<NameAndValue> Mapping,bool CheckGlobal,bool IgnoreCase) const148 llvm::Optional<int64_t> ClangTidyCheck::OptionsView::getEnumInt(
149     StringRef LocalName, ArrayRef<NameAndValue> Mapping, bool CheckGlobal,
150     bool IgnoreCase) const {
151   if (!CheckGlobal && Context->getOptionsCollector())
152     Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());
153   auto Iter = CheckGlobal
154                   ? findPriorityOption(CheckOptions, NamePrefix, LocalName,
155                                        Context->getOptionsCollector())
156                   : CheckOptions.find((NamePrefix + LocalName).str());
157   if (Iter == CheckOptions.end())
158     return None;
159 
160   StringRef Value = Iter->getValue().Value;
161   StringRef Closest;
162   unsigned EditDistance = 3;
163   for (const auto &NameAndEnum : Mapping) {
164     if (IgnoreCase) {
165       if (Value.equals_insensitive(NameAndEnum.second))
166         return NameAndEnum.first;
167     } else if (Value.equals(NameAndEnum.second)) {
168       return NameAndEnum.first;
169     } else if (Value.equals_insensitive(NameAndEnum.second)) {
170       Closest = NameAndEnum.second;
171       EditDistance = 0;
172       continue;
173     }
174     unsigned Distance =
175         Value.edit_distance(NameAndEnum.second, true, EditDistance);
176     if (Distance < EditDistance) {
177       EditDistance = Distance;
178       Closest = NameAndEnum.second;
179     }
180   }
181   if (EditDistance < 3)
182     diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value, Closest);
183   else
184     diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value);
185   return None;
186 }
187 
188 static constexpr llvm::StringLiteral ConfigWarning(
189     "invalid configuration value '%0' for option '%1'%select{|; expected a "
190     "bool|; expected an integer|; did you mean '%3'?}2");
191 
diagnoseBadBooleanOption(const Twine & Lookup,StringRef Unparsed) const192 void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption(
193     const Twine &Lookup, StringRef Unparsed) const {
194   SmallString<64> Buffer;
195   Context->configurationDiag(ConfigWarning)
196       << Unparsed << Lookup.toStringRef(Buffer) << 1;
197 }
198 
diagnoseBadIntegerOption(const Twine & Lookup,StringRef Unparsed) const199 void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption(
200     const Twine &Lookup, StringRef Unparsed) const {
201   SmallString<64> Buffer;
202   Context->configurationDiag(ConfigWarning)
203       << Unparsed << Lookup.toStringRef(Buffer) << 2;
204 }
205 
diagnoseBadEnumOption(const Twine & Lookup,StringRef Unparsed,StringRef Suggestion) const206 void ClangTidyCheck::OptionsView::diagnoseBadEnumOption(
207     const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const {
208   SmallString<64> Buffer;
209   auto Diag = Context->configurationDiag(ConfigWarning)
210               << Unparsed << Lookup.toStringRef(Buffer);
211   if (Suggestion.empty())
212     Diag << 0;
213   else
214     Diag << 3 << Suggestion;
215 }
216 
get(StringRef LocalName,StringRef Default) const217 StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName,
218                                            StringRef Default) const {
219   return get(LocalName).value_or(Default);
220 }
221 
222 StringRef
getLocalOrGlobal(StringRef LocalName,StringRef Default) const223 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
224                                               StringRef Default) const {
225   return getLocalOrGlobal(LocalName).value_or(Default);
226 }
227 } // namespace tidy
228 } // namespace clang
229