blob: ba4655b16a89840334645fbae61361bfee8edb13 [file] [log] [blame]
Alexander Kornienko98ba0812016-01-13 14:16:35 +00001//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "VirtualNearMissCheck.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;
17
18namespace clang {
19namespace tidy {
20namespace misc {
21
22/// Finds out if the given method overrides some method.
23static bool isOverrideMethod(const CXXMethodDecl *MD) {
24 return MD->size_overridden_methods() > 0 || MD->hasAttr<OverrideAttr>();
25}
26
27/// Checks whether the return types are covariant, according to
28/// C++[class.virtual]p7.
29///
30/// Similar with clang::Sema::CheckOverridingFunctionReturnType.
31/// \returns true if the return types of BaseMD and DerivedMD are covariant.
32static bool checkOverridingFunctionReturnType(const ASTContext *Context,
33 const CXXMethodDecl *BaseMD,
34 const CXXMethodDecl *DerivedMD) {
35 QualType BaseReturnTy =
36 BaseMD->getType()->getAs<FunctionType>()->getReturnType();
37 QualType DerivedReturnTy =
38 DerivedMD->getType()->getAs<FunctionType>()->getReturnType();
39
40 if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType())
41 return false;
42
43 // Check if return types are identical.
44 if (Context->hasSameType(DerivedReturnTy, BaseReturnTy))
45 return true;
46
47 /// Check if the return types are covariant.
Gabor Horvath93bc5762016-01-22 21:45:51 +000048
49 // Both types must be pointers or references to classes.
50 if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) &&
51 !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType()))
52 return false;
53
Alexander Kornienko98ba0812016-01-13 14:16:35 +000054 /// BTy is the class type in return type of BaseMD. For example,
55 /// B* Base::md()
56 /// While BRD is the declaration of B.
Gabor Horvath93bc5762016-01-22 21:45:51 +000057 QualType DTy = DerivedReturnTy->getPointeeType();
58 QualType BTy = BaseReturnTy->getPointeeType();
Alexander Kornienko98ba0812016-01-13 14:16:35 +000059
Gabor Horvath93bc5762016-01-22 21:45:51 +000060 const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl();
61 const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl();
Alexander Kornienko98ba0812016-01-13 14:16:35 +000062 if (DRD == nullptr || BRD == nullptr)
63 return false;
64
65 if (DRD == BRD)
66 return true;
67
68 if (!Context->hasSameUnqualifiedType(DTy, BTy)) {
69 // Begin checking whether the conversion from D to B is valid.
70 CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
71 /*DetectVirtual=*/false);
72
73 // Check whether D is derived from B, and fill in a CXXBasePaths object.
74 if (!DRD->isDerivedFrom(BRD, Paths))
75 return false;
76
77 // Check ambiguity.
78 if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType()))
79 return false;
80
81 // Check accessibility.
82 // FIXME: We currently only support checking if B is accessible base class
83 // of D, or D is the same class which DerivedMD is in.
84 bool IsItself = DRD == DerivedMD->getParent();
85 bool HasPublicAccess = false;
86 for (const auto &Path : Paths) {
87 if (Path.Access == AS_public)
88 HasPublicAccess = true;
89 }
90 if (!HasPublicAccess && !IsItself)
91 return false;
92 // End checking conversion from D to B.
93 }
94
95 // Both pointers or references should have the same cv-qualification.
96 if (DerivedReturnTy.getLocalCVRQualifiers() !=
97 BaseReturnTy.getLocalCVRQualifiers())
98 return false;
99
100 // The class type D should have the same cv-qualification as or less
101 // cv-qualification than the class type B.
102 if (DTy.isMoreQualifiedThan(BTy))
103 return false;
104
105 return true;
106}
107
Gabor Horvath93bc5762016-01-22 21:45:51 +0000108/// \returns decayed type for arrays and functions.
109static QualType getDecayedType(QualType Type) {
110 if (const auto *Decayed = Type->getAs<DecayedType>())
111 return Decayed->getDecayedType();
112 return Type;
113}
114
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000115/// \returns true if the param types are the same.
116static bool checkParamTypes(const CXXMethodDecl *BaseMD,
117 const CXXMethodDecl *DerivedMD) {
118 unsigned NumParamA = BaseMD->getNumParams();
119 unsigned NumParamB = DerivedMD->getNumParams();
120 if (NumParamA != NumParamB)
121 return false;
122
123 for (unsigned I = 0; I < NumParamA; I++) {
Gabor Horvath93bc5762016-01-22 21:45:51 +0000124 if (getDecayedType(BaseMD->getParamDecl(I)->getType()) !=
125 getDecayedType(DerivedMD->getParamDecl(I)->getType()))
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000126 return false;
127 }
128 return true;
129}
130
131/// \returns true if derived method can override base method except for the
132/// name.
133static bool checkOverrideWithoutName(const ASTContext *Context,
134 const CXXMethodDecl *BaseMD,
135 const CXXMethodDecl *DerivedMD) {
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000136 if (BaseMD->isStatic() != DerivedMD->isStatic())
137 return false;
138
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000139 if (BaseMD->getType() == DerivedMD->getType())
140 return true;
141
142 // Now the function types are not identical. Then check if the return types
143 // are covariant and if the param types are the same.
144 if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD))
145 return false;
146 return checkParamTypes(BaseMD, DerivedMD);
147}
148
149/// Check whether BaseMD overrides DerivedMD.
150///
151/// Prerequisite: the class which BaseMD is in should be a base class of that
152/// DerivedMD is in.
153static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD,
154 const CXXMethodDecl *DerivedMD) {
155 if (BaseMD->getNameAsString() != DerivedMD->getNameAsString())
156 return false;
157
158 if (!checkParamTypes(BaseMD, DerivedMD))
159 return false;
160
161 return true;
162}
163
164/// Generate unique ID for given MethodDecl.
165///
166/// The Id is used as key for 'PossibleMap'.
167/// Typical Id: "Base::func void (void)"
168static std::string generateMethodId(const CXXMethodDecl *MD) {
169 return MD->getQualifiedNameAsString() + " " + MD->getType().getAsString();
170}
171
172bool VirtualNearMissCheck::isPossibleToBeOverridden(
173 const CXXMethodDecl *BaseMD) {
174 std::string Id = generateMethodId(BaseMD);
175 auto Iter = PossibleMap.find(Id);
176 if (Iter != PossibleMap.end())
177 return Iter->second;
178
179 bool IsPossible = !BaseMD->isImplicit() && !isa<CXXConstructorDecl>(BaseMD) &&
Gabor Horvath93bc5762016-01-22 21:45:51 +0000180 !isa<CXXDestructorDecl>(BaseMD) && BaseMD->isVirtual() &&
181 !BaseMD->isOverloadedOperator();
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000182 PossibleMap[Id] = IsPossible;
183 return IsPossible;
184}
185
186bool VirtualNearMissCheck::isOverriddenByDerivedClass(
187 const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) {
188 auto Key = std::make_pair(generateMethodId(BaseMD),
189 DerivedRD->getQualifiedNameAsString());
190 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 if (!getLangOpts().CPlusPlus)
210 return;
211
Gabor Horvath93bc5762016-01-22 21:45:51 +0000212 Finder->addMatcher(
213 cxxMethodDecl(unless(anyOf(isOverride(), isImplicit(),
214 cxxConstructorDecl(), cxxDestructorDecl())))
215 .bind("method"),
216 this);
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000217}
218
219void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) {
220 const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
Gabor Horvath93bc5762016-01-22 21:45:51 +0000221 assert(DerivedMD);
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000222
223 if (DerivedMD->isStatic())
224 return;
225
Gabor Horvath93bc5762016-01-22 21:45:51 +0000226 if (DerivedMD->isOverloadedOperator())
227 return;
228
Alexander Kornienko98ba0812016-01-13 14:16:35 +0000229 const ASTContext *Context = Result.Context;
230
231 const auto *DerivedRD = DerivedMD->getParent();
232
233 for (const auto &BaseSpec : DerivedRD->bases()) {
234 if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) {
235 for (const auto *BaseMD : BaseRD->methods()) {
236 if (!isPossibleToBeOverridden(BaseMD))
237 continue;
238
239 if (isOverriddenByDerivedClass(BaseMD, DerivedRD))
240 continue;
241
242 unsigned EditDistance =
243 BaseMD->getName().edit_distance(DerivedMD->getName());
244 if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) {
245 if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) {
246 // A "virtual near miss" is found.
247 diag(DerivedMD->getLocStart(),
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 }
253 }
254 }
255 }
256 }
257}
258
259} // namespace misc
260} // namespace tidy
261} // namespace clang