123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- //===--- 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::tidy::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<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()
- ->castAs<FunctionType>()
- ->getReturnType()
- .getCanonicalType();
- QualType DerivedReturnTy = DerivedMD->getType()
- ->castAs<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) {
- 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->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 clang::tidy::bugprone
|