| 1 | //===--- RenamerClangTidyCheck.cpp - clang-tidy ---------------------------===// |
| 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 "RenamerClangTidyCheck.h" |
| 10 | #include "ASTUtils.h" |
| 11 | #include "clang/AST/CXXInheritance.h" |
| 12 | #include "clang/AST/RecursiveASTVisitor.h" |
| 13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 14 | #include "clang/Basic/CharInfo.h" |
| 15 | #include "clang/Frontend/CompilerInstance.h" |
| 16 | #include "clang/Lex/PPCallbacks.h" |
| 17 | #include "clang/Lex/Preprocessor.h" |
| 18 | #include "llvm/ADT/DenseMapInfo.h" |
| 19 | #include "llvm/ADT/PointerIntPair.h" |
| 20 | #include <optional> |
| 21 | |
| 22 | #define DEBUG_TYPE "clang-tidy" |
| 23 | |
| 24 | using namespace clang::ast_matchers; |
| 25 | |
| 26 | namespace llvm { |
| 27 | |
| 28 | /// Specialization of DenseMapInfo to allow NamingCheckId objects in DenseMaps |
| 29 | template <> |
| 30 | struct DenseMapInfo<clang::tidy::RenamerClangTidyCheck::NamingCheckId> { |
| 31 | using NamingCheckId = clang::tidy::RenamerClangTidyCheck::NamingCheckId; |
| 32 | |
| 33 | static inline NamingCheckId getEmptyKey() { |
| 34 | return {DenseMapInfo<clang::SourceLocation>::getEmptyKey(), "EMPTY" }; |
| 35 | } |
| 36 | |
| 37 | static inline NamingCheckId getTombstoneKey() { |
| 38 | return {DenseMapInfo<clang::SourceLocation>::getTombstoneKey(), |
| 39 | "TOMBSTONE" }; |
| 40 | } |
| 41 | |
| 42 | static unsigned getHashValue(NamingCheckId Val) { |
| 43 | assert(Val != getEmptyKey() && "Cannot hash the empty key!" ); |
| 44 | assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!" ); |
| 45 | |
| 46 | return DenseMapInfo<clang::SourceLocation>::getHashValue(Loc: Val.first) + |
| 47 | DenseMapInfo<StringRef>::getHashValue(Val: Val.second); |
| 48 | } |
| 49 | |
| 50 | static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) { |
| 51 | if (RHS == getEmptyKey()) |
| 52 | return LHS == getEmptyKey(); |
| 53 | if (RHS == getTombstoneKey()) |
| 54 | return LHS == getTombstoneKey(); |
| 55 | return LHS == RHS; |
| 56 | } |
| 57 | }; |
| 58 | |
| 59 | } // namespace llvm |
| 60 | |
| 61 | namespace clang::tidy { |
| 62 | |
| 63 | namespace { |
| 64 | |
| 65 | class NameLookup { |
| 66 | llvm::PointerIntPair<const NamedDecl *, 1, bool> Data; |
| 67 | |
| 68 | public: |
| 69 | explicit NameLookup(const NamedDecl *ND) : Data(ND, false) {} |
| 70 | explicit NameLookup(std::nullopt_t) : Data(nullptr, true) {} |
| 71 | explicit NameLookup(std::nullptr_t) : Data(nullptr, false) {} |
| 72 | NameLookup() : NameLookup(nullptr) {} |
| 73 | |
| 74 | bool hasMultipleResolutions() const { return Data.getInt(); } |
| 75 | const NamedDecl *getDecl() const { |
| 76 | assert(!hasMultipleResolutions() && "Found multiple decls" ); |
| 77 | return Data.getPointer(); |
| 78 | } |
| 79 | operator bool() const { return !hasMultipleResolutions(); } |
| 80 | const NamedDecl *operator*() const { return getDecl(); } |
| 81 | }; |
| 82 | |
| 83 | } // namespace |
| 84 | |
| 85 | static const NamedDecl *findDecl(const RecordDecl &RecDecl, |
| 86 | StringRef DeclName) { |
| 87 | for (const Decl *D : RecDecl.decls()) { |
| 88 | if (const auto *ND = dyn_cast<NamedDecl>(D)) { |
| 89 | if (ND->getDeclName().isIdentifier() && ND->getName() == DeclName) |
| 90 | return ND; |
| 91 | } |
| 92 | } |
| 93 | return nullptr; |
| 94 | } |
| 95 | |
| 96 | /// Returns the function that \p Method is overridding. If There are none or |
| 97 | /// multiple overrides it returns nullptr. If the overridden function itself is |
| 98 | /// overridding then it will recurse up to find the first decl of the function. |
| 99 | static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) { |
| 100 | if (Method->size_overridden_methods() != 1) |
| 101 | return nullptr; |
| 102 | |
| 103 | while (true) { |
| 104 | Method = *Method->begin_overridden_methods(); |
| 105 | assert(Method && "Overridden method shouldn't be null" ); |
| 106 | unsigned NumOverrides = Method->size_overridden_methods(); |
| 107 | if (NumOverrides == 0) |
| 108 | return Method; |
| 109 | if (NumOverrides > 1) |
| 110 | return nullptr; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | static bool hasNoName(const NamedDecl *Decl) { |
| 115 | return !Decl->getIdentifier() || Decl->getName().empty(); |
| 116 | } |
| 117 | |
| 118 | static const NamedDecl *getFailureForNamedDecl(const NamedDecl *ND) { |
| 119 | const auto *Canonical = cast<NamedDecl>(ND->getCanonicalDecl()); |
| 120 | if (Canonical != ND) |
| 121 | return Canonical; |
| 122 | |
| 123 | if (const auto *Method = dyn_cast<CXXMethodDecl>(Val: ND)) { |
| 124 | if (const CXXMethodDecl *Overridden = getOverrideMethod(Method)) |
| 125 | Canonical = cast<NamedDecl>(Val: Overridden->getCanonicalDecl()); |
| 126 | else if (const FunctionTemplateDecl *Primary = Method->getPrimaryTemplate()) |
| 127 | if (const FunctionDecl *TemplatedDecl = Primary->getTemplatedDecl()) |
| 128 | Canonical = cast<NamedDecl>(Val: TemplatedDecl->getCanonicalDecl()); |
| 129 | |
| 130 | if (Canonical != ND) |
| 131 | return Canonical; |
| 132 | } |
| 133 | |
| 134 | return ND; |
| 135 | } |
| 136 | |
| 137 | /// Returns a decl matching the \p DeclName in \p Parent or one of its base |
| 138 | /// classes. If \p AggressiveTemplateLookup is `true` then it will check |
| 139 | /// template dependent base classes as well. |
| 140 | /// If a matching decl is found in multiple base classes then it will return a |
| 141 | /// flag indicating the multiple resolutions. |
| 142 | static NameLookup findDeclInBases(const CXXRecordDecl &Parent, |
| 143 | StringRef DeclName, |
| 144 | bool AggressiveTemplateLookup) { |
| 145 | if (!Parent.hasDefinition()) |
| 146 | return NameLookup(nullptr); |
| 147 | if (const NamedDecl *InClassRef = findDecl(Parent, DeclName)) |
| 148 | return NameLookup(InClassRef); |
| 149 | const NamedDecl *Found = nullptr; |
| 150 | |
| 151 | for (CXXBaseSpecifier Base : Parent.bases()) { |
| 152 | const auto *Record = Base.getType()->getAsCXXRecordDecl(); |
| 153 | if (!Record && AggressiveTemplateLookup) { |
| 154 | if (const auto *TST = |
| 155 | Base.getType()->getAs<TemplateSpecializationType>()) { |
| 156 | if (const auto *TD = llvm::dyn_cast_or_null<ClassTemplateDecl>( |
| 157 | Val: TST->getTemplateName().getAsTemplateDecl())) |
| 158 | Record = TD->getTemplatedDecl(); |
| 159 | } |
| 160 | } |
| 161 | if (!Record) |
| 162 | continue; |
| 163 | if (auto Search = |
| 164 | findDeclInBases(Parent: *Record, DeclName, AggressiveTemplateLookup)) { |
| 165 | if (*Search) { |
| 166 | if (Found) |
| 167 | return NameLookup( |
| 168 | std::nullopt); // Multiple decls found in different base classes. |
| 169 | Found = *Search; |
| 170 | continue; |
| 171 | } |
| 172 | } else |
| 173 | return NameLookup(std::nullopt); // Propagate multiple resolution back up. |
| 174 | } |
| 175 | return NameLookup(Found); // If nullptr, decl wasn't found. |
| 176 | } |
| 177 | |
| 178 | namespace { |
| 179 | |
| 180 | /// Callback supplies macros to RenamerClangTidyCheck::checkMacro |
| 181 | class RenamerClangTidyCheckPPCallbacks : public PPCallbacks { |
| 182 | public: |
| 183 | RenamerClangTidyCheckPPCallbacks(const SourceManager &SM, |
| 184 | RenamerClangTidyCheck *Check) |
| 185 | : SM(SM), Check(Check) {} |
| 186 | |
| 187 | /// MacroDefined calls checkMacro for macros in the main file |
| 188 | void MacroDefined(const Token &MacroNameTok, |
| 189 | const MacroDirective *MD) override { |
| 190 | const MacroInfo *Info = MD->getMacroInfo(); |
| 191 | if (Info->isBuiltinMacro()) |
| 192 | return; |
| 193 | if (SM.isWrittenInBuiltinFile(Loc: MacroNameTok.getLocation())) |
| 194 | return; |
| 195 | if (SM.isWrittenInCommandLineFile(Loc: MacroNameTok.getLocation())) |
| 196 | return; |
| 197 | Check->checkMacro(MacroNameTok, MI: Info, SourceMgr: SM); |
| 198 | } |
| 199 | |
| 200 | /// MacroExpands calls expandMacro for macros in the main file |
| 201 | void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, |
| 202 | SourceRange /*Range*/, |
| 203 | const MacroArgs * /*Args*/) override { |
| 204 | Check->expandMacro(MacroNameTok, MI: MD.getMacroInfo(), SourceMgr: SM); |
| 205 | } |
| 206 | |
| 207 | private: |
| 208 | const SourceManager &SM; |
| 209 | RenamerClangTidyCheck *Check; |
| 210 | }; |
| 211 | |
| 212 | class RenamerClangTidyVisitor |
| 213 | : public RecursiveASTVisitor<RenamerClangTidyVisitor> { |
| 214 | public: |
| 215 | RenamerClangTidyVisitor(RenamerClangTidyCheck *Check, const SourceManager &SM, |
| 216 | bool AggressiveDependentMemberLookup) |
| 217 | : Check(Check), SM(SM), |
| 218 | AggressiveDependentMemberLookup(AggressiveDependentMemberLookup) {} |
| 219 | |
| 220 | bool shouldVisitTemplateInstantiations() const { return true; } |
| 221 | |
| 222 | bool shouldVisitImplicitCode() const { return false; } |
| 223 | |
| 224 | bool VisitCXXConstructorDecl(CXXConstructorDecl *Decl) { |
| 225 | if (Decl->isImplicit()) |
| 226 | return true; |
| 227 | Check->addUsage(Decl->getParent(), Decl->getNameInfo().getSourceRange(), |
| 228 | SM); |
| 229 | |
| 230 | for (const auto *Init : Decl->inits()) { |
| 231 | if (!Init->isWritten() || Init->isInClassMemberInitializer()) |
| 232 | continue; |
| 233 | if (const FieldDecl *FD = Init->getAnyMember()) |
| 234 | Check->addUsage(FD, SourceRange(Init->getMemberLocation()), SM); |
| 235 | // Note: delegating constructors and base class initializers are handled |
| 236 | // via the "typeLoc" matcher. |
| 237 | } |
| 238 | |
| 239 | return true; |
| 240 | } |
| 241 | |
| 242 | bool VisitCXXDestructorDecl(CXXDestructorDecl *Decl) { |
| 243 | if (Decl->isImplicit()) |
| 244 | return true; |
| 245 | SourceRange Range = Decl->getNameInfo().getSourceRange(); |
| 246 | if (Range.getBegin().isInvalid()) |
| 247 | return true; |
| 248 | |
| 249 | // The first token that will be found is the ~ (or the equivalent trigraph), |
| 250 | // we want instead to replace the next token, that will be the identifier. |
| 251 | Range.setBegin(CharSourceRange::getTokenRange(R: Range).getEnd()); |
| 252 | Check->addUsage(Decl->getParent(), Range, SM); |
| 253 | return true; |
| 254 | } |
| 255 | |
| 256 | bool VisitUsingDecl(UsingDecl *Decl) { |
| 257 | for (const auto *Shadow : Decl->shadows()) |
| 258 | Check->addUsage(Shadow->getTargetDecl(), |
| 259 | Decl->getNameInfo().getSourceRange(), SM); |
| 260 | return true; |
| 261 | } |
| 262 | |
| 263 | bool VisitUsingDirectiveDecl(UsingDirectiveDecl *Decl) { |
| 264 | Check->addUsage(Decl: Decl->getNominatedNamespaceAsWritten(), |
| 265 | Range: Decl->getIdentLocation(), SourceMgr: SM); |
| 266 | return true; |
| 267 | } |
| 268 | |
| 269 | bool VisitNamedDecl(NamedDecl *Decl) { |
| 270 | SourceRange UsageRange = |
| 271 | DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) |
| 272 | .getSourceRange(); |
| 273 | Check->addUsage(Decl, Range: UsageRange, SourceMgr: SM); |
| 274 | return true; |
| 275 | } |
| 276 | |
| 277 | bool VisitDeclRefExpr(DeclRefExpr *DeclRef) { |
| 278 | SourceRange Range = DeclRef->getNameInfo().getSourceRange(); |
| 279 | Check->addUsage(DeclRef->getDecl(), Range, SM); |
| 280 | return true; |
| 281 | } |
| 282 | |
| 283 | bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Loc) { |
| 284 | if (const NestedNameSpecifier *Spec = Loc.getNestedNameSpecifier()) { |
| 285 | if (const NamespaceDecl *Decl = Spec->getAsNamespace()) |
| 286 | Check->addUsage(Decl, Loc.getLocalSourceRange(), SM); |
| 287 | } |
| 288 | |
| 289 | using Base = RecursiveASTVisitor<RenamerClangTidyVisitor>; |
| 290 | return Base::TraverseNestedNameSpecifierLoc(NNS: Loc); |
| 291 | } |
| 292 | |
| 293 | bool VisitMemberExpr(MemberExpr *MemberRef) { |
| 294 | SourceRange Range = MemberRef->getMemberNameInfo().getSourceRange(); |
| 295 | Check->addUsage(MemberRef->getMemberDecl(), Range, SM); |
| 296 | return true; |
| 297 | } |
| 298 | |
| 299 | bool |
| 300 | VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *DepMemberRef) { |
| 301 | QualType BaseType = DepMemberRef->isArrow() |
| 302 | ? DepMemberRef->getBaseType()->getPointeeType() |
| 303 | : DepMemberRef->getBaseType(); |
| 304 | if (BaseType.isNull()) |
| 305 | return true; |
| 306 | const CXXRecordDecl *Base = BaseType.getTypePtr()->getAsCXXRecordDecl(); |
| 307 | if (!Base) |
| 308 | return true; |
| 309 | DeclarationName DeclName = DepMemberRef->getMemberNameInfo().getName(); |
| 310 | if (!DeclName.isIdentifier()) |
| 311 | return true; |
| 312 | StringRef DependentName = DeclName.getAsIdentifierInfo()->getName(); |
| 313 | |
| 314 | if (NameLookup Resolved = findDeclInBases( |
| 315 | Parent: *Base, DeclName: DependentName, AggressiveTemplateLookup: AggressiveDependentMemberLookup)) { |
| 316 | if (*Resolved) |
| 317 | Check->addUsage(Decl: *Resolved, |
| 318 | Range: DepMemberRef->getMemberNameInfo().getSourceRange(), SourceMgr: SM); |
| 319 | } |
| 320 | |
| 321 | return true; |
| 322 | } |
| 323 | |
| 324 | bool VisitTypedefTypeLoc(const TypedefTypeLoc &Loc) { |
| 325 | Check->addUsage(Loc.getTypedefNameDecl(), Loc.getSourceRange(), SM); |
| 326 | return true; |
| 327 | } |
| 328 | |
| 329 | bool VisitTagTypeLoc(const TagTypeLoc &Loc) { |
| 330 | Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); |
| 331 | return true; |
| 332 | } |
| 333 | |
| 334 | bool VisitInjectedClassNameTypeLoc(const InjectedClassNameTypeLoc &Loc) { |
| 335 | Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); |
| 336 | return true; |
| 337 | } |
| 338 | |
| 339 | bool VisitUnresolvedUsingTypeLoc(const UnresolvedUsingTypeLoc &Loc) { |
| 340 | Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); |
| 341 | return true; |
| 342 | } |
| 343 | |
| 344 | bool VisitTemplateTypeParmTypeLoc(const TemplateTypeParmTypeLoc &Loc) { |
| 345 | Check->addUsage(Loc.getDecl(), Loc.getSourceRange(), SM); |
| 346 | return true; |
| 347 | } |
| 348 | |
| 349 | bool |
| 350 | VisitTemplateSpecializationTypeLoc(const TemplateSpecializationTypeLoc &Loc) { |
| 351 | const TemplateDecl *Decl = |
| 352 | Loc.getTypePtr()->getTemplateName().getAsTemplateDecl(); |
| 353 | |
| 354 | SourceRange Range(Loc.getTemplateNameLoc(), Loc.getTemplateNameLoc()); |
| 355 | if (const auto *ClassDecl = dyn_cast<TemplateDecl>(Decl)) { |
| 356 | if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl()) |
| 357 | Check->addUsage(Decl: TemplDecl, Range, SourceMgr: SM); |
| 358 | } |
| 359 | |
| 360 | return true; |
| 361 | } |
| 362 | |
| 363 | bool VisitDependentTemplateSpecializationTypeLoc( |
| 364 | const DependentTemplateSpecializationTypeLoc &Loc) { |
| 365 | if (const TagDecl *Decl = Loc.getTypePtr()->getAsTagDecl()) |
| 366 | Check->addUsage(Decl, Loc.getSourceRange(), SM); |
| 367 | |
| 368 | return true; |
| 369 | } |
| 370 | |
| 371 | bool VisitDesignatedInitExpr(DesignatedInitExpr *Expr) { |
| 372 | for (const DesignatedInitExpr::Designator &D : Expr->designators()) { |
| 373 | if (!D.isFieldDesignator()) |
| 374 | continue; |
| 375 | const FieldDecl *FD = D.getFieldDecl(); |
| 376 | if (!FD) |
| 377 | continue; |
| 378 | const IdentifierInfo *II = FD->getIdentifier(); |
| 379 | if (!II) |
| 380 | continue; |
| 381 | SourceRange FixLocation{D.getFieldLoc(), D.getFieldLoc()}; |
| 382 | Check->addUsage(FD, FixLocation, SM); |
| 383 | } |
| 384 | |
| 385 | return true; |
| 386 | } |
| 387 | |
| 388 | private: |
| 389 | RenamerClangTidyCheck *Check; |
| 390 | const SourceManager &SM; |
| 391 | const bool AggressiveDependentMemberLookup; |
| 392 | }; |
| 393 | |
| 394 | } // namespace |
| 395 | |
| 396 | RenamerClangTidyCheck::RenamerClangTidyCheck(StringRef CheckName, |
| 397 | ClangTidyContext *Context) |
| 398 | : ClangTidyCheck(CheckName, Context), |
| 399 | AggressiveDependentMemberLookup( |
| 400 | Options.get(LocalName: "AggressiveDependentMemberLookup" , Default: false)) {} |
| 401 | RenamerClangTidyCheck::~RenamerClangTidyCheck() = default; |
| 402 | |
| 403 | void RenamerClangTidyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 404 | Options.store(Options&: Opts, LocalName: "AggressiveDependentMemberLookup" , |
| 405 | Value: AggressiveDependentMemberLookup); |
| 406 | } |
| 407 | |
| 408 | void RenamerClangTidyCheck::registerMatchers(MatchFinder *Finder) { |
| 409 | Finder->addMatcher(NodeMatch: translationUnitDecl(), Action: this); |
| 410 | } |
| 411 | |
| 412 | void RenamerClangTidyCheck::registerPPCallbacks( |
| 413 | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { |
| 414 | ModuleExpanderPP->addPPCallbacks( |
| 415 | C: std::make_unique<RenamerClangTidyCheckPPCallbacks>(args: SM, args: this)); |
| 416 | } |
| 417 | |
| 418 | std::pair<RenamerClangTidyCheck::NamingCheckFailureMap::iterator, bool> |
| 419 | RenamerClangTidyCheck::addUsage( |
| 420 | const RenamerClangTidyCheck::NamingCheckId &FailureId, |
| 421 | SourceRange UsageRange, const SourceManager &SourceMgr) { |
| 422 | // Do nothing if the provided range is invalid. |
| 423 | if (UsageRange.isInvalid()) |
| 424 | return {NamingCheckFailures.end(), false}; |
| 425 | |
| 426 | // Get the spelling location for performing the fix. This is necessary because |
| 427 | // macros can map the same spelling location to different source locations, |
| 428 | // and we only want to fix the token once, before it is expanded by the macro. |
| 429 | SourceLocation FixLocation = UsageRange.getBegin(); |
| 430 | FixLocation = SourceMgr.getSpellingLoc(Loc: FixLocation); |
| 431 | if (FixLocation.isInvalid()) |
| 432 | return {NamingCheckFailures.end(), false}; |
| 433 | |
| 434 | auto EmplaceResult = NamingCheckFailures.try_emplace(Key: FailureId); |
| 435 | NamingCheckFailure &Failure = EmplaceResult.first->second; |
| 436 | |
| 437 | // Try to insert the identifier location in the Usages map, and bail out if it |
| 438 | // is already in there |
| 439 | if (!Failure.RawUsageLocs.insert(V: FixLocation).second) |
| 440 | return EmplaceResult; |
| 441 | |
| 442 | if (Failure.FixStatus != RenamerClangTidyCheck::ShouldFixStatus::ShouldFix) |
| 443 | return EmplaceResult; |
| 444 | |
| 445 | if (SourceMgr.isWrittenInScratchSpace(Loc: FixLocation)) |
| 446 | Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro; |
| 447 | |
| 448 | if (!utils::rangeCanBeFixed(Range: UsageRange, SM: &SourceMgr)) |
| 449 | Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro; |
| 450 | |
| 451 | return EmplaceResult; |
| 452 | } |
| 453 | |
| 454 | void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl, |
| 455 | SourceRange UsageRange, |
| 456 | const SourceManager &SourceMgr) { |
| 457 | if (hasNoName(Decl)) |
| 458 | return; |
| 459 | |
| 460 | // Ignore ClassTemplateSpecializationDecl which are creating duplicate |
| 461 | // replacements with CXXRecordDecl. |
| 462 | if (isa<ClassTemplateSpecializationDecl>(Val: Decl)) |
| 463 | return; |
| 464 | |
| 465 | // We don't want to create a failure for every NamedDecl we find. Ideally |
| 466 | // there is just one NamedDecl in every group of "related" NamedDecls that |
| 467 | // becomes the failure. This NamedDecl and all of its related NamedDecls |
| 468 | // become usages. E.g. Since NamedDecls are Redeclarable, only the canonical |
| 469 | // NamedDecl becomes the failure and all redeclarations become usages. |
| 470 | const NamedDecl *FailureDecl = getFailureForNamedDecl(ND: Decl); |
| 471 | |
| 472 | std::optional<FailureInfo> MaybeFailure = |
| 473 | getDeclFailureInfo(Decl: FailureDecl, SM: SourceMgr); |
| 474 | if (!MaybeFailure) |
| 475 | return; |
| 476 | |
| 477 | NamingCheckId FailureId(FailureDecl->getLocation(), FailureDecl->getName()); |
| 478 | |
| 479 | auto [FailureIter, NewFailure] = addUsage(FailureId, UsageRange, SourceMgr); |
| 480 | |
| 481 | if (FailureIter == NamingCheckFailures.end()) { |
| 482 | // Nothing to do if the usage wasn't accepted. |
| 483 | return; |
| 484 | } |
| 485 | if (!NewFailure) { |
| 486 | // FailureInfo has already been provided. |
| 487 | return; |
| 488 | } |
| 489 | |
| 490 | // Update the stored failure with info regarding the FailureDecl. |
| 491 | NamingCheckFailure &Failure = FailureIter->second; |
| 492 | Failure.Info = std::move(*MaybeFailure); |
| 493 | |
| 494 | // Don't overwritte the failure status if it was already set. |
| 495 | if (!Failure.shouldFix()) { |
| 496 | return; |
| 497 | } |
| 498 | const IdentifierTable &Idents = FailureDecl->getASTContext().Idents; |
| 499 | auto CheckNewIdentifier = Idents.find(Failure.Info.Fixup); |
| 500 | if (CheckNewIdentifier != Idents.end()) { |
| 501 | const IdentifierInfo *Ident = CheckNewIdentifier->second; |
| 502 | if (Ident->isKeyword(LangOpts: getLangOpts())) |
| 503 | Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword; |
| 504 | else if (Ident->hasMacroDefinition()) |
| 505 | Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition; |
| 506 | } else if (!isValidAsciiIdentifier(S: Failure.Info.Fixup)) { |
| 507 | Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier; |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) { |
| 512 | if (!Result.SourceManager) { |
| 513 | // In principle SourceManager is not null but going only by the definition |
| 514 | // of MatchResult it must be handled. Cannot rename anything without a |
| 515 | // SourceManager. |
| 516 | return; |
| 517 | } |
| 518 | RenamerClangTidyVisitor Visitor(this, *Result.SourceManager, |
| 519 | AggressiveDependentMemberLookup); |
| 520 | Visitor.TraverseAST(AST&: *Result.Context); |
| 521 | } |
| 522 | |
| 523 | void RenamerClangTidyCheck::checkMacro(const Token &MacroNameTok, |
| 524 | const MacroInfo *MI, |
| 525 | const SourceManager &SourceMgr) { |
| 526 | std::optional<FailureInfo> MaybeFailure = |
| 527 | getMacroFailureInfo(MacroNameTok, SM: SourceMgr); |
| 528 | if (!MaybeFailure) |
| 529 | return; |
| 530 | FailureInfo &Info = *MaybeFailure; |
| 531 | StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); |
| 532 | NamingCheckId ID(MI->getDefinitionLoc(), Name); |
| 533 | NamingCheckFailure &Failure = NamingCheckFailures[ID]; |
| 534 | SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); |
| 535 | |
| 536 | if (!isValidAsciiIdentifier(S: Info.Fixup)) |
| 537 | Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier; |
| 538 | |
| 539 | Failure.Info = std::move(Info); |
| 540 | addUsage(FailureId: ID, UsageRange: Range, SourceMgr); |
| 541 | } |
| 542 | |
| 543 | void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok, |
| 544 | const MacroInfo *MI, |
| 545 | const SourceManager &SourceMgr) { |
| 546 | StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); |
| 547 | NamingCheckId ID(MI->getDefinitionLoc(), Name); |
| 548 | |
| 549 | auto Failure = NamingCheckFailures.find(Val: ID); |
| 550 | if (Failure == NamingCheckFailures.end()) |
| 551 | return; |
| 552 | |
| 553 | SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); |
| 554 | addUsage(FailureId: ID, UsageRange: Range, SourceMgr); |
| 555 | } |
| 556 | |
| 557 | static std::string |
| 558 | getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus, |
| 559 | const std::string &Fixup) { |
| 560 | if (Fixup.empty() || |
| 561 | FixStatus == RenamerClangTidyCheck::ShouldFixStatus::FixInvalidIdentifier) |
| 562 | return "; cannot be fixed automatically" ; |
| 563 | if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ShouldFix) |
| 564 | return {}; |
| 565 | if (FixStatus >= |
| 566 | RenamerClangTidyCheck::ShouldFixStatus::IgnoreFailureThreshold) |
| 567 | return {}; |
| 568 | if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithKeyword) |
| 569 | return "; cannot be fixed because '" + Fixup + |
| 570 | "' would conflict with a keyword" ; |
| 571 | if (FixStatus == |
| 572 | RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithMacroDefinition) |
| 573 | return "; cannot be fixed because '" + Fixup + |
| 574 | "' would conflict with a macro definition" ; |
| 575 | llvm_unreachable("invalid ShouldFixStatus" ); |
| 576 | } |
| 577 | |
| 578 | void RenamerClangTidyCheck::onEndOfTranslationUnit() { |
| 579 | for (const auto &Pair : NamingCheckFailures) { |
| 580 | const NamingCheckId &Decl = Pair.first; |
| 581 | const NamingCheckFailure &Failure = Pair.second; |
| 582 | |
| 583 | if (Failure.Info.KindName.empty()) |
| 584 | continue; |
| 585 | |
| 586 | if (Failure.shouldNotify()) { |
| 587 | auto DiagInfo = getDiagInfo(ID: Decl, Failure); |
| 588 | auto Diag = diag(Loc: Decl.first, |
| 589 | Description: DiagInfo.Text + getDiagnosticSuffix(FixStatus: Failure.FixStatus, |
| 590 | Fixup: Failure.Info.Fixup)); |
| 591 | DiagInfo.ApplyArgs(Diag); |
| 592 | |
| 593 | if (Failure.shouldFix()) { |
| 594 | for (const auto &Loc : Failure.RawUsageLocs) { |
| 595 | // We assume that the identifier name is made of one token only. This |
| 596 | // is always the case as we ignore usages in macros that could build |
| 597 | // identifier names by combining multiple tokens. |
| 598 | // |
| 599 | // For destructors, we already take care of it by remembering the |
| 600 | // location of the start of the identifier and not the start of the |
| 601 | // tilde. |
| 602 | // |
| 603 | // Other multi-token identifiers, such as operators are not checked at |
| 604 | // all. |
| 605 | Diag << FixItHint::CreateReplacement(RemoveRange: SourceRange(Loc), |
| 606 | Code: Failure.Info.Fixup); |
| 607 | } |
| 608 | } |
| 609 | } |
| 610 | } |
| 611 | } |
| 612 | |
| 613 | } // namespace clang::tidy |
| 614 | |