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