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