forked from OSchip/llvm-project
174 lines
7.1 KiB
C++
174 lines
7.1 KiB
C++
//===--- ForwardDeclarationNamespaceCheck.cpp - clang-tidy ------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ForwardDeclarationNamespaceCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include <stack>
|
|
#include <string>
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace bugprone {
|
|
|
|
void ForwardDeclarationNamespaceCheck::registerMatchers(MatchFinder *Finder) {
|
|
// Match all class declarations/definitions *EXCEPT*
|
|
// 1. implicit classes, e.g. `class A {};` has implicit `class A` inside `A`.
|
|
// 2. nested classes declared/defined inside another class.
|
|
// 3. template class declaration, template instantiation or
|
|
// specialization (NOTE: extern specialization is filtered out by
|
|
// `unless(hasAncestor(cxxRecordDecl()))`).
|
|
auto IsInSpecialization = hasAncestor(
|
|
decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
|
|
functionDecl(isExplicitTemplateSpecialization()))));
|
|
Finder->addMatcher(
|
|
cxxRecordDecl(
|
|
hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))),
|
|
unless(isImplicit()), unless(hasAncestor(cxxRecordDecl())),
|
|
unless(isInstantiated()), unless(IsInSpecialization),
|
|
unless(classTemplateSpecializationDecl()))
|
|
.bind("record_decl"),
|
|
this);
|
|
|
|
// Match all friend declarations. Classes used in friend declarations are not
|
|
// marked as referenced in AST. We need to record all record classes used in
|
|
// friend declarations.
|
|
Finder->addMatcher(friendDecl().bind("friend_decl"), this);
|
|
}
|
|
|
|
void ForwardDeclarationNamespaceCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
if (const auto *RecordDecl =
|
|
Result.Nodes.getNodeAs<CXXRecordDecl>("record_decl")) {
|
|
StringRef DeclName = RecordDecl->getName();
|
|
if (RecordDecl->isThisDeclarationADefinition()) {
|
|
DeclNameToDefinitions[DeclName].push_back(RecordDecl);
|
|
} else {
|
|
// If a declaration has no definition, the definition could be in another
|
|
// namespace (a wrong namespace).
|
|
// NOTE: even a declaration does have definition, we still need it to
|
|
// compare with other declarations.
|
|
DeclNameToDeclarations[DeclName].push_back(RecordDecl);
|
|
}
|
|
} else {
|
|
const auto *Decl = Result.Nodes.getNodeAs<FriendDecl>("friend_decl");
|
|
assert(Decl && "Decl is neither record_decl nor friend decl!");
|
|
|
|
// Classes used in friend declarations are not marked referenced in AST,
|
|
// so we need to check classes used in friend declarations manually to
|
|
// reduce the rate of false positive.
|
|
// For example, in
|
|
// \code
|
|
// struct A;
|
|
// struct B { friend A; };
|
|
// \endcode
|
|
// `A` will not be marked as "referenced" in the AST.
|
|
if (const TypeSourceInfo *Tsi = Decl->getFriendType()) {
|
|
QualType Desugared = Tsi->getType().getDesugaredType(*Result.Context);
|
|
FriendTypes.insert(Desugared.getTypePtr());
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1,
|
|
const CXXRecordDecl *Decl2) {
|
|
const DeclContext *ParentDecl1 = Decl1->getLexicalParent();
|
|
const DeclContext *ParentDecl2 = Decl2->getLexicalParent();
|
|
|
|
// Since we only matched declarations whose parent is Namespace or
|
|
// TranslationUnit declaration, the parent should be either a translation unit
|
|
// or namespace.
|
|
if (ParentDecl1->getDeclKind() == Decl::TranslationUnit ||
|
|
ParentDecl2->getDeclKind() == Decl::TranslationUnit) {
|
|
return ParentDecl1 == ParentDecl2;
|
|
}
|
|
assert(ParentDecl1->getDeclKind() == Decl::Namespace &&
|
|
"ParentDecl1 declaration must be a namespace");
|
|
assert(ParentDecl2->getDeclKind() == Decl::Namespace &&
|
|
"ParentDecl2 declaration must be a namespace");
|
|
auto *Ns1 = NamespaceDecl::castFromDeclContext(ParentDecl1);
|
|
auto *Ns2 = NamespaceDecl::castFromDeclContext(ParentDecl2);
|
|
return Ns1->getOriginalNamespace() == Ns2->getOriginalNamespace();
|
|
}
|
|
|
|
static std::string getNameOfNamespace(const CXXRecordDecl *Decl) {
|
|
const auto *ParentDecl = Decl->getLexicalParent();
|
|
if (ParentDecl->getDeclKind() == Decl::TranslationUnit) {
|
|
return "(global)";
|
|
}
|
|
const auto *NsDecl = cast<NamespaceDecl>(ParentDecl);
|
|
std::string Ns;
|
|
llvm::raw_string_ostream OStream(Ns);
|
|
NsDecl->printQualifiedName(OStream);
|
|
OStream.flush();
|
|
return Ns.empty() ? "(global)" : Ns;
|
|
}
|
|
|
|
void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() {
|
|
// Iterate each group of declarations by name.
|
|
for (const auto &KeyValuePair : DeclNameToDeclarations) {
|
|
const auto &Declarations = KeyValuePair.second;
|
|
// If more than 1 declaration exists, we check if all are in the same
|
|
// namespace.
|
|
for (const auto *CurDecl : Declarations) {
|
|
if (CurDecl->hasDefinition() || CurDecl->isReferenced()) {
|
|
continue; // Skip forward declarations that are used/referenced.
|
|
}
|
|
if (FriendTypes.contains(CurDecl->getTypeForDecl())) {
|
|
continue; // Skip forward declarations referenced as friend.
|
|
}
|
|
if (CurDecl->getLocation().isMacroID() ||
|
|
CurDecl->getLocation().isInvalid()) {
|
|
continue;
|
|
}
|
|
// Compare with all other declarations with the same name.
|
|
for (const auto *Decl : Declarations) {
|
|
if (Decl == CurDecl) {
|
|
continue; // Don't compare with self.
|
|
}
|
|
if (!CurDecl->hasDefinition() &&
|
|
!haveSameNamespaceOrTranslationUnit(CurDecl, Decl)) {
|
|
diag(CurDecl->getLocation(),
|
|
"declaration %0 is never referenced, but a declaration with "
|
|
"the same name found in another namespace '%1'")
|
|
<< CurDecl << getNameOfNamespace(Decl);
|
|
diag(Decl->getLocation(), "a declaration of %0 is found here",
|
|
DiagnosticIDs::Note)
|
|
<< Decl;
|
|
break; // FIXME: We only generate one warning for each declaration.
|
|
}
|
|
}
|
|
// Check if a definition in another namespace exists.
|
|
const auto DeclName = CurDecl->getName();
|
|
if (DeclNameToDefinitions.find(DeclName) == DeclNameToDefinitions.end()) {
|
|
continue; // No definition in this translation unit, we can skip it.
|
|
}
|
|
// Make a warning for each definition with the same name (in other
|
|
// namespaces).
|
|
const auto &Definitions = DeclNameToDefinitions[DeclName];
|
|
for (const auto *Def : Definitions) {
|
|
diag(CurDecl->getLocation(),
|
|
"no definition found for %0, but a definition with "
|
|
"the same name %1 found in another namespace '%2'")
|
|
<< CurDecl << Def << getNameOfNamespace(Def);
|
|
diag(Def->getLocation(), "a definition of %0 is found here",
|
|
DiagnosticIDs::Note)
|
|
<< Def;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace bugprone
|
|
} // namespace tidy
|
|
} // namespace clang
|