clang-tools  14.0.0git
VirtualNearMissCheck.cpp
Go to the documentation of this file.
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 {
22 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
23 
24 AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
25  return Node.isOverloadedOperator();
26 }
27 } // namespace
28 
29 /// Finds out if the given method overrides some method.
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.
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.
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.
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()) !=
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.
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.
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 
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 
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 
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 
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
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
Type
NodeType Type
Definition: HTMLGenerator.cpp:73
clang::tidy::bugprone::checkOverrideWithoutName
static bool checkOverrideWithoutName(const ASTContext *Context, const CXXMethodDecl *BaseMD, const CXXMethodDecl *DerivedMD)
Definition: VirtualNearMissCheck.cpp:149
clang::doc::MD
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
clang::tidy::bugprone::getDecayedType
static QualType getDecayedType(QualType Type)
Definition: VirtualNearMissCheck.cpp:124
clang::ast_matchers
Definition: AbseilMatcher.h:14
VirtualNearMissCheck.h
clang::clangd::SymbolKind::Key
@ Key
clang::tidy::bugprone::checkOverridingFunctionReturnType
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.
Definition: VirtualNearMissCheck.cpp:39
clang::tidy::bugprone::checkParamTypes
static bool checkParamTypes(const CXXMethodDecl *BaseMD, const CXXMethodDecl *DerivedMD)
Definition: VirtualNearMissCheck.cpp:131
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:253
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::getReturnType
std::string getReturnType(const CodeCompletionString &CCS)
Gets detail to be used as the detail field in an LSP completion item.
Definition: CodeCompletionStrings.cpp:272
clang::tidy::bugprone::isOverrideMethod
static bool isOverrideMethod(const CXXMethodDecl *MD)
Finds out if the given method overrides some method.
Definition: VirtualNearMissCheck.cpp:30
clang::tidy::bugprone::checkOverrideByDerivedMethod
static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, const CXXMethodDecl *DerivedMD)
Check whether BaseMD overrides DerivedMD.
Definition: VirtualNearMissCheck.cpp:169
Path
std::vector< HeaderHandle > Path
Definition: PreprocessorTracker.cpp:525