1 //===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===//
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
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
{
20 AST_MATCHER(CXXMethodDecl
, isStatic
) { return Node
.isStatic(); }
22 AST_MATCHER(CXXMethodDecl
, isOverloadedOperator
) {
23 return Node
.isOverloadedOperator();
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.
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
>()
44 QualType DerivedReturnTy
= DerivedMD
->getType()
45 ->castAs
<FunctionType
>()
49 if (DerivedReturnTy
->isDependentType() || BaseReturnTy
->isDependentType())
52 // Check if return types are identical.
53 if (Context
->hasSameType(DerivedReturnTy
, BaseReturnTy
))
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()))
63 /// BTy is the class type in return type of BaseMD. For example,
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)
74 if (!DRD
->hasDefinition() || !BRD
->hasDefinition())
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
))
90 if (Paths
.isAmbiguous(Context
->getCanonicalType(BTy
).getUnqualifiedType()))
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.
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
)
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())
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
))
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();
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
)
136 for (unsigned I
= 0; I
< NumParamA
; I
++) {
137 if (getDecayedType(BaseMD
->getParamDecl(I
)->getType().getCanonicalType()) !=
139 DerivedMD
->getParamDecl(I
)->getType().getCanonicalType()))
145 /// \returns true if derived method can override base method except for the
147 static bool checkOverrideWithoutName(const ASTContext
*Context
,
148 const CXXMethodDecl
*BaseMD
,
149 const CXXMethodDecl
*DerivedMD
) {
150 if (BaseMD
->isStatic() != DerivedMD
->isStatic())
153 if (BaseMD
->getType() == DerivedMD
->getType())
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
))
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
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();
172 const CXXMethodDecl
*OverriddenMD
= *I
;
173 if (BaseMD
->getCanonicalDecl() == OverriddenMD
->getCanonicalDecl())
180 bool VirtualNearMissCheck::isPossibleToBeOverridden(
181 const CXXMethodDecl
*BaseMD
) {
182 auto Iter
= PossibleMap
.find(BaseMD
);
183 if (Iter
!= PossibleMap
.end())
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
;
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())
201 bool IsOverridden
= false;
202 for (const CXXMethodDecl
*DerivedMD
: DerivedRD
->methods()) {
203 if (!isOverrideMethod(DerivedMD
))
206 if (checkOverrideByDerivedMethod(BaseMD
, DerivedMD
)) {
211 OverriddenMap
[Key
] = IsOverridden
;
215 void VirtualNearMissCheck::registerMatchers(MatchFinder
*Finder
) {
218 unless(anyOf(isOverride(), isImplicit(), cxxConstructorDecl(),
219 cxxDestructorDecl(), cxxConversionDecl(), isStatic(),
220 isOverloadedOperator())))
225 void VirtualNearMissCheck::check(const MatchFinder::MatchResult
&Result
) {
226 const auto *DerivedMD
= Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("method");
229 const ASTContext
*Context
= Result
.Context
;
231 const auto *DerivedRD
= DerivedMD
->getParent()->getDefinition();
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
))
240 if (isOverriddenByDerivedClass(BaseMD
, DerivedRD
))
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();
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();
260 Diag
<< FixItHint::CreateReplacement(Range
, BaseMD
->getName());
268 } // namespace clang::tidy::bugprone