clang  19.0.0git
RefCntblBaseVirtualDtorChecker.cpp
Go to the documentation of this file.
1 //=======- RefCntblBaseVirtualDtor.cpp ---------------------------*- C++ -*-==//
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 "ASTUtils.h"
10 #include "DiagOutputUtils.h"
11 #include "PtrTypesSemantics.h"
14 #include "clang/AST/StmtVisitor.h"
19 #include "llvm/ADT/DenseSet.h"
20 #include "llvm/ADT/SetVector.h"
21 #include <optional>
22 
23 using namespace clang;
24 using namespace ento;
25 
26 namespace {
27 
28 class DerefFuncDeleteExprVisitor
29  : public ConstStmtVisitor<DerefFuncDeleteExprVisitor, bool> {
30  // Returns true if any of child statements return true.
31  bool VisitChildren(const Stmt *S) {
32  for (const Stmt *Child : S->children()) {
33  if (Child && Visit(Child))
34  return true;
35  }
36  return false;
37  }
38 
39  bool VisitBody(const Stmt *Body) {
40  if (!Body)
41  return false;
42 
43  auto [It, IsNew] = VisitedBody.insert(Body);
44  if (!IsNew) // This body is recursive
45  return false;
46 
47  return Visit(Body);
48  }
49 
50 public:
51  DerefFuncDeleteExprVisitor(const TemplateArgumentList &ArgList,
52  const CXXRecordDecl *ClassDecl)
53  : ArgList(&ArgList), ClassDecl(ClassDecl) {}
54 
55  DerefFuncDeleteExprVisitor(const CXXRecordDecl *ClassDecl)
56  : ClassDecl(ClassDecl) {}
57 
58  std::optional<bool> HasSpecializedDelete(CXXMethodDecl *Decl) {
59  if (auto *Body = Decl->getBody())
60  return VisitBody(Body);
61  if (Decl->getTemplateInstantiationPattern())
62  return std::nullopt; // Indeterminate. There was no concrete instance.
63  return false;
64  }
65 
66  bool VisitCallExpr(const CallExpr *CE) {
67  const Decl *D = CE->getCalleeDecl();
68  if (D && D->hasBody())
69  return VisitBody(D->getBody());
70  return false;
71  }
72 
73  bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
74  auto *Arg = E->getArgument();
75  while (Arg) {
76  if (auto *Paren = dyn_cast<ParenExpr>(Arg))
77  Arg = Paren->getSubExpr();
78  else if (auto *Cast = dyn_cast<CastExpr>(Arg)) {
79  Arg = Cast->getSubExpr();
80  auto CastType = Cast->getType();
81  if (auto *PtrType = dyn_cast<PointerType>(CastType)) {
82  auto PointeeType = PtrType->getPointeeType();
83  while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) {
84  if (ET->isSugared())
85  PointeeType = ET->desugar();
86  }
87  if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) {
88  if (ArgList) {
89  auto ParmIndex = ParmType->getIndex();
90  auto Type = ArgList->get(ParmIndex).getAsType();
91  if (Type->getAsCXXRecordDecl() == ClassDecl)
92  return true;
93  }
94  } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) {
95  if (RD->getDecl() == ClassDecl)
96  return true;
97  } else if (auto *ST =
98  dyn_cast<SubstTemplateTypeParmType>(PointeeType)) {
99  auto Type = ST->getReplacementType();
100  if (auto *RD = dyn_cast<RecordType>(Type)) {
101  if (RD->getDecl() == ClassDecl)
102  return true;
103  }
104  }
105  }
106  } else
107  break;
108  }
109  return false;
110  }
111 
112  bool VisitStmt(const Stmt *S) { return VisitChildren(S); }
113 
114  // Return false since the contents of lambda isn't necessarily executed.
115  // If it is executed, VisitCallExpr above will visit its body.
116  bool VisitLambdaExpr(const LambdaExpr *) { return false; }
117 
118 private:
119  const TemplateArgumentList *ArgList{nullptr};
120  const CXXRecordDecl *ClassDecl;
121  llvm::DenseSet<const Stmt *> VisitedBody;
122 };
123 
124 class RefCntblBaseVirtualDtorChecker
125  : public Checker<check::ASTDecl<TranslationUnitDecl>> {
126 private:
127  BugType Bug;
128  mutable BugReporter *BR;
129 
130 public:
131  RefCntblBaseVirtualDtorChecker()
132  : Bug(this,
133  "Reference-countable base class doesn't have virtual destructor",
134  "WebKit coding guidelines") {}
135 
136  void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
137  BugReporter &BRArg) const {
138  BR = &BRArg;
139 
140  // The calls to checkAST* from AnalysisConsumer don't
141  // visit template instantiations or lambda classes. We
142  // want to visit those, so we make our own RecursiveASTVisitor.
143  struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
144  const RefCntblBaseVirtualDtorChecker *Checker;
145  explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker)
146  : Checker(Checker) {
147  assert(Checker);
148  }
149 
150  bool shouldVisitTemplateInstantiations() const { return true; }
151  bool shouldVisitImplicitCode() const { return false; }
152 
153  bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
154  if (!RD->hasDefinition())
155  return true;
156 
157  Decls.insert(RD);
158 
159  for (auto &Base : RD->bases()) {
160  const auto AccSpec = Base.getAccessSpecifier();
161  if (AccSpec == AS_protected || AccSpec == AS_private ||
162  (AccSpec == AS_none && RD->isClass()))
163  continue;
164 
165  QualType T = Base.getType();
166  if (T.isNull())
167  continue;
168 
169  const CXXRecordDecl *C = T->getAsCXXRecordDecl();
170  if (!C)
171  continue;
172 
173  if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
174  for (auto &Arg : CTSD->getTemplateArgs().asArray()) {
175  if (Arg.getKind() != TemplateArgument::Type)
176  continue;
177  auto TemplT = Arg.getAsType();
178  if (TemplT.isNull())
179  continue;
180 
181  bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
182  if (!IsCRTP)
183  continue;
184  CRTPs.insert(C);
185  }
186  }
187  }
188 
189  return true;
190  }
191 
192  llvm::SetVector<const CXXRecordDecl *> Decls;
194  };
195 
196  LocalVisitor visitor(this);
197  visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
198  for (auto *RD : visitor.Decls) {
199  if (visitor.CRTPs.contains(RD))
200  continue;
201  visitCXXRecordDecl(RD);
202  }
203  }
204 
205  void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
206  if (shouldSkipDecl(RD))
207  return;
208 
209  for (auto &Base : RD->bases()) {
210  const auto AccSpec = Base.getAccessSpecifier();
211  if (AccSpec == AS_protected || AccSpec == AS_private ||
212  (AccSpec == AS_none && RD->isClass()))
213  continue;
214 
215  auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
216  auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
217 
218  bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
219  bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
220 
221  QualType T = Base.getType();
222  if (T.isNull())
223  continue;
224 
225  const CXXRecordDecl *C = T->getAsCXXRecordDecl();
226  if (!C)
227  continue;
228 
229  bool AnyInconclusiveBase = false;
230  const auto hasPublicRefInBase =
231  [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
232  auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
233  if (!hasRefInBase) {
234  AnyInconclusiveBase = true;
235  return false;
236  }
237  return (*hasRefInBase) != nullptr;
238  };
239  const auto hasPublicDerefInBase =
240  [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
241  auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
242  if (!hasDerefInBase) {
243  AnyInconclusiveBase = true;
244  return false;
245  }
246  return (*hasDerefInBase) != nullptr;
247  };
248  CXXBasePaths Paths;
249  Paths.setOrigin(C);
250  hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
251  /*LookupInDependent =*/true);
252  hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
253  /*LookupInDependent =*/true);
254  if (AnyInconclusiveBase || !hasRef || !hasDeref)
255  continue;
256 
257  auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
258  if (!HasSpecializedDelete || *HasSpecializedDelete)
259  continue;
260  if (C->lookupInBases(
261  [&](const CXXBaseSpecifier *Base, CXXBasePath &) {
262  auto *T = Base->getType().getTypePtrOrNull();
263  if (!T)
264  return false;
265  auto *R = T->getAsCXXRecordDecl();
266  if (!R)
267  return false;
268  auto Result = isClassWithSpecializedDelete(R, RD);
269  if (!Result)
270  AnyInconclusiveBase = true;
271  return Result && *Result;
272  },
273  Paths, /*LookupInDependent =*/true))
274  continue;
275  if (AnyInconclusiveBase)
276  continue;
277 
278  const auto *Dtor = C->getDestructor();
279  if (!Dtor || !Dtor->isVirtual()) {
280  auto *ProblematicBaseSpecifier = &Base;
281  auto *ProblematicBaseClass = C;
282  reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
283  }
284  }
285  }
286 
287  bool shouldSkipDecl(const CXXRecordDecl *RD) const {
288  if (!RD->isThisDeclarationADefinition())
289  return true;
290 
291  if (RD->isImplicit())
292  return true;
293 
294  if (RD->isLambda())
295  return true;
296 
297  // If the construct doesn't have a source file, then it's not something
298  // we want to diagnose.
299  const auto RDLocation = RD->getLocation();
300  if (!RDLocation.isValid())
301  return true;
302 
303  const auto Kind = RD->getTagKind();
305  return true;
306 
307  // Ignore CXXRecords that come from system headers.
308  if (BR->getSourceManager().getFileCharacteristic(RDLocation) !=
310  return true;
311 
312  return false;
313  }
314 
315  static bool isRefCountedClass(const CXXRecordDecl *D) {
317  return false;
318  auto *NsDecl = D->getParent();
319  if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
320  return false;
321  auto NamespaceName = safeGetName(NsDecl);
322  auto ClsNameStr = safeGetName(D);
323  StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
324  return NamespaceName == "WTF" &&
325  (ClsName.ends_with("RefCounted") ||
326  ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
327  }
328 
329  static std::optional<bool>
330  isClassWithSpecializedDelete(const CXXRecordDecl *C,
331  const CXXRecordDecl *DerivedClass) {
332  if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
333  for (auto *MethodDecl : C->methods()) {
334  if (safeGetName(MethodDecl) == "deref") {
335  DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
336  DerivedClass);
337  auto Result = Visitor.HasSpecializedDelete(MethodDecl);
338  if (!Result || *Result)
339  return Result;
340  }
341  }
342  return false;
343  }
344  for (auto *MethodDecl : C->methods()) {
345  if (safeGetName(MethodDecl) == "deref") {
346  DerefFuncDeleteExprVisitor Visitor(DerivedClass);
347  auto Result = Visitor.HasSpecializedDelete(MethodDecl);
348  if (!Result || *Result)
349  return Result;
350  }
351  }
352  return false;
353  }
354 
355  void reportBug(const CXXRecordDecl *DerivedClass,
356  const CXXBaseSpecifier *BaseSpec,
357  const CXXRecordDecl *ProblematicBaseClass) const {
358  assert(DerivedClass);
359  assert(BaseSpec);
360  assert(ProblematicBaseClass);
361 
362  SmallString<100> Buf;
363  llvm::raw_svector_ostream Os(Buf);
364 
365  Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
366  printQuotedQualifiedName(Os, ProblematicBaseClass);
367 
368  Os << " is used as a base of "
369  << (DerivedClass->isClass() ? "class" : "struct") << " ";
370  printQuotedQualifiedName(Os, DerivedClass);
371 
372  Os << " but doesn't have virtual destructor";
373 
374  PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
375  BR->getSourceManager());
376  auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
377  Report->addRange(BaseSpec->getSourceRange());
378  BR->emitReport(std::move(Report));
379  }
380 };
381 } // namespace
382 
383 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
384  Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
385 }
386 
387 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
388  const CheckerManager &mgr) {
389  return true;
390 }
CastType
Definition: SemaCast.cpp:50
Represents a path from a specific derived class (which is not represented as part of the path) to a p...
BasePaths - Represents the set of paths from a derived class to one of its (direct or indirect) bases...
Represents a base class of a C++ class.
Definition: DeclCXX.h:146
SourceRange getSourceRange() const LLVM_READONLY
Retrieves the source range that contains the entire base specifier.
Definition: DeclCXX.h:193
Represents a delete expression for memory deallocation and destructor calls, e.g.
Definition: ExprCXX.h:2493
Expr * getArgument()
Definition: ExprCXX.h:2534
Represents a static or instance method of a struct/union/class.
Definition: DeclCXX.h:2060
Represents a C++ struct/union/class.
Definition: DeclCXX.h:258
base_class_range bases()
Definition: DeclCXX.h:619
bool isLambda() const
Determine whether this class describes a lambda function object.
Definition: DeclCXX.h:1022
const CXXRecordDecl * getTemplateInstantiationPattern() const
Retrieve the record declaration from which this record could be instantiated.
Definition: DeclCXX.cpp:1930
bool hasDefinition() const
Definition: DeclCXX.h:571
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2872
Decl * getCalleeDecl()
Definition: Expr.h:3036
ConstStmtVisitor - This class implements a simple visitor for Stmt subclasses.
Definition: StmtVisitor.h:195
DeclContext * getParent()
getParent - Returns the containing DeclContext.
Definition: DeclBase.h:2066
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:86
bool isImplicit() const
isImplicit - Indicates whether the declaration was implicitly generated by the implementation.
Definition: DeclBase.h:599
virtual Stmt * getBody() const
getBody - If this Decl represents a declaration for a body of code, such as a function or method defi...
Definition: DeclBase.h:1077
virtual bool hasBody() const
Returns true if this Decl represents a declaration for a body of code, such as a function or method d...
Definition: DeclBase.h:1083
SourceLocation getLocation() const
Definition: DeclBase.h:445
A C++ lambda expression, which produces a function object (of unspecified type) that can be invoked l...
Definition: ExprCXX.h:1950
A (possibly-)qualified type.
Definition: Type.h:940
A class that does preorder or postorder depth-first traversal on the entire Clang AST and visits each...
SrcMgr::CharacteristicKind getFileCharacteristic(SourceLocation Loc) const
Return the file characteristic of the specified source location, indicating whether this is a normal ...
SourceLocation getBegin() const
Stmt - This represents one statement.
Definition: Stmt.h:84
bool isThisDeclarationADefinition() const
Return true if this declaration is a completion definition of the type.
Definition: Decl.h:3685
bool isClass() const
Definition: Decl.h:3792
TagKind getTagKind() const
Definition: Decl.h:3782
A template argument list.
Definition: DeclTemplate.h:244
@ Type
The template argument is a type.
Definition: TemplateBase.h:70
The top declaration context.
Definition: Decl.h:84
The base class of the type hierarchy.
Definition: Type.h:1813
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition: Type.cpp:1881
BugReporter is a utility class for generating PathDiagnostics for analysis.
Definition: BugReporter.h:585
const SourceManager & getSourceManager()
Definition: BugReporter.h:623
virtual void emitReport(std::unique_ptr< BugReport > R)
Add the given report to the set of reports tracked by BugReporter.
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
bool Cast(InterpState &S, CodePtr OpPC)
Definition: Interp.h:1717
The JSON file list parser is used to communicate input to InstallAPI.
std::optional< const clang::CXXRecordDecl * > hasPublicMethodInBase(const CXXBaseSpecifier *Base, const char *NameToMatch)
void printQuotedQualifiedName(llvm::raw_ostream &Os, const NamedDeclDerivedT &D)
@ Struct
The "struct" keyword.
@ Class
The "class" keyword.
const FunctionProtoType * T
std::string safeGetName(const T *ASTNode)
Definition: ASTUtils.h:68
@ AS_protected
Definition: Specifiers.h:122
@ AS_none
Definition: Specifiers.h:124
@ AS_private
Definition: Specifiers.h:123