[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clang-tidy / bugprone / VirtualNearMissCheck.cpp
blob76fa2d916f0e86050e036d06c9ae9258ae267b9f
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 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers;
17 namespace clang::tidy::bugprone {
19 namespace {
20 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
22 AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
23 return Node.isOverloadedOperator();
25 } // namespace
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>();
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();
49 if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType())
50 return false;
52 // Check if return types are identical.
53 if (Context->hasSameType(DerivedReturnTy, BaseReturnTy))
54 return true;
56 /// Check if the return types are covariant.
58 // Both types must be pointers or references to classes.
59 if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) &&
60 !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType()))
61 return false;
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();
69 const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl();
70 const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl();
71 if (DRD == nullptr || BRD == nullptr)
72 return false;
74 if (!DRD->hasDefinition() || !BRD->hasDefinition())
75 return false;
77 if (DRD == BRD)
78 return true;
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);
85 // Check whether D is derived from B, and fill in a CXXBasePaths object.
86 if (!DRD->isDerivedFrom(BRD, Paths))
87 return false;
89 // Check ambiguity.
90 if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType()))
91 return false;
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;
103 if (!HasPublicAccess && !IsItself)
104 return false;
105 // End checking conversion from D to B.
108 // Both pointers or references should have the same cv-qualification.
109 if (DerivedReturnTy.getLocalCVRQualifiers() !=
110 BaseReturnTy.getLocalCVRQualifiers())
111 return false;
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;
118 return true;
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;
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;
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;
142 return true;
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;
153 if (BaseMD->getType() == DerivedMD->getType())
154 return true;
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);
163 /// Check whether BaseMD overrides DerivedMD.
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;
177 return false;
180 bool VirtualNearMissCheck::isPossibleToBeOverridden(
181 const CXXMethodDecl *BaseMD) {
182 auto Iter = PossibleMap.find(BaseMD);
183 if (Iter != PossibleMap.end())
184 return Iter->second;
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;
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;
201 bool IsOverridden = false;
202 for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) {
203 if (!isOverrideMethod(DerivedMD))
204 continue;
206 if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) {
207 IsOverridden = true;
208 break;
211 OverriddenMap[Key] = IsOverridden;
212 return IsOverridden;
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);
225 void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) {
226 const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
227 assert(DerivedMD);
229 const ASTContext *Context = Result.Context;
231 const auto *DerivedRD = DerivedMD->getParent()->getDefinition();
232 assert(DerivedRD);
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;
240 if (isOverriddenByDerivedClass(BaseMD, DerivedRD))
241 continue;
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()));
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());
268 } // namespace clang::tidy::bugprone