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