clang-tools  14.0.0git
ForwardDeclarationNamespaceCheck.cpp
Go to the documentation of this file.
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 
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 
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 
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.contains(CurDecl->getTypeForDecl())) {
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() &&
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
ForwardDeclarationNamespaceCheck.h
clang::ast_matchers
Definition: AbseilMatcher.h:14
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::tidy::bugprone::getNameOfNamespace
static std::string getNameOfNamespace(const CXXRecordDecl *Decl)
Definition: ForwardDeclarationNamespaceCheck.cpp:103
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:259
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::isExplicitTemplateSpecialization
bool isExplicitTemplateSpecialization(const NamedDecl *D)
Indicates if D is an explicit template specialization, e.g.
Definition: AST.cpp:158
clang::tidy::bugprone::haveSameNamespaceOrTranslationUnit
static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1, const CXXRecordDecl *Decl2)
Definition: ForwardDeclarationNamespaceCheck.cpp:82