brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.6 KiB · bc6de97 Raw
615 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 24using namespace clang::ast_matchers;25 26namespace llvm {27 28/// Specialization of DenseMapInfo to allow NamingCheckId objects in DenseMaps29template <>30struct DenseMapInfo<clang::tidy::RenamerClangTidyCheck::NamingCheckId> {31  using NamingCheckId = clang::tidy::RenamerClangTidyCheck::NamingCheckId;32 33  static NamingCheckId getEmptyKey() {34    return {DenseMapInfo<clang::SourceLocation>::getEmptyKey(), "EMPTY"};35  }36 37  static 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(Val.first) +47           DenseMapInfo<StringRef>::getHashValue(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 llvm60 61namespace clang::tidy {62 63namespace {64 65class NameLookup {66  llvm::PointerIntPair<const NamedDecl *, 1, bool> Data;67 68public: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} // namespace84 85static 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 overriding. If There are none or97/// multiple overrides it returns nullptr. If the overridden function itself is98/// overriding then it will recurse up to find the first decl of the function.99static 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    const unsigned NumOverrides = Method->size_overridden_methods();107    if (NumOverrides == 0)108      return Method;109    if (NumOverrides > 1)110      return nullptr;111  }112}113 114static bool hasNoName(const NamedDecl *Decl) {115  return !Decl->getIdentifier() || Decl->getName().empty();116}117 118static 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>(ND)) {124    if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))125      Canonical = cast<NamedDecl>(Overridden->getCanonicalDecl());126    else if (const FunctionTemplateDecl *Primary = Method->getPrimaryTemplate())127      if (const FunctionDecl *TemplatedDecl = Primary->getTemplatedDecl())128        Canonical = cast<NamedDecl>(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 base138/// classes. If \p AggressiveTemplateLookup is `true` then it will check139/// template dependent base classes as well.140/// If a matching decl is found in multiple base classes then it will return a141/// flag indicating the multiple resolutions.142static 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 (const 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                TST->getTemplateName().getAsTemplateDecl()))158          Record = TD->getTemplatedDecl();159      }160    }161    if (!Record)162      continue;163    if (auto Search =164            findDeclInBases(*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    } else173      return NameLookup(std::nullopt); // Propagate multiple resolution back up.174  }175  return NameLookup(Found); // If nullptr, decl wasn't found.176}177 178namespace {179 180/// Callback supplies macros to RenamerClangTidyCheck::checkMacro181class RenamerClangTidyCheckPPCallbacks : public PPCallbacks {182public:183  RenamerClangTidyCheckPPCallbacks(const SourceManager &SM,184                                   RenamerClangTidyCheck *Check)185      : SM(SM), Check(Check) {}186 187  /// MacroDefined calls checkMacro for macros in the main file188  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(MacroNameTok.getLocation()))194      return;195    if (SM.isWrittenInCommandLineFile(MacroNameTok.getLocation()))196      return;197    if (SM.isInSystemHeader(MacroNameTok.getLocation()))198      return;199    Check->checkMacro(MacroNameTok, Info, SM);200  }201 202  /// MacroExpands calls expandMacro for macros in the main file203  void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,204                    SourceRange /*Range*/,205                    const MacroArgs * /*Args*/) override {206    Check->expandMacro(MacroNameTok, MD.getMacroInfo(), SM);207  }208 209private:210  const SourceManager &SM;211  RenamerClangTidyCheck *Check;212};213 214class RenamerClangTidyVisitor215    : public RecursiveASTVisitor<RenamerClangTidyVisitor> {216public:217  RenamerClangTidyVisitor(RenamerClangTidyCheck *Check, const SourceManager &SM,218                          bool AggressiveDependentMemberLookup)219      : Check(Check), SM(SM),220        AggressiveDependentMemberLookup(AggressiveDependentMemberLookup) {}221 222  bool shouldVisitTemplateInstantiations() const { return true; }223 224  bool shouldVisitImplicitCode() const { return false; }225 226  bool VisitCXXConstructorDecl(CXXConstructorDecl *Decl) {227    if (Decl->isImplicit())228      return true;229    Check->addUsage(Decl->getParent(), Decl->getNameInfo().getSourceRange(),230                    SM);231 232    for (const auto *Init : Decl->inits()) {233      if (!Init->isWritten() || Init->isInClassMemberInitializer())234        continue;235      if (const FieldDecl *FD = Init->getAnyMember())236        Check->addUsage(FD, SourceRange(Init->getMemberLocation()), SM);237      // Note: delegating constructors and base class initializers are handled238      // via the "typeLoc" matcher.239    }240 241    return true;242  }243 244  bool VisitCXXDestructorDecl(CXXDestructorDecl *Decl) {245    if (Decl->isImplicit())246      return true;247    SourceRange Range = Decl->getNameInfo().getSourceRange();248    if (Range.getBegin().isInvalid())249      return true;250 251    // The first token that will be found is the ~ (or the equivalent trigraph),252    // we want instead to replace the next token, that will be the identifier.253    Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd());254    Check->addUsage(Decl->getParent(), Range, SM);255    return true;256  }257 258  bool VisitUsingDecl(UsingDecl *Decl) {259    for (const auto *Shadow : Decl->shadows())260      Check->addUsage(Shadow->getTargetDecl(),261                      Decl->getNameInfo().getSourceRange(), SM);262    return true;263  }264 265  bool VisitUsingDirectiveDecl(UsingDirectiveDecl *Decl) {266    Check->addUsage(Decl->getNominatedNamespaceAsWritten(),267                    Decl->getIdentLocation(), SM);268    return true;269  }270 271  bool VisitNamedDecl(NamedDecl *Decl) {272    const SourceRange UsageRange =273        DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())274            .getSourceRange();275    Check->addUsage(Decl, UsageRange, SM);276    return true;277  }278 279  bool VisitDeclRefExpr(DeclRefExpr *DeclRef) {280    const SourceRange Range = DeclRef->getNameInfo().getSourceRange();281    Check->addUsage(DeclRef->getDecl(), Range, SM);282    return true;283  }284 285  bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Loc) {286    if (const NestedNameSpecifier Spec = Loc.getNestedNameSpecifier();287        Spec.getKind() == NestedNameSpecifier::Kind::Namespace) {288      if (const auto *Decl =289              dyn_cast<NamespaceDecl>(Spec.getAsNamespaceAndPrefix().Namespace))290        Check->addUsage(Decl, Loc.getLocalSourceRange(), SM);291    }292 293    using Base = RecursiveASTVisitor<RenamerClangTidyVisitor>;294    return Base::TraverseNestedNameSpecifierLoc(Loc);295  }296 297  bool VisitMemberExpr(MemberExpr *MemberRef) {298    const SourceRange Range = MemberRef->getMemberNameInfo().getSourceRange();299    Check->addUsage(MemberRef->getMemberDecl(), Range, SM);300    return true;301  }302 303  bool304  VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *DepMemberRef) {305    const QualType BaseType =306        DepMemberRef->isArrow() ? DepMemberRef->getBaseType()->getPointeeType()307                                : DepMemberRef->getBaseType();308    if (BaseType.isNull())309      return true;310    const CXXRecordDecl *Base = BaseType.getTypePtr()->getAsCXXRecordDecl();311    if (!Base)312      return true;313    const DeclarationName DeclName =314        DepMemberRef->getMemberNameInfo().getName();315    if (!DeclName.isIdentifier())316      return true;317    const StringRef DependentName = DeclName.getAsIdentifierInfo()->getName();318 319    if (const NameLookup Resolved = findDeclInBases(320            *Base, DependentName, AggressiveDependentMemberLookup)) {321      if (*Resolved)322        Check->addUsage(*Resolved,323                        DepMemberRef->getMemberNameInfo().getSourceRange(), SM);324    }325 326    return true;327  }328 329  bool VisitTypedefTypeLoc(const TypedefTypeLoc &Loc) {330    Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM);331    return true;332  }333 334  bool VisitTagTypeLoc(const TagTypeLoc &Loc) {335    Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM);336    return true;337  }338 339  bool VisitUnresolvedUsingTypeLoc(const UnresolvedUsingTypeLoc &Loc) {340    Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM);341    return true;342  }343 344  bool VisitTemplateTypeParmTypeLoc(const TemplateTypeParmTypeLoc &Loc) {345    Check->addUsage(Loc.getDecl(), Loc.getNameLoc(), SM);346    return true;347  }348 349  bool350  VisitTemplateSpecializationTypeLoc(const TemplateSpecializationTypeLoc &Loc) {351    const TemplateDecl *Decl =352        Loc.getTypePtr()->getTemplateName().getAsTemplateDecl(353            /*IgnoreDeduced=*/true);354    if (!Decl)355      return true;356 357    if (const auto *ClassDecl = dyn_cast<TemplateDecl>(Decl))358      if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl())359        Check->addUsage(TemplDecl, Loc.getTemplateNameLoc(), SM);360 361    return true;362  }363 364  bool VisitDesignatedInitExpr(DesignatedInitExpr *Expr) {365    for (const DesignatedInitExpr::Designator &D : Expr->designators()) {366      if (!D.isFieldDesignator())367        continue;368      const FieldDecl *FD = D.getFieldDecl();369      if (!FD)370        continue;371      const IdentifierInfo *II = FD->getIdentifier();372      if (!II)373        continue;374      const SourceRange FixLocation{D.getFieldLoc(), D.getFieldLoc()};375      Check->addUsage(FD, FixLocation, SM);376    }377 378    return true;379  }380 381private:382  RenamerClangTidyCheck *Check;383  const SourceManager &SM;384  const bool AggressiveDependentMemberLookup;385};386 387} // namespace388 389RenamerClangTidyCheck::RenamerClangTidyCheck(StringRef CheckName,390                                             ClangTidyContext *Context)391    : ClangTidyCheck(CheckName, Context),392      AggressiveDependentMemberLookup(393          Options.get("AggressiveDependentMemberLookup", false)) {}394RenamerClangTidyCheck::~RenamerClangTidyCheck() = default;395 396void RenamerClangTidyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {397  Options.store(Opts, "AggressiveDependentMemberLookup",398                AggressiveDependentMemberLookup);399}400 401void RenamerClangTidyCheck::registerMatchers(MatchFinder *Finder) {402  Finder->addMatcher(translationUnitDecl(), this);403}404 405void RenamerClangTidyCheck::registerPPCallbacks(406    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {407  ModuleExpanderPP->addPPCallbacks(408      std::make_unique<RenamerClangTidyCheckPPCallbacks>(SM, this));409}410 411std::pair<RenamerClangTidyCheck::NamingCheckFailureMap::iterator, bool>412RenamerClangTidyCheck::addUsage(413    const RenamerClangTidyCheck::NamingCheckId &FailureId,414    SourceRange UsageRange, const SourceManager &SourceMgr) {415  // Do nothing if the provided range is invalid.416  if (UsageRange.isInvalid())417    return {NamingCheckFailures.end(), false};418 419  // Get the spelling location for performing the fix. This is necessary because420  // macros can map the same spelling location to different source locations,421  // and we only want to fix the token once, before it is expanded by the macro.422  SourceLocation FixLocation = UsageRange.getBegin();423  FixLocation = SourceMgr.getSpellingLoc(FixLocation);424  if (FixLocation.isInvalid())425    return {NamingCheckFailures.end(), false};426 427  // Skip if in system system header428  if (SourceMgr.isInSystemHeader(FixLocation))429    return {NamingCheckFailures.end(), false};430 431  auto EmplaceResult = NamingCheckFailures.try_emplace(FailureId);432  NamingCheckFailure &Failure = EmplaceResult.first->second;433 434  // Try to insert the identifier location in the Usages map, and bail out if it435  // is already in there436  if (!Failure.RawUsageLocs.insert(FixLocation).second)437    return EmplaceResult;438 439  if (Failure.FixStatus != RenamerClangTidyCheck::ShouldFixStatus::ShouldFix)440    return EmplaceResult;441 442  if (SourceMgr.isWrittenInScratchSpace(FixLocation))443    Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;444 445  if (!utils::rangeCanBeFixed(UsageRange, &SourceMgr))446    Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;447 448  return EmplaceResult;449}450 451void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl,452                                     SourceRange UsageRange,453                                     const SourceManager &SourceMgr) {454  if (SourceMgr.isInSystemHeader(Decl->getLocation()))455    return;456 457  if (hasNoName(Decl))458    return;459 460  // Ignore ClassTemplateSpecializationDecl which are creating duplicate461  // replacements with CXXRecordDecl.462  if (isa<ClassTemplateSpecializationDecl>(Decl))463    return;464 465  // We don't want to create a failure for every NamedDecl we find. Ideally466  // there is just one NamedDecl in every group of "related" NamedDecls that467  // becomes the failure. This NamedDecl and all of its related NamedDecls468  // become usages. E.g. Since NamedDecls are Redeclarable, only the canonical469  // NamedDecl becomes the failure and all redeclarations become usages.470  const NamedDecl *FailureDecl = getFailureForNamedDecl(Decl);471 472  std::optional<FailureInfo> MaybeFailure =473      getDeclFailureInfo(FailureDecl, SourceMgr);474  if (!MaybeFailure)475    return;476 477  const NamingCheckId FailureId(FailureDecl->getLocation(),478                                FailureDecl->getName());479 480  auto [FailureIter, NewFailure] = addUsage(FailureId, UsageRange, SourceMgr);481 482  if (FailureIter == NamingCheckFailures.end()) {483    // Nothing to do if the usage wasn't accepted.484    return;485  }486  if (!NewFailure) {487    // FailureInfo has already been provided.488    return;489  }490 491  // Update the stored failure with info regarding the FailureDecl.492  NamingCheckFailure &Failure = FailureIter->second;493  Failure.Info = std::move(*MaybeFailure);494 495  // Don't overwrite the failure status if it was already set.496  if (!Failure.shouldFix()) {497    return;498  }499  const IdentifierTable &Idents = FailureDecl->getASTContext().Idents;500  auto CheckNewIdentifier = Idents.find(Failure.Info.Fixup);501  if (CheckNewIdentifier != Idents.end()) {502    const IdentifierInfo *Ident = CheckNewIdentifier->second;503    if (Ident->isKeyword(getLangOpts()))504      Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;505    else if (Ident->hasMacroDefinition())506      Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;507  } else if (!isValidAsciiIdentifier(Failure.Info.Fixup)) {508    Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier;509  }510}511 512void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) {513  if (!Result.SourceManager) {514    // In principle SourceManager is not null but going only by the definition515    // of MatchResult it must be handled. Cannot rename anything without a516    // SourceManager.517    return;518  }519  RenamerClangTidyVisitor Visitor(this, *Result.SourceManager,520                                  AggressiveDependentMemberLookup);521  Visitor.TraverseAST(*Result.Context);522}523 524void RenamerClangTidyCheck::checkMacro(const Token &MacroNameTok,525                                       const MacroInfo *MI,526                                       const SourceManager &SourceMgr) {527  std::optional<FailureInfo> MaybeFailure =528      getMacroFailureInfo(MacroNameTok, SourceMgr);529  if (!MaybeFailure)530    return;531  FailureInfo &Info = *MaybeFailure;532  const StringRef Name = MacroNameTok.getIdentifierInfo()->getName();533  const NamingCheckId ID(MI->getDefinitionLoc(), Name);534  NamingCheckFailure &Failure = NamingCheckFailures[ID];535  const SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());536 537  if (!isValidAsciiIdentifier(Info.Fixup))538    Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier;539 540  Failure.Info = std::move(Info);541  addUsage(ID, Range, SourceMgr);542}543 544void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok,545                                        const MacroInfo *MI,546                                        const SourceManager &SourceMgr) {547  const StringRef Name = MacroNameTok.getIdentifierInfo()->getName();548  const NamingCheckId ID(MI->getDefinitionLoc(), Name);549 550  auto Failure = NamingCheckFailures.find(ID);551  if (Failure == NamingCheckFailures.end())552    return;553 554  const SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());555  addUsage(ID, Range, SourceMgr);556}557 558static std::string559getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus,560                    const std::string &Fixup) {561  if (Fixup.empty() ||562      FixStatus == RenamerClangTidyCheck::ShouldFixStatus::FixInvalidIdentifier)563    return "; cannot be fixed automatically";564  if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ShouldFix)565    return {};566  if (FixStatus >=567      RenamerClangTidyCheck::ShouldFixStatus::IgnoreFailureThreshold)568    return {};569  if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithKeyword)570    return "; cannot be fixed because '" + Fixup +571           "' would conflict with a keyword";572  if (FixStatus ==573      RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithMacroDefinition)574    return "; cannot be fixed because '" + Fixup +575           "' would conflict with a macro definition";576  llvm_unreachable("invalid ShouldFixStatus");577}578 579void RenamerClangTidyCheck::onEndOfTranslationUnit() {580  for (const auto &Pair : NamingCheckFailures) {581    const NamingCheckId &Decl = Pair.first;582    const NamingCheckFailure &Failure = Pair.second;583 584    if (Failure.Info.KindName.empty())585      continue;586 587    if (Failure.shouldNotify()) {588      auto DiagInfo = getDiagInfo(Decl, Failure);589      auto Diag = diag(Decl.first,590                       DiagInfo.Text + getDiagnosticSuffix(Failure.FixStatus,591                                                           Failure.Info.Fixup));592      DiagInfo.ApplyArgs(Diag);593 594      if (Failure.shouldFix()) {595        for (const auto &Loc : Failure.RawUsageLocs) {596          // We assume that the identifier name is made of one token only. This597          // is always the case as we ignore usages in macros that could build598          // identifier names by combining multiple tokens.599          //600          // For destructors, we already take care of it by remembering the601          // location of the start of the identifier and not the start of the602          // tilde.603          //604          // Other multi-token identifiers, such as operators are not checked at605          // all.606          Diag << FixItHint::CreateReplacement(SourceRange(Loc),607                                               Failure.Info.Fixup);608        }609      }610    }611  }612}613 614} // namespace clang::tidy615