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