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