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