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 
registerMatchers(MatchFinder * Finder)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 
check(const MatchFinder::MatchResult & Result)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 declarations 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 
haveSameNamespaceOrTranslationUnit(const CXXRecordDecl * Decl1,const CXXRecordDecl * Decl2)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 
getNameOfNamespace(const CXXRecordDecl * Decl)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 
onEndOfTranslationUnit()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