1 //===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "VirtualNearMissCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/AST/CXXInheritance.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/Lex/Lexer.h" 14 15 using namespace clang::ast_matchers; 16 17 namespace clang::tidy::bugprone { 18 19 namespace { 20 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } 21 22 AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { 23 return Node.isOverloadedOperator(); 24 } 25 } // namespace 26 27 /// Finds out if the given method overrides some method. 28 static bool isOverrideMethod(const CXXMethodDecl *MD) { 29 return MD->size_overridden_methods() > 0 || MD->hasAttr<OverrideAttr>(); 30 } 31 32 /// Checks whether the return types are covariant, according to 33 /// C++[class.virtual]p7. 34 /// 35 /// Similar with clang::Sema::CheckOverridingFunctionReturnType. 36 /// \returns true if the return types of BaseMD and DerivedMD are covariant. 37 static bool checkOverridingFunctionReturnType(const ASTContext *Context, 38 const CXXMethodDecl *BaseMD, 39 const CXXMethodDecl *DerivedMD) { 40 QualType BaseReturnTy = BaseMD->getType() 41 ->castAs<FunctionType>() 42 ->getReturnType() 43 .getCanonicalType(); 44 QualType DerivedReturnTy = DerivedMD->getType() 45 ->castAs<FunctionType>() 46 ->getReturnType() 47 .getCanonicalType(); 48 49 if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType()) 50 return false; 51 52 // Check if return types are identical. 53 if (Context->hasSameType(DerivedReturnTy, BaseReturnTy)) 54 return true; 55 56 /// Check if the return types are covariant. 57 58 // Both types must be pointers or references to classes. 59 if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) && 60 !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType())) 61 return false; 62 63 /// BTy is the class type in return type of BaseMD. For example, 64 /// B* Base::md() 65 /// While BRD is the declaration of B. 66 QualType DTy = DerivedReturnTy->getPointeeType().getCanonicalType(); 67 QualType BTy = BaseReturnTy->getPointeeType().getCanonicalType(); 68 69 const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl(); 70 const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl(); 71 if (DRD == nullptr || BRD == nullptr) 72 return false; 73 74 if (!DRD->hasDefinition() || !BRD->hasDefinition()) 75 return false; 76 77 if (DRD == BRD) 78 return true; 79 80 if (!Context->hasSameUnqualifiedType(DTy, BTy)) { 81 // Begin checking whether the conversion from D to B is valid. 82 CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true, 83 /*DetectVirtual=*/false); 84 85 // Check whether D is derived from B, and fill in a CXXBasePaths object. 86 if (!DRD->isDerivedFrom(BRD, Paths)) 87 return false; 88 89 // Check ambiguity. 90 if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType())) 91 return false; 92 93 // Check accessibility. 94 // FIXME: We currently only support checking if B is accessible base class 95 // of D, or D is the same class which DerivedMD is in. 96 bool IsItself = 97 DRD->getCanonicalDecl() == DerivedMD->getParent()->getCanonicalDecl(); 98 bool HasPublicAccess = false; 99 for (const auto &Path : Paths) { 100 if (Path.Access == AS_public) 101 HasPublicAccess = true; 102 } 103 if (!HasPublicAccess && !IsItself) 104 return false; 105 // End checking conversion from D to B. 106 } 107 108 // Both pointers or references should have the same cv-qualification. 109 if (DerivedReturnTy.getLocalCVRQualifiers() != 110 BaseReturnTy.getLocalCVRQualifiers()) 111 return false; 112 113 // The class type D should have the same cv-qualification as or less 114 // cv-qualification than the class type B. 115 if (DTy.isMoreQualifiedThan(BTy, *Context)) 116 return false; 117 118 return true; 119 } 120 121 /// \returns decayed type for arrays and functions. 122 static QualType getDecayedType(QualType Type) { 123 if (const auto *Decayed = Type->getAs<DecayedType>()) 124 return Decayed->getDecayedType(); 125 return Type; 126 } 127 128 /// \returns true if the param types are the same. 129 static bool checkParamTypes(const CXXMethodDecl *BaseMD, 130 const CXXMethodDecl *DerivedMD) { 131 unsigned NumParamA = BaseMD->getNumParams(); 132 unsigned NumParamB = DerivedMD->getNumParams(); 133 if (NumParamA != NumParamB) 134 return false; 135 136 for (unsigned I = 0; I < NumParamA; I++) { 137 if (getDecayedType(BaseMD->getParamDecl(I)->getType().getCanonicalType()) != 138 getDecayedType( 139 DerivedMD->getParamDecl(I)->getType().getCanonicalType())) 140 return false; 141 } 142 return true; 143 } 144 145 /// \returns true if derived method can override base method except for the 146 /// name. 147 static bool checkOverrideWithoutName(const ASTContext *Context, 148 const CXXMethodDecl *BaseMD, 149 const CXXMethodDecl *DerivedMD) { 150 if (BaseMD->isStatic() != DerivedMD->isStatic()) 151 return false; 152 153 if (BaseMD->getType() == DerivedMD->getType()) 154 return true; 155 156 // Now the function types are not identical. Then check if the return types 157 // are covariant and if the param types are the same. 158 if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD)) 159 return false; 160 return checkParamTypes(BaseMD, DerivedMD); 161 } 162 163 /// Check whether BaseMD overrides DerivedMD. 164 /// 165 /// Prerequisite: the class which BaseMD is in should be a base class of that 166 /// DerivedMD is in. 167 static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, 168 const CXXMethodDecl *DerivedMD) { 169 for (CXXMethodDecl::method_iterator I = DerivedMD->begin_overridden_methods(), 170 E = DerivedMD->end_overridden_methods(); 171 I != E; ++I) { 172 const CXXMethodDecl *OverriddenMD = *I; 173 if (BaseMD->getCanonicalDecl() == OverriddenMD->getCanonicalDecl()) 174 return true; 175 } 176 177 return false; 178 } 179 180 bool VirtualNearMissCheck::isPossibleToBeOverridden( 181 const CXXMethodDecl *BaseMD) { 182 auto Iter = PossibleMap.find(BaseMD); 183 if (Iter != PossibleMap.end()) 184 return Iter->second; 185 186 bool IsPossible = !BaseMD->isImplicit() && !isa<CXXConstructorDecl>(BaseMD) && 187 !isa<CXXDestructorDecl>(BaseMD) && BaseMD->isVirtual() && 188 !BaseMD->isOverloadedOperator() && 189 !isa<CXXConversionDecl>(BaseMD); 190 PossibleMap[BaseMD] = IsPossible; 191 return IsPossible; 192 } 193 194 bool VirtualNearMissCheck::isOverriddenByDerivedClass( 195 const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) { 196 auto Key = std::make_pair(BaseMD, DerivedRD); 197 auto Iter = OverriddenMap.find(Key); 198 if (Iter != OverriddenMap.end()) 199 return Iter->second; 200 201 bool IsOverridden = false; 202 for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) { 203 if (!isOverrideMethod(DerivedMD)) 204 continue; 205 206 if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) { 207 IsOverridden = true; 208 break; 209 } 210 } 211 OverriddenMap[Key] = IsOverridden; 212 return IsOverridden; 213 } 214 215 void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) { 216 Finder->addMatcher( 217 cxxMethodDecl( 218 unless(anyOf(isOverride(), isImplicit(), cxxConstructorDecl(), 219 cxxDestructorDecl(), cxxConversionDecl(), isStatic(), 220 isOverloadedOperator()))) 221 .bind("method"), 222 this); 223 } 224 225 void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { 226 const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method"); 227 assert(DerivedMD); 228 229 const ASTContext *Context = Result.Context; 230 231 const auto *DerivedRD = DerivedMD->getParent()->getDefinition(); 232 assert(DerivedRD); 233 234 for (const auto &BaseSpec : DerivedRD->bases()) { 235 if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) { 236 for (const auto *BaseMD : BaseRD->methods()) { 237 if (!isPossibleToBeOverridden(BaseMD)) 238 continue; 239 240 if (isOverriddenByDerivedClass(BaseMD, DerivedRD)) 241 continue; 242 243 unsigned EditDistance = BaseMD->getName().edit_distance( 244 DerivedMD->getName(), EditDistanceThreshold); 245 if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) { 246 if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) { 247 // A "virtual near miss" is found. 248 auto Range = CharSourceRange::getTokenRange( 249 SourceRange(DerivedMD->getLocation())); 250 251 bool ApplyFix = !BaseMD->isTemplateInstantiation() && 252 !DerivedMD->isTemplateInstantiation(); 253 auto Diag = 254 diag(DerivedMD->getBeginLoc(), 255 "method '%0' has a similar name and the same signature as " 256 "virtual method '%1'; did you mean to override it?") 257 << DerivedMD->getQualifiedNameAsString() 258 << BaseMD->getQualifiedNameAsString(); 259 if (ApplyFix) 260 Diag << FixItHint::CreateReplacement(Range, BaseMD->getName()); 261 } 262 } 263 } 264 } 265 } 266 } 267 268 } // namespace clang::tidy::bugprone 269