clang-tools  3.9.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 
11 #include <stack>
12 #include <string>
13 #include "clang/AST/ASTContext.h"
14 #include "clang/AST/Decl.h"
15 #include "clang/ASTMatchers/ASTMatchFinder.h"
16 #include "clang/ASTMatchers/ASTMatchers.h"
17 
18 using namespace clang::ast_matchers;
19 
20 namespace clang {
21 namespace tidy {
22 namespace misc {
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 misc
173 } // namespace tidy
174 } // namespace clang
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:210
static std::string getNameOfNamespace(const CXXRecordDecl *Decl)
static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1, const CXXRecordDecl *Decl2)
const NamedDecl * Result
Definition: USRFinder.cpp:137