brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.1 KiB · 11270e7 Raw
170 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 "ForwardDeclarationNamespaceCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/Decl.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include <string>15 16using namespace clang::ast_matchers;17 18namespace clang::tidy::bugprone {19 20void ForwardDeclarationNamespaceCheck::registerMatchers(MatchFinder *Finder) {21  // Match all class declarations/definitions *EXCEPT*22  // 1. implicit classes, e.g. `class A {};` has implicit `class A` inside `A`.23  // 2. nested classes declared/defined inside another class.24  // 3. template class declaration, template instantiation or25  //    specialization (NOTE: extern specialization is filtered out by26  //    `unless(hasAncestor(cxxRecordDecl()))`).27  auto IsInSpecialization = hasAncestor(28      decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),29                 functionDecl(isExplicitTemplateSpecialization()))));30  Finder->addMatcher(31      cxxRecordDecl(32          hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))),33          unless(isImplicit()), unless(hasAncestor(cxxRecordDecl())),34          unless(isInstantiated()), unless(IsInSpecialization),35          unless(classTemplateSpecializationDecl()))36          .bind("record_decl"),37      this);38 39  // Match all friend declarations. Classes used in friend declarations are not40  // marked as referenced in AST. We need to record all record classes used in41  // friend declarations.42  Finder->addMatcher(friendDecl().bind("friend_decl"), this);43}44 45void ForwardDeclarationNamespaceCheck::check(46    const MatchFinder::MatchResult &Result) {47  if (const auto *RecordDecl =48          Result.Nodes.getNodeAs<CXXRecordDecl>("record_decl")) {49    const StringRef DeclName = RecordDecl->getName();50    if (RecordDecl->isThisDeclarationADefinition()) {51      DeclNameToDefinitions[DeclName].push_back(RecordDecl);52    } else {53      // If a declaration has no definition, the definition could be in another54      // namespace (a wrong namespace).55      // NOTE: even a declaration does have definition, we still need it to56      // compare with other declarations.57      DeclNameToDeclarations[DeclName].push_back(RecordDecl);58    }59  } else {60    const auto *Decl = Result.Nodes.getNodeAs<FriendDecl>("friend_decl");61    assert(Decl && "Decl is neither record_decl nor friend decl!");62 63    // Classes used in friend declarations are not marked referenced in AST,64    // so we need to check classes used in friend declarations manually to65    // reduce the rate of false positive.66    // For example, in67    //    \code68    //      struct A;69    //      struct B { friend A; };70    //    \endcode71    // `A` will not be marked as "referenced" in the AST.72    if (const TypeSourceInfo *Tsi = Decl->getFriendType())73      FriendTypes.insert(74          Tsi->getType()->getCanonicalTypeUnqualified().getTypePtr());75  }76}77 78static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1,79                                               const CXXRecordDecl *Decl2) {80  const DeclContext *ParentDecl1 = Decl1->getLexicalParent();81  const DeclContext *ParentDecl2 = Decl2->getLexicalParent();82 83  // Since we only matched declarations whose parent is Namespace or84  // TranslationUnit declaration, the parent should be either a translation unit85  // or namespace.86  if (ParentDecl1->getDeclKind() == Decl::TranslationUnit ||87      ParentDecl2->getDeclKind() == Decl::TranslationUnit) {88    return ParentDecl1 == ParentDecl2;89  }90  assert(ParentDecl1->getDeclKind() == Decl::Namespace &&91         "ParentDecl1 declaration must be a namespace");92  assert(ParentDecl2->getDeclKind() == Decl::Namespace &&93         "ParentDecl2 declaration must be a namespace");94  auto *Ns1 = NamespaceDecl::castFromDeclContext(ParentDecl1);95  auto *Ns2 = NamespaceDecl::castFromDeclContext(ParentDecl2);96  return Ns1->getFirstDecl() == Ns2->getFirstDecl();97}98 99static std::string getNameOfNamespace(const CXXRecordDecl *Decl) {100  const auto *ParentDecl = Decl->getLexicalParent();101  if (ParentDecl->getDeclKind() == Decl::TranslationUnit) {102    return "(global)";103  }104  const auto *NsDecl = cast<NamespaceDecl>(ParentDecl);105  std::string Ns;106  llvm::raw_string_ostream OStream(Ns);107  NsDecl->printQualifiedName(OStream);108  return Ns.empty() ? "(global)" : Ns;109}110 111void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() {112  // Iterate each group of declarations by name.113  for (const auto &KeyValuePair : DeclNameToDeclarations) {114    const auto &Declarations = KeyValuePair.second;115    // If more than 1 declaration exists, we check if all are in the same116    // namespace.117    for (const auto *CurDecl : Declarations) {118      if (CurDecl->hasDefinition() || CurDecl->isReferenced()) {119        continue; // Skip forward declarations that are used/referenced.120      }121      if (FriendTypes.contains(CurDecl->getASTContext()122                                   .getCanonicalTagType(CurDecl)123                                   ->getTypePtr())) {124        continue; // Skip forward declarations referenced as friend.125      }126      if (CurDecl->getLocation().isMacroID() ||127          CurDecl->getLocation().isInvalid()) {128        continue;129      }130      // Compare with all other declarations with the same name.131      for (const auto *Decl : Declarations) {132        if (Decl == CurDecl) {133          continue; // Don't compare with self.134        }135        if (!CurDecl->hasDefinition() &&136            !haveSameNamespaceOrTranslationUnit(CurDecl, Decl)) {137          diag(CurDecl->getLocation(),138               "declaration %0 is never referenced, but a declaration with "139               "the same name found in another namespace '%1'")140              << CurDecl << getNameOfNamespace(Decl);141          diag(Decl->getLocation(), "a declaration of %0 is found here",142               DiagnosticIDs::Note)143              << Decl;144          break; // FIXME: We only generate one warning for each declaration.145        }146      }147      // Check if a definition in another namespace exists.148      const auto DeclName = CurDecl->getName();149      auto It = DeclNameToDefinitions.find(DeclName);150      if (It == DeclNameToDefinitions.end()) {151        continue; // No definition in this translation unit, we can skip it.152      }153      // Make a warning for each definition with the same name (in other154      // namespaces).155      const auto &Definitions = It->second;156      for (const auto *Def : Definitions) {157        diag(CurDecl->getLocation(),158             "no definition found for %0, but a definition with "159             "the same name %1 found in another namespace '%2'")160            << CurDecl << Def << getNameOfNamespace(Def);161        diag(Def->getLocation(), "a definition of %0 is found here",162             DiagnosticIDs::Note)163            << Def;164      }165    }166  }167}168 169} // namespace clang::tidy::bugprone170