Index: clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tidy/misc/CMakeLists.txt +++ clang-tidy/misc/CMakeLists.txt @@ -25,6 +25,7 @@ UnusedParametersCheck.cpp UnusedRAIICheck.cpp UniqueptrResetReleaseCheck.cpp + VirtualNearMissCheck.cpp LINK_LIBS clangAST Index: clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tidy/misc/MiscTidyModule.cpp +++ clang-tidy/misc/MiscTidyModule.cpp @@ -33,6 +33,7 @@ #include "UnusedAliasDeclsCheck.h" #include "UnusedParametersCheck.h" #include "UnusedRAIICheck.h" +#include "VirtualNearMissCheck.h" namespace clang { namespace tidy { @@ -84,6 +85,8 @@ CheckFactories.registerCheck( "misc-unused-parameters"); CheckFactories.registerCheck("misc-unused-raii"); + CheckFactories.registerCheck( + "misc-virtual-near-miss"); } }; Index: clang-tidy/misc/VirtualNearMissCheck.h =================================================================== --- /dev/null +++ clang-tidy/misc/VirtualNearMissCheck.h @@ -0,0 +1,65 @@ +//===--- VirtualNearMissCheck.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_VIRTUAL_NEAR_MISS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H + +#include "../ClangTidy.h" +#include +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// \brief Checks for near miss of virtual methods. +/// +/// For a method in a derived class, this check looks for virtual method with a +/// very similar name and an identical signature defined in a base class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-virtual-near-miss.html +class VirtualNearMissCheck : public ClangTidyCheck { +public: + VirtualNearMissCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Check if the given method is possible to be overridden by some other + /// method. + /// + /// It should look up the PossibleMap or update it. + bool isPossibleToBeOverridden(const CXXMethodDecl *BaseMD); + + /// Check if the given base method is overridden by some methods in the given + /// derived class. + /// + /// It should look up the OverriddenMap or update it. + bool isOverriddenByDerivedClass(const CXXMethodDecl *BaseMD, + const CXXRecordDecl *DerivedRD); + + /// key: the unique ID of a method; + /// value: whether the method is possible to be overridden. + std::map PossibleMap; + + /// key: + /// value: whether the base method is overridden by some method in the derived + /// class. + std::map, bool> OverriddenMap; + + const unsigned EditDistanceThreshold = 1; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H Index: clang-tidy/misc/VirtualNearMissCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/misc/VirtualNearMissCheck.cpp @@ -0,0 +1,271 @@ +//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VirtualNearMissCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds out if the given method overrides some method. +bool isOverrideMethod(const CXXMethodDecl *MD) { + return MD->size_overridden_methods() > 0 || MD->hasAttr(); +} + +/// Checks whether the return types are covariant, according to +/// C++[class.virtual]p7. +/// +/// Similar with clang::Sema::CheckOverridingFunctionReturnType. +/// \returns true if the return types of BaseMD and DerivedMD are covariant. +bool checkOverridingFunctionReturnType(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + QualType BaseReturnTy = + BaseMD->getType()->getAs()->getReturnType(); + QualType DerivedReturnTy = + DerivedMD->getType()->getAs()->getReturnType(); + + if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType()) + return false; + + // Check if return types are identical + if (Context->hasSameType(DerivedReturnTy, BaseReturnTy)) + return true; + + /// Check if the return types are covariant. + /// BTy is the class type in return type of BaseMD. For example, + /// B* Base::md() + /// While BRD is the declaration of B. + QualType BTy, DTy; + const CXXRecordDecl *BRD, *DRD; + + // Both types must be pointers or references to classes. + if (const auto *DerivedPT = DerivedReturnTy->getAs()) { + if (const auto *BasePT = BaseReturnTy->getAs()) { + DTy = DerivedPT->getPointeeType(); + BTy = BasePT->getPointeeType(); + } + } else if (const auto *DerivedRT = DerivedReturnTy->getAs()) { + if (const auto *BaseRT = BaseReturnTy->getAs()) { + DTy = DerivedRT->getPointeeType(); + BTy = BaseRT->getPointeeType(); + } + } + + // The return types aren't either both pointers or references to a class type. + if (DTy.isNull()) { + return false; + } + + DRD = DTy->getAsCXXRecordDecl(); + BRD = BTy->getAsCXXRecordDecl(); + if (DRD == nullptr || BRD == nullptr) + return false; + + if (DRD == BRD) + return true; + + if (!Context->hasSameUnqualifiedType(DTy, BTy)) { + // Begin checking whether the conversion from D to B is valid. + CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + + // Check whether D is derived from B, and fill in a CXXBasePaths object. + if (!DRD->isDerivedFrom(BRD, Paths)) + return false; + + // Check ambiguity. + if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType())) + return false; + + // Check accessibility. + // FIXME: We currently only support checking if B is accessible base class + // of D, or D is the same class which DerivedMD is in. + bool IsItself = DRD == DerivedMD->getParent(); + bool HasPublicAccess = false; + for (const auto &Path : Paths) { + if (Path.Access == AS_public) { + HasPublicAccess = true; + } + } + if (!HasPublicAccess && !IsItself) + return false; + // End checking conversion from D to B. + } + + // Both pointers or references should have the same cv-qualification. + if (DerivedReturnTy.getLocalCVRQualifiers() != + BaseReturnTy.getLocalCVRQualifiers()) + return false; + + // The class type D should have the same cv-qualification as or less + // cv-qualification than the class type B. + if (DTy.isMoreQualifiedThan(BTy)) + return false; + + return true; +} + +/// \returns true if the param types are the same. +bool checkParamTypes(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + unsigned NumParamA = BaseMD->getNumParams(); + unsigned NumParamB = DerivedMD->getNumParams(); + if (NumParamA != NumParamB) + return false; + + for (unsigned I = 0; I < NumParamA; I++) { + if (BaseMD->getParamDecl(I)->getType() != + DerivedMD->getParamDecl(I)->getType()) + return false; + } + return true; +} + +/// \returns true if derived method can override base method except for the +/// name. +bool checkOverrideWithoutName(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->getTypeQualifiers() != DerivedMD->getTypeQualifiers()) + return false; + + if (BaseMD->isStatic() != DerivedMD->isStatic()) + return false; + + if (BaseMD->getAccess() != DerivedMD->getAccess()) + return false; + + if (BaseMD->getType() == DerivedMD->getType()) + return true; + + // Now the function types are not identical. Then check if the return types + // are covariant and if the param types are the same. + if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD)) + return false; + return checkParamTypes(BaseMD, DerivedMD); +} + +/// Check whether BaseMD overrides DerivedMD. +/// +/// Prerequisite: the class which BaseMD is in should be a base class of that +/// DerivedMD is in. +bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->getNameAsString() != DerivedMD->getNameAsString()) + return false; + + if (!checkParamTypes(BaseMD, DerivedMD)) + return false; + + return true; +} + +/// Generate unique ID for given MethodDecl. +/// +/// The Id is used as key for 'PossibleMap'. +/// Typical Id: "Base::func void (void)" +std::string generateMethodId(const CXXMethodDecl *MD) { + return MD->getQualifiedNameAsString() + " " + MD->getType().getAsString(); +} + +bool VirtualNearMissCheck::isPossibleToBeOverridden( + const CXXMethodDecl *BaseMD) { + std::string Id = generateMethodId(BaseMD); + auto Iter = PossibleMap.find(Id); + if (Iter != PossibleMap.end()) { + return Iter->second; + } + + bool IsPossible = !BaseMD->isImplicit() && !isa(BaseMD) && + BaseMD->isVirtual(); + PossibleMap[Id] = IsPossible; + return IsPossible; +} + +bool VirtualNearMissCheck::isOverriddenByDerivedClass( + const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) { + auto Key = std::make_pair(generateMethodId(BaseMD), + DerivedRD->getQualifiedNameAsString()); + auto Iter = OverriddenMap.find(Key); + if (Iter != OverriddenMap.end()) { + return Iter->second; + } + + bool IsOverridden = false; + for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) { + if (!isOverrideMethod(DerivedMD)) + continue; + + if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) { + IsOverridden = true; + break; + } + } + OverriddenMap[Key] = IsOverridden; + return IsOverridden; +} + +void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxMethodDecl(unless(anyOf(isOverride(), isImplicit(), + cxxConstructorDecl()))) + .bind("method"), + this); +} + +void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { + const auto *DerivedMD = Result.Nodes.getNodeAs("method"); + assert(DerivedMD != nullptr); + + if (DerivedMD->isStatic()) + return; + + const ASTContext *Context = Result.Context; + + const auto *DerivedRD = DerivedMD->getParent(); + + for (const auto &BaseSpec : DerivedRD->bases()) { + if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) { + for (const auto *BaseMD : BaseRD->methods()) { + if (!isPossibleToBeOverridden(BaseMD)) + continue; + + if (isOverriddenByDerivedClass(BaseMD, DerivedRD)) + continue; + + unsigned EditDistance = + BaseMD->getName().edit_distance(DerivedMD->getName()); + if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) { + if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) { + // A "virtual near miss" is found. + diag(DerivedMD->getLocStart(), + "'%0' has a similar name and the same " + "signature with virtual method '%1'. " + "Did you meant to override it?") + << DerivedMD->getQualifiedNameAsString() + << BaseMD->getQualifiedNameAsString(); + } + } + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -56,6 +56,7 @@ misc-unused-alias-decls misc-unused-parameters misc-unused-raii + misc-virtual-near-miss modernize-loop-convert modernize-make-unique modernize-pass-by-value Index: docs/clang-tidy/checks/misc-virtual-near-miss.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/misc-virtual-near-miss.rst @@ -0,0 +1,17 @@ +misc-virtual-near-miss +====================== + +Warn if a function is a near miss (ie. the name is very similar and the function signiture is the same) to a virtual function from a base class. + +Example: + +.. code-block:: c++ + + struct Base { + virtual void func(); + }; + + struct Derived : Base { + virtual funk(); + // warning: Do you want to override 'func'? + }; Index: test/clang-tidy/misc-virtual-near-miss.cpp =================================================================== --- /dev/null +++ test/clang-tidy/misc-virtual-near-miss.cpp @@ -0,0 +1,65 @@ +// RUN: %check_clang_tidy %s misc-virtual-near-miss %t + +struct Base { + virtual void func(); + virtual void gunk(); +}; + +struct Derived : Base { + // Should not warn "do you want to override 'gunk'?", because gunk is already + // overriden by this class. + virtual void funk(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Derived::funk' has a similar name and the same signature with virtual method 'Base::func'. Did you meant to override it? [misc-virtual-near-miss] + + void func2(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Derived::func2' has a similar name and the same signature with virtual method 'Base::func'. Did you meant to override it? + + void func22(); // Should not warn. + + void gunk(); // Should not warn: gunk is override. + + void fun(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Derived::fun' has a similar name and the same signature with virtual method 'Base::func'. Did you meant to override it? +}; + +class Father { +public: + Father(); + virtual void func(); + virtual Father *create(int i); + virtual Base &&generate(); +}; + +class Mother { +public: + Mother(); + static void method(); + virtual int method(int argc, const char **argv); + virtual int method(int argc) const; +}; + +class Child : Father, Mother { +public: + Child(); + + virtual void func2(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::func2' has a similar name and the same signature with virtual method 'Father::func'. Did you meant to override it? + + int methoe(int x, char **strs); // Should not warn: parameter types don't match. + + int methoe(int x); // Should not warn: method is not const. + + void methof(int x, const char **strs); // Should not warn: return types don't match. + + int methoh(int x, const char **strs); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::methoh' has a similar name and the same signature with virtual method 'Mother::method'. Did you meant to override it? + + virtual Child *creat(int i); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::creat' has a similar name and the same signature with virtual method 'Father::create'. Did you meant to override it? + + virtual Derived &&generat(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::generat' has a similar name and the same signature with virtual method 'Father::generate'. Did you meant to override it? + +private: + void funk(); // Should not warn: access qualifers don't match. +};