Index: clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt +++ clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt @@ -6,6 +6,7 @@ AssignOperatorSignatureCheck.cpp BoolPointerImplicitConversionCheck.cpp DefinitionsInHeadersCheck.cpp + ForwardDeclarationNamespaceCheck.cpp InaccurateEraseCheck.cpp IncorrectRoundings.cpp InefficientAlgorithmCheck.cpp Index: clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h =================================================================== --- clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h +++ clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h @@ -0,0 +1,59 @@ +//===--- ForwardDeclarationNamespaceCheck.h - clang-tidy --------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H + +#include +#include +#include "llvm/ADT/SmallPtrSet.h" +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks if an unused forward declaration is in a wrong namespace. +/// +/// The check inspects all unused forward declarations and checks if there is +/// any declaration/definition with the same name, which could indicate +/// that the forward declaration is potentially in a wrong namespace. +/// +/// \code +/// namespace na { struct A; } +/// namespace nb { struct A {} }; +/// nb::A a; +/// // warning : no definition found for 'A', but a definition with the same +/// name 'A' found in another namespace 'nb::' +/// \endcode +/// +/// This check can only generate warnings, but it can't suggest fixes at this +/// point. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-forward-declaration-namespace.html +class ForwardDeclarationNamespaceCheck : public ClangTidyCheck { +public: + ForwardDeclarationNamespaceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + llvm::StringMap> DeclNameToDefinitions; + llvm::StringMap> DeclNameToDeclarations; + llvm::SmallPtrSet FriendTypes; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H Index: clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp =================================================================== --- clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp +++ clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp @@ -0,0 +1,174 @@ +//===--- ForwardDeclarationNamespaceCheck.cpp - clang-tidy ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForwardDeclarationNamespaceCheck.h" +#include +#include +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +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("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("friend_decl"); + assert(Decl && "Decl is neither record_decl nor friend decl!"); + + // Classes used in friend delarations 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(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.count(CurDecl->getTypeForDecl()) != 0) { + 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->getName() << getNameOfNamespace(Decl); + diag(Decl->getLocation(), "a declaration of '%0' is found here", + DiagnosticIDs::Note) + << Decl->getName(); + 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->getName() << Def->getName() << getNameOfNamespace(Def); + diag(Def->getLocation(), "a definition of '%0' is found here", + DiagnosticIDs::Note) + << Def->getName(); + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang Index: clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp +++ clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp @@ -15,6 +15,7 @@ #include "AssignOperatorSignatureCheck.h" #include "BoolPointerImplicitConversionCheck.h" #include "DefinitionsInHeadersCheck.h" +#include "ForwardDeclarationNamespaceCheck.h" #include "InaccurateEraseCheck.h" #include "IncorrectRoundings.h" #include "InefficientAlgorithmCheck.h" @@ -55,6 +56,8 @@ "misc-bool-pointer-implicit-conversion"); CheckFactories.registerCheck( "misc-definitions-in-headers"); + CheckFactories.registerCheck( + "misc-forward-declaration-namespace"); CheckFactories.registerCheck( "misc-inaccurate-erase"); CheckFactories.registerCheck( Index: clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst @@ -51,6 +51,7 @@ misc-assign-operator-signature misc-bool-pointer-implicit-conversion misc-definitions-in-headers + misc-forward-declaration-namespace misc-inaccurate-erase misc-incorrect-roundings misc-inefficient-algorithm Index: clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst =================================================================== --- clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst +++ clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-forward-declaration-namespace + +misc-forward-declaration-namespace +================================== + +Checks if an unused forward declaration is in a wrong namespace. + +The check inspects all unused forward declarations and checks if there is any +declaration/definition with the same name existing, which could indicate that +the forward declaration is in a potentially wrong namespace. + +.. code:: c++ + namespace na { struct A; } + namespace nb { struct A {}; } + nb::A a; + // warning : no definition found for 'A', but a definition with the same name + // 'A' found in another namespace 'nb::' + +This check can only generate warnings, but it can't suggest a fix at this point. Index: clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp =================================================================== --- clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp +++ clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp @@ -0,0 +1,163 @@ +// RUN: %check_clang_tidy %s misc-forward-declaration-namespace %t + +namespace { +// This is a declaration in a wrong namespace. +class T_A; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_A' is never referenced, but a declaration with the same name found in another namespace 'na' [misc-forward-declaration-namespace] +// CHECK-MESSAGES: note: a declaration of 'T_A' is found here +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for 'T_A', but a definition with the same name 'T_A' found in another namespace '(global)' [misc-forward-declaration-namespace] +// CHECK-MESSAGES: note: a definition of 'T_A' is found here +} + +namespace na { +// This is a declaration in a wrong namespace. +class T_A; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_A' is never referenced, but a declaration with the same name found in another namespace '(anonymous)' +// CHECK-MESSAGES: note: a declaration of 'T_A' is found here +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for 'T_A', but a definition with the same name 'T_A' found in another namespace '(global)' +// CHECK-MESSAGES: note: a definition of 'T_A' is found here +} + +class T_A; + +class T_A { + int x; +}; + +class NESTED; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: no definition found for 'NESTED', but a definition with the same name 'NESTED' found in another namespace '(anonymous namespace)::nq::(anonymous)' +// CHECK-MESSAGES: note: a definition of 'NESTED' is found here + +namespace { +namespace nq { +namespace { +class NESTED {}; +} +} +} + +namespace na { +class T_B; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_B' is never referenced, but a declaration with the same name found in another namespace 'nb' +// CHECK-MESSAGES: note: a declaration of 'T_B' is found here +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for 'T_B', but a definition with the same name 'T_B' found in another namespace 'nb' +// CHECK-MESSAGES: note: a definition of 'T_B' is found here +} + +namespace nb { +class T_B; +} + +namespace nb { +class T_B { + int x; +}; +} + +namespace na { +class T_B; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_B' is never referenced, but a declaration with the same name found in another namespace 'nb' +// CHECK-MESSAGES: note: a declaration of 'T_B' is found here +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for 'T_B', but a definition with the same name 'T_B' found in another namespace 'nb' +// CHECK-MESSAGES: note: a definition of 'T_B' is found here +} + +// A simple forward declaration. Although it is never used, but no declaration +// with the same name is found in other namespace. +class OUTSIDER; + +namespace na { +// This class is referenced declaration, we don't generate warning. +class OUTSIDER_1; +} + +void f(na::OUTSIDER_1); + +namespace nc { +// This class is referenced as friend in OOP. +class OUTSIDER_1; + +class OOP { + friend struct OUTSIDER_1; +}; +} + +namespace nd { +class OUTSIDER_1; +void f(OUTSIDER_1 *); +} + +namespace nb { +class OUTSIDER_1; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'OUTSIDER_1' is never referenced, but a declaration with the same name found in another namespace 'na' +// CHECK-MESSAGES: note: a declaration of 'OUTSIDER_1' is found here +} + + +namespace na { +template +class T_C; +} + +namespace nb { +// FIXME: this is an error, but we don't consider template class declaration +// now. +template +class T_C; +} + +namespace na { +template +class T_C { + int x; +}; +} + +namespace na { + +template +class T_TEMP { + template + struct rebind { typedef T_TEMP<_Tp1> other; }; +}; + +// We ignore class template specialization. +template class T_TEMP; +} + +namespace nb { + +template +class T_TEMP_1 { + template + struct rebind { typedef T_TEMP_1<_Tp1> other; }; +}; + +// We ignore class template specialization. +extern template class T_TEMP_1; +} + +namespace nd { +class D; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'D' is never referenced, but a declaration with the same name found in another namespace 'nd::ne' +// CHECK-MESSAGES: note: a declaration of 'D' is found here +} + +namespace nd { +namespace ne { +class D; +} +} + +int f(nd::ne::D &d); + + +// This should be ignored by the check. +template +class Observer { + class Impl; +}; + +template +class Observer::Impl { +};