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