diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp new file mode 100644 index 00000000000..6b3f49879af --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp @@ -0,0 +1,274 @@ +//===--- 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 bugprone { + +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { + return Node.isOverloadedOperator(); +} + +/// Finds out if the given method overrides some method. +static bool isOverrideMethod(const CXXMethodDecl *MD) { + return MD->size_overridden_methods() > 0 || MD->hasAttr<OverrideAttr>(); +} + +/// 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. +static bool checkOverridingFunctionReturnType(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + QualType BaseReturnTy = BaseMD->getType() + ->getAs<FunctionType>() + ->getReturnType() + .getCanonicalType(); + QualType DerivedReturnTy = DerivedMD->getType() + ->getAs<FunctionType>() + ->getReturnType() + .getCanonicalType(); + + 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. + + // Both types must be pointers or references to classes. + if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) && + !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType())) + return false; + + /// BTy is the class type in return type of BaseMD. For example, + /// B* Base::md() + /// While BRD is the declaration of B. + QualType DTy = DerivedReturnTy->getPointeeType().getCanonicalType(); + QualType BTy = BaseReturnTy->getPointeeType().getCanonicalType(); + + const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl(); + const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl(); + if (DRD == nullptr || BRD == nullptr) + return false; + + if (!DRD->hasDefinition() || !BRD->hasDefinition()) + 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->getCanonicalDecl() == DerivedMD->getParent()->getCanonicalDecl(); + 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 decayed type for arrays and functions. +static QualType getDecayedType(QualType Type) { + if (const auto *Decayed = Type->getAs<DecayedType>()) + return Decayed->getDecayedType(); + return Type; +} + +/// \returns true if the param types are the same. +static 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 (getDecayedType(BaseMD->getParamDecl(I)->getType().getCanonicalType()) != + getDecayedType( + DerivedMD->getParamDecl(I)->getType().getCanonicalType())) + return false; + } + return true; +} + +/// \returns true if derived method can override base method except for the +/// name. +static bool checkOverrideWithoutName(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->isStatic() != DerivedMD->isStatic()) + 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. +static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + for (CXXMethodDecl::method_iterator I = DerivedMD->begin_overridden_methods(), + E = DerivedMD->end_overridden_methods(); + I != E; ++I) { + const CXXMethodDecl *OverriddenMD = *I; + if (BaseMD->getCanonicalDecl() == OverriddenMD->getCanonicalDecl()) + return true; + } + + return false; +} + +bool VirtualNearMissCheck::isPossibleToBeOverridden( + const CXXMethodDecl *BaseMD) { + auto Iter = PossibleMap.find(BaseMD); + if (Iter != PossibleMap.end()) + return Iter->second; + + bool IsPossible = !BaseMD->isImplicit() && !isa<CXXConstructorDecl>(BaseMD) && + !isa<CXXDestructorDecl>(BaseMD) && BaseMD->isVirtual() && + !BaseMD->isOverloadedOperator() && + !isa<CXXConversionDecl>(BaseMD); + PossibleMap[BaseMD] = IsPossible; + return IsPossible; +} + +bool VirtualNearMissCheck::isOverriddenByDerivedClass( + const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) { + auto Key = std::make_pair(BaseMD, DerivedRD); + 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(), + cxxDestructorDecl(), cxxConversionDecl(), isStatic(), + isOverloadedOperator()))) + .bind("method"), + this); +} + +void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { + const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method"); + assert(DerivedMD); + + const ASTContext *Context = Result.Context; + + const auto *DerivedRD = DerivedMD->getParent()->getDefinition(); + assert(DerivedRD); + + 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(), EditDistanceThreshold); + if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) { + if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) { + // A "virtual near miss" is found. + auto Range = CharSourceRange::getTokenRange( + SourceRange(DerivedMD->getLocation())); + + bool ApplyFix = !BaseMD->isTemplateInstantiation() && + !DerivedMD->isTemplateInstantiation(); + auto Diag = + diag(DerivedMD->getLocStart(), + "method '%0' has a similar name and the same signature as " + "virtual method '%1'; did you mean to override it?") + << DerivedMD->getQualifiedNameAsString() + << BaseMD->getQualifiedNameAsString(); + if (ApplyFix) + Diag << FixItHint::CreateReplacement(Range, BaseMD->getName()); + } + } + } + } + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang |