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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp