xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp (revision b9d678d22f74ebd6e34f0a3501fb01d3d80984e7)
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::tidy::bugprone {
18 
19 namespace {
20 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
21 
22 AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
23   return Node.isOverloadedOperator();
24 }
25 } // namespace
26 
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>();
30 }
31 
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();
48 
49   if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType())
50     return false;
51 
52   // Check if return types are identical.
53   if (Context->hasSameType(DerivedReturnTy, BaseReturnTy))
54     return true;
55 
56   /// Check if the return types are covariant.
57 
58   // Both types must be pointers or references to classes.
59   if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) &&
60       !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType()))
61     return false;
62 
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();
68 
69   const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl();
70   const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl();
71   if (DRD == nullptr || BRD == nullptr)
72     return false;
73 
74   if (!DRD->hasDefinition() || !BRD->hasDefinition())
75     return false;
76 
77   if (DRD == BRD)
78     return true;
79 
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);
84 
85     // Check whether D is derived from B, and fill in a CXXBasePaths object.
86     if (!DRD->isDerivedFrom(BRD, Paths))
87       return false;
88 
89     // Check ambiguity.
90     if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType()))
91       return false;
92 
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;
102     }
103     if (!HasPublicAccess && !IsItself)
104       return false;
105     // End checking conversion from D to B.
106   }
107 
108   // Both pointers or references should have the same cv-qualification.
109   if (DerivedReturnTy.getLocalCVRQualifiers() !=
110       BaseReturnTy.getLocalCVRQualifiers())
111     return false;
112 
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;
117 
118   return true;
119 }
120 
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;
126 }
127 
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;
135 
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;
141   }
142   return true;
143 }
144 
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;
152 
153   if (BaseMD->getType() == DerivedMD->getType())
154     return true;
155 
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);
161 }
162 
163 /// Check whether BaseMD overrides DerivedMD.
164 ///
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;
175   }
176 
177   return false;
178 }
179 
180 bool VirtualNearMissCheck::isPossibleToBeOverridden(
181     const CXXMethodDecl *BaseMD) {
182   auto Iter = PossibleMap.find(BaseMD);
183   if (Iter != PossibleMap.end())
184     return Iter->second;
185 
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;
192 }
193 
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;
200 
201   bool IsOverridden = false;
202   for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) {
203     if (!isOverrideMethod(DerivedMD))
204       continue;
205 
206     if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) {
207       IsOverridden = true;
208       break;
209     }
210   }
211   OverriddenMap[Key] = IsOverridden;
212   return IsOverridden;
213 }
214 
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);
223 }
224 
225 void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) {
226   const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
227   assert(DerivedMD);
228 
229   const ASTContext *Context = Result.Context;
230 
231   const auto *DerivedRD = DerivedMD->getParent()->getDefinition();
232   assert(DerivedRD);
233 
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;
239 
240         if (isOverriddenByDerivedClass(BaseMD, DerivedRD))
241           continue;
242 
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()));
250 
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());
261           }
262         }
263       }
264     }
265   }
266 }
267 
268 } // namespace clang::tidy::bugprone
269