xref: /llvm-project/clang-tools-extra/clang-tidy/ClangTidyCheck.cpp (revision ae9bf17697d2245be707e93125f18d09eaf77aa9)
1e9087fe7SAlexander Kornienko //===--- ClangTidyCheck.cpp - clang-tidy ------------------------*- C++ -*-===//
2e9087fe7SAlexander Kornienko //
3e9087fe7SAlexander Kornienko // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e9087fe7SAlexander Kornienko // See https://llvm.org/LICENSE.txt for license information.
5e9087fe7SAlexander Kornienko // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e9087fe7SAlexander Kornienko //
7e9087fe7SAlexander Kornienko //===----------------------------------------------------------------------===//
8e9087fe7SAlexander Kornienko 
9e9087fe7SAlexander Kornienko #include "ClangTidyCheck.h"
10fcf7cc26SNathan James #include "llvm/ADT/StringRef.h"
1142dfaa15SCongcong Cai #include "llvm/ADT/StringSet.h"
1268e642caSNathan James #include "llvm/Support/YAMLParser.h"
1371f55735SKazu Hirata #include <optional>
1442dfaa15SCongcong Cai #include <string>
15e9087fe7SAlexander Kornienko 
167d2ea6c4SCarlos Galvez namespace clang::tidy {
17e9087fe7SAlexander Kornienko 
18e9087fe7SAlexander Kornienko ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context)
19e9087fe7SAlexander Kornienko     : CheckName(CheckName), Context(Context),
2027553933SNathan James       Options(CheckName, Context->getOptions().CheckOptions, Context) {
21e9087fe7SAlexander Kornienko   assert(Context != nullptr);
22e9087fe7SAlexander Kornienko   assert(!CheckName.empty());
23e9087fe7SAlexander Kornienko }
24e9087fe7SAlexander Kornienko 
25f1f16331SPiotr Zegar DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc,
26f1f16331SPiotr Zegar                                        StringRef Description,
27e9087fe7SAlexander Kornienko                                        DiagnosticIDs::Level Level) {
28f1f16331SPiotr Zegar   return Context->diag(CheckName, Loc, Description, Level);
29e9087fe7SAlexander Kornienko }
30e9087fe7SAlexander Kornienko 
31f1f16331SPiotr Zegar DiagnosticBuilder ClangTidyCheck::diag(StringRef Description,
3227553933SNathan James                                        DiagnosticIDs::Level Level) {
33f1f16331SPiotr Zegar   return Context->diag(CheckName, Description, Level);
3427553933SNathan James }
3527553933SNathan James 
3627553933SNathan James DiagnosticBuilder
3727553933SNathan James ClangTidyCheck::configurationDiag(StringRef Description,
382b9b5bc0SDouglas Chen                                   DiagnosticIDs::Level Level) const {
3927553933SNathan James   return Context->configurationDiag(Description, Level);
4027553933SNathan James }
4127553933SNathan James 
42e9087fe7SAlexander Kornienko void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) {
43e9087fe7SAlexander Kornienko   // For historical reasons, checks don't implement the MatchFinder run()
44e9087fe7SAlexander Kornienko   // callback directly. We keep the run()/check() distinction to avoid interface
45e9087fe7SAlexander Kornienko   // churn, and to allow us to add cross-cutting logic in the future.
46e9087fe7SAlexander Kornienko   check(Result);
47e9087fe7SAlexander Kornienko }
48e9087fe7SAlexander Kornienko 
4927553933SNathan James ClangTidyCheck::OptionsView::OptionsView(
5027553933SNathan James     StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions,
5127553933SNathan James     ClangTidyContext *Context)
5212cb5405SNathan James     : NamePrefix((CheckName + ".").str()), CheckOptions(CheckOptions),
5327553933SNathan James       Context(Context) {}
54e9087fe7SAlexander Kornienko 
55f71ffd3bSKazu Hirata std::optional<StringRef>
56fcf7cc26SNathan James ClangTidyCheck::OptionsView::get(StringRef LocalName) const {
575ca68d58SNathan James   if (Context->getOptionsCollector())
585ca68d58SNathan James     Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());
5912cb5405SNathan James   const auto &Iter = CheckOptions.find((NamePrefix + LocalName).str());
60e9087fe7SAlexander Kornienko   if (Iter != CheckOptions.end())
6112cb5405SNathan James     return StringRef(Iter->getValue().Value);
62cd8702efSKazu Hirata   return std::nullopt;
63e9087fe7SAlexander Kornienko }
64e9087fe7SAlexander Kornienko 
6542dfaa15SCongcong Cai static const llvm::StringSet<> DeprecatedGlobalOptions{
6642dfaa15SCongcong Cai     "StrictMode",
6742dfaa15SCongcong Cai     "IgnoreMacros",
6842dfaa15SCongcong Cai };
6942dfaa15SCongcong Cai 
70a25487fdSNathan James static ClangTidyOptions::OptionMap::const_iterator
715ca68d58SNathan James findPriorityOption(const ClangTidyOptions::OptionMap &Options,
725ca68d58SNathan James                    StringRef NamePrefix, StringRef LocalName,
7342dfaa15SCongcong Cai                    ClangTidyContext *Context) {
7442dfaa15SCongcong Cai   llvm::StringSet<> *Collector = Context->getOptionsCollector();
755ca68d58SNathan James   if (Collector) {
765ca68d58SNathan James     Collector->insert((NamePrefix + LocalName).str());
775ca68d58SNathan James     Collector->insert(LocalName);
785ca68d58SNathan James   }
79a25487fdSNathan James   auto IterLocal = Options.find((NamePrefix + LocalName).str());
8012cb5405SNathan James   auto IterGlobal = Options.find(LocalName);
8142dfaa15SCongcong Cai   // FIXME: temporary solution for deprecation warnings, should be removed
8242dfaa15SCongcong Cai   // after 22.x. Warn configuration deps on deprecation global options.
8342dfaa15SCongcong Cai   if (IterLocal == Options.end() && IterGlobal != Options.end() &&
8442dfaa15SCongcong Cai       DeprecatedGlobalOptions.contains(LocalName))
8542dfaa15SCongcong Cai     Context->configurationDiag(
8642dfaa15SCongcong Cai         "global option '%0' is deprecated, please use '%1%0' instead.")
8742dfaa15SCongcong Cai         << LocalName << NamePrefix;
88a25487fdSNathan James   if (IterLocal == Options.end())
89a25487fdSNathan James     return IterGlobal;
90a25487fdSNathan James   if (IterGlobal == Options.end())
91a25487fdSNathan James     return IterLocal;
9245a720a8SNathan James   if (IterLocal->getValue().Priority >= IterGlobal->getValue().Priority)
93a25487fdSNathan James     return IterLocal;
94a25487fdSNathan James   return IterGlobal;
95a25487fdSNathan James }
96a25487fdSNathan James 
97f71ffd3bSKazu Hirata std::optional<StringRef>
98fcf7cc26SNathan James ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const {
9942dfaa15SCongcong Cai   auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName, Context);
100a25487fdSNathan James   if (Iter != CheckOptions.end())
10112cb5405SNathan James     return StringRef(Iter->getValue().Value);
102cd8702efSKazu Hirata   return std::nullopt;
103fcf7cc26SNathan James }
104fcf7cc26SNathan James 
105f71ffd3bSKazu Hirata static std::optional<bool> getAsBool(StringRef Value,
106fcf7cc26SNathan James                                      const llvm::Twine &LookupName) {
10768e642caSNathan James 
1088b61376dSFangrui Song   if (std::optional<bool> Parsed = llvm::yaml::parseBool(Value))
10976284bebSPiotr Zegar     return Parsed;
11068e642caSNathan James   // To maintain backwards compatability, we support parsing numbers as
11168e642caSNathan James   // booleans, even though its not supported in YAML.
112cbdc3e1bSPiotr Zegar   long long Number = 0;
11368e642caSNathan James   if (!Value.getAsInteger(10, Number))
11468e642caSNathan James     return Number != 0;
115cd8702efSKazu Hirata   return std::nullopt;
116fcf7cc26SNathan James }
117fcf7cc26SNathan James 
118fcf7cc26SNathan James template <>
119f71ffd3bSKazu Hirata std::optional<bool>
120fcf7cc26SNathan James ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const {
121f71ffd3bSKazu Hirata   if (std::optional<StringRef> ValueOr = get(LocalName)) {
12282289aa6SNathan James     if (auto Result = getAsBool(*ValueOr, NamePrefix + LocalName))
12382289aa6SNathan James       return Result;
12482289aa6SNathan James     diagnoseBadBooleanOption(NamePrefix + LocalName, *ValueOr);
12582289aa6SNathan James   }
126cd8702efSKazu Hirata   return std::nullopt;
127fcf7cc26SNathan James }
128fcf7cc26SNathan James 
129fcf7cc26SNathan James template <>
130f71ffd3bSKazu Hirata std::optional<bool>
131fcf7cc26SNathan James ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const {
13242dfaa15SCongcong Cai   auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName, Context);
13382289aa6SNathan James   if (Iter != CheckOptions.end()) {
13482289aa6SNathan James     if (auto Result = getAsBool(Iter->getValue().Value, Iter->getKey()))
13582289aa6SNathan James       return Result;
13682289aa6SNathan James     diagnoseBadBooleanOption(Iter->getKey(), Iter->getValue().Value);
137fcf7cc26SNathan James   }
138cd8702efSKazu Hirata   return std::nullopt;
139e9087fe7SAlexander Kornienko }
140e9087fe7SAlexander Kornienko 
141e9087fe7SAlexander Kornienko void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
142e9087fe7SAlexander Kornienko                                         StringRef LocalName,
143e9087fe7SAlexander Kornienko                                         StringRef Value) const {
14412cb5405SNathan James   Options[(NamePrefix + LocalName).str()] = Value;
145e9087fe7SAlexander Kornienko }
146e9087fe7SAlexander Kornienko 
147fcf0f75aSNathan James void ClangTidyCheck::OptionsView::storeInt(ClangTidyOptions::OptionMap &Options,
148e9087fe7SAlexander Kornienko                                            StringRef LocalName,
149e9087fe7SAlexander Kornienko                                            int64_t Value) const {
150e9087fe7SAlexander Kornienko   store(Options, LocalName, llvm::itostr(Value));
151e9087fe7SAlexander Kornienko }
152e9087fe7SAlexander Kornienko 
153c52b18d1Sealcdan void ClangTidyCheck::OptionsView::storeUnsigned(
154c52b18d1Sealcdan     ClangTidyOptions::OptionMap &Options, StringRef LocalName,
155c52b18d1Sealcdan     uint64_t Value) const {
156c52b18d1Sealcdan   store(Options, LocalName, llvm::utostr(Value));
157c52b18d1Sealcdan }
158c52b18d1Sealcdan 
159fcf0f75aSNathan James template <>
160fcf0f75aSNathan James void ClangTidyCheck::OptionsView::store<bool>(
161fcf0f75aSNathan James     ClangTidyOptions::OptionMap &Options, StringRef LocalName,
162fcf0f75aSNathan James     bool Value) const {
163fcf0f75aSNathan James   store(Options, LocalName, Value ? StringRef("true") : StringRef("false"));
164fcf0f75aSNathan James }
165fcf0f75aSNathan James 
166*ae9bf176SCongcong Cai std::optional<int64_t>
167*ae9bf176SCongcong Cai ClangTidyCheck::OptionsView::getEnumInt(StringRef LocalName,
168*ae9bf176SCongcong Cai                                         ArrayRef<NameAndValue> Mapping,
169*ae9bf176SCongcong Cai                                         bool CheckGlobal) const {
1705ca68d58SNathan James   if (!CheckGlobal && Context->getOptionsCollector())
1715ca68d58SNathan James     Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());
17242dfaa15SCongcong Cai   auto Iter = CheckGlobal ? findPriorityOption(CheckOptions, NamePrefix,
17342dfaa15SCongcong Cai                                                LocalName, Context)
174a25487fdSNathan James                           : CheckOptions.find((NamePrefix + LocalName).str());
175fcf7cc26SNathan James   if (Iter == CheckOptions.end())
176cd8702efSKazu Hirata     return std::nullopt;
177fcf7cc26SNathan James 
17845a720a8SNathan James   StringRef Value = Iter->getValue().Value;
179fcf7cc26SNathan James   StringRef Closest;
18053df522aSNathan James   unsigned EditDistance = 3;
181fcf7cc26SNathan James   for (const auto &NameAndEnum : Mapping) {
182*ae9bf176SCongcong Cai     if (Value == NameAndEnum.second) {
183c3bdc981SNathan James       return NameAndEnum.first;
184*ae9bf176SCongcong Cai     }
185*ae9bf176SCongcong Cai     if (Value.equals_insensitive(NameAndEnum.second)) {
186c3bdc981SNathan James       Closest = NameAndEnum.second;
187fcf7cc26SNathan James       EditDistance = 0;
188fcf7cc26SNathan James       continue;
189fcf7cc26SNathan James     }
19053df522aSNathan James     unsigned Distance =
19153df522aSNathan James         Value.edit_distance(NameAndEnum.second, true, EditDistance);
192fcf7cc26SNathan James     if (Distance < EditDistance) {
193fcf7cc26SNathan James       EditDistance = Distance;
194c3bdc981SNathan James       Closest = NameAndEnum.second;
195fcf7cc26SNathan James     }
196fcf7cc26SNathan James   }
197fcf7cc26SNathan James   if (EditDistance < 3)
19812cb5405SNathan James     diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value, Closest);
1991fd2049eSNathan James   else
20012cb5405SNathan James     diagnoseBadEnumOption(Iter->getKey(), Iter->getValue().Value);
201cd8702efSKazu Hirata   return std::nullopt;
2021fd2049eSNathan James }
2031fd2049eSNathan James 
20482289aa6SNathan James static constexpr llvm::StringLiteral ConfigWarning(
20582289aa6SNathan James     "invalid configuration value '%0' for option '%1'%select{|; expected a "
20682289aa6SNathan James     "bool|; expected an integer|; did you mean '%3'?}2");
20782289aa6SNathan James 
20882289aa6SNathan James void ClangTidyCheck::OptionsView::diagnoseBadBooleanOption(
20982289aa6SNathan James     const Twine &Lookup, StringRef Unparsed) const {
21082289aa6SNathan James   SmallString<64> Buffer;
21182289aa6SNathan James   Context->configurationDiag(ConfigWarning)
21282289aa6SNathan James       << Unparsed << Lookup.toStringRef(Buffer) << 1;
21382289aa6SNathan James }
21482289aa6SNathan James 
21582289aa6SNathan James void ClangTidyCheck::OptionsView::diagnoseBadIntegerOption(
21682289aa6SNathan James     const Twine &Lookup, StringRef Unparsed) const {
21782289aa6SNathan James   SmallString<64> Buffer;
21882289aa6SNathan James   Context->configurationDiag(ConfigWarning)
21982289aa6SNathan James       << Unparsed << Lookup.toStringRef(Buffer) << 2;
22082289aa6SNathan James }
22182289aa6SNathan James 
22282289aa6SNathan James void ClangTidyCheck::OptionsView::diagnoseBadEnumOption(
22382289aa6SNathan James     const Twine &Lookup, StringRef Unparsed, StringRef Suggestion) const {
22482289aa6SNathan James   SmallString<64> Buffer;
22582289aa6SNathan James   auto Diag = Context->configurationDiag(ConfigWarning)
22682289aa6SNathan James               << Unparsed << Lookup.toStringRef(Buffer);
22782289aa6SNathan James   if (Suggestion.empty())
22882289aa6SNathan James     Diag << 0;
2291fd2049eSNathan James   else
23082289aa6SNathan James     Diag << 3 << Suggestion;
2311fd2049eSNathan James }
2321fd2049eSNathan James 
23312cb5405SNathan James StringRef ClangTidyCheck::OptionsView::get(StringRef LocalName,
23482289aa6SNathan James                                            StringRef Default) const {
2355dd171dcSKazu Hirata   return get(LocalName).value_or(Default);
23682289aa6SNathan James }
23712cb5405SNathan James 
23812cb5405SNathan James StringRef
23982289aa6SNathan James ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
24082289aa6SNathan James                                               StringRef Default) const {
2415dd171dcSKazu Hirata   return getLocalOrGlobal(LocalName).value_or(Default);
24282289aa6SNathan James }
2437d2ea6c4SCarlos Galvez } // namespace clang::tidy
244