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