//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===// // // 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 "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 { namespace { AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { return Node.isOverloadedOperator(); } } // namespace /// Finds out if the given method overrides some method. static 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. static bool checkOverridingFunctionReturnType(const ASTContext *Context, const CXXMethodDecl *BaseMD, const CXXMethodDecl *DerivedMD) { QualType BaseReturnTy = BaseMD->getType() ->getAs() ->getReturnType() .getCanonicalType(); QualType DerivedReturnTy = DerivedMD->getType() ->getAs() ->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()) 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(BaseMD) && !isa(BaseMD) && BaseMD->isVirtual() && !BaseMD->isOverloadedOperator() && !isa(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("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->getBeginLoc(), "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