clang-tools 6.0.0

ForwardDeclarationNamespaceCheck.cpp
Go to the documentation of this file.
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
clang::ast_matchers
Definition: ClangTidyDiagnosticConsumer.h:27
clang::tidy::bugprone::haveSameNamespaceOrTranslationUnit
static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1, const CXXRecordDecl *Decl2)
Definition: ForwardDeclarationNamespaceCheck.cpp:83
clang::tidy::bugprone::getNameOfNamespace
static std::string getNameOfNamespace(const CXXRecordDecl *Decl)
Definition: ForwardDeclarationNamespaceCheck.cpp:104
clang
Definition: AndroidTidyModule.cpp:28
ForwardDeclarationNamespaceCheck.h