clang-tools 19.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
17using namespace clang::ast_matchers;
18
19namespace clang::tidy::bugprone {
20
22 // Match all class declarations/definitions *EXCEPT*
23 // 1. implicit classes, e.g. `class A {};` has implicit `class A` inside `A`.
24 // 2. nested classes declared/defined inside another class.
25 // 3. template class declaration, template instantiation or
26 // specialization (NOTE: extern specialization is filtered out by
27 // `unless(hasAncestor(cxxRecordDecl()))`).
28 auto IsInSpecialization = hasAncestor(
29 decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
30 functionDecl(isExplicitTemplateSpecialization()))));
31 Finder->addMatcher(
32 cxxRecordDecl(
33 hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))),
34 unless(isImplicit()), unless(hasAncestor(cxxRecordDecl())),
35 unless(isInstantiated()), unless(IsInSpecialization),
36 unless(classTemplateSpecializationDecl()))
37 .bind("record_decl"),
38 this);
39
40 // Match all friend declarations. Classes used in friend declarations are not
41 // marked as referenced in AST. We need to record all record classes used in
42 // friend declarations.
43 Finder->addMatcher(friendDecl().bind("friend_decl"), this);
44}
45
47 const MatchFinder::MatchResult &Result) {
48 if (const auto *RecordDecl =
49 Result.Nodes.getNodeAs<CXXRecordDecl>("record_decl")) {
50 StringRef DeclName = RecordDecl->getName();
51 if (RecordDecl->isThisDeclarationADefinition()) {
52 DeclNameToDefinitions[DeclName].push_back(RecordDecl);
53 } else {
54 // If a declaration has no definition, the definition could be in another
55 // namespace (a wrong namespace).
56 // NOTE: even a declaration does have definition, we still need it to
57 // compare with other declarations.
58 DeclNameToDeclarations[DeclName].push_back(RecordDecl);
59 }
60 } else {
61 const auto *Decl = Result.Nodes.getNodeAs<FriendDecl>("friend_decl");
62 assert(Decl && "Decl is neither record_decl nor friend decl!");
63
64 // Classes used in friend declarations are not marked referenced in AST,
65 // so we need to check classes used in friend declarations manually to
66 // reduce the rate of false positive.
67 // For example, in
68 // \code
69 // struct A;
70 // struct B { friend A; };
71 // \endcode
72 // `A` will not be marked as "referenced" in the AST.
73 if (const TypeSourceInfo *Tsi = Decl->getFriendType()) {
74 QualType Desugared = Tsi->getType().getDesugaredType(*Result.Context);
75 FriendTypes.insert(Desugared.getTypePtr());
76 }
77 }
78}
79
80static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1,
81 const CXXRecordDecl *Decl2) {
82 const DeclContext *ParentDecl1 = Decl1->getLexicalParent();
83 const DeclContext *ParentDecl2 = Decl2->getLexicalParent();
84
85 // Since we only matched declarations whose parent is Namespace or
86 // TranslationUnit declaration, the parent should be either a translation unit
87 // or namespace.
88 if (ParentDecl1->getDeclKind() == Decl::TranslationUnit ||
89 ParentDecl2->getDeclKind() == Decl::TranslationUnit) {
90 return ParentDecl1 == ParentDecl2;
91 }
92 assert(ParentDecl1->getDeclKind() == Decl::Namespace &&
93 "ParentDecl1 declaration must be a namespace");
94 assert(ParentDecl2->getDeclKind() == Decl::Namespace &&
95 "ParentDecl2 declaration must be a namespace");
96 auto *Ns1 = NamespaceDecl::castFromDeclContext(ParentDecl1);
97 auto *Ns2 = NamespaceDecl::castFromDeclContext(ParentDecl2);
98 return Ns1->getOriginalNamespace() == Ns2->getOriginalNamespace();
99}
100
101static std::string getNameOfNamespace(const CXXRecordDecl *Decl) {
102 const auto *ParentDecl = Decl->getLexicalParent();
103 if (ParentDecl->getDeclKind() == Decl::TranslationUnit) {
104 return "(global)";
105 }
106 const auto *NsDecl = cast<NamespaceDecl>(ParentDecl);
107 std::string Ns;
108 llvm::raw_string_ostream OStream(Ns);
109 NsDecl->printQualifiedName(OStream);
110 OStream.flush();
111 return Ns.empty() ? "(global)" : Ns;
112}
113
115 // Iterate each group of declarations by name.
116 for (const auto &KeyValuePair : DeclNameToDeclarations) {
117 const auto &Declarations = KeyValuePair.second;
118 // If more than 1 declaration exists, we check if all are in the same
119 // namespace.
120 for (const auto *CurDecl : Declarations) {
121 if (CurDecl->hasDefinition() || CurDecl->isReferenced()) {
122 continue; // Skip forward declarations that are used/referenced.
123 }
124 if (FriendTypes.contains(CurDecl->getTypeForDecl())) {
125 continue; // Skip forward declarations referenced as friend.
126 }
127 if (CurDecl->getLocation().isMacroID() ||
128 CurDecl->getLocation().isInvalid()) {
129 continue;
130 }
131 // Compare with all other declarations with the same name.
132 for (const auto *Decl : Declarations) {
133 if (Decl == CurDecl) {
134 continue; // Don't compare with self.
135 }
136 if (!CurDecl->hasDefinition() &&
138 diag(CurDecl->getLocation(),
139 "declaration %0 is never referenced, but a declaration with "
140 "the same name found in another namespace '%1'")
141 << CurDecl << getNameOfNamespace(Decl);
142 diag(Decl->getLocation(), "a declaration of %0 is found here",
143 DiagnosticIDs::Note)
144 << Decl;
145 break; // FIXME: We only generate one warning for each declaration.
146 }
147 }
148 // Check if a definition in another namespace exists.
149 const auto DeclName = CurDecl->getName();
150 if (!DeclNameToDefinitions.contains(DeclName)) {
151 continue; // No definition in this translation unit, we can skip it.
152 }
153 // Make a warning for each definition with the same name (in other
154 // namespaces).
155 const auto &Definitions = DeclNameToDefinitions[DeclName];
156 for (const auto *Def : Definitions) {
157 diag(CurDecl->getLocation(),
158 "no definition found for %0, but a definition with "
159 "the same name %1 found in another namespace '%2'")
160 << CurDecl << Def << getNameOfNamespace(Def);
161 diag(Def->getLocation(), "a definition of %0 is found here",
162 DiagnosticIDs::Note)
163 << Def;
164 }
165 }
166 }
167}
168
169} // namespace clang::tidy::bugprone
const FunctionDecl * Decl
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static std::string getNameOfNamespace(const CXXRecordDecl *Decl)
static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1, const CXXRecordDecl *Decl2)