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