clang 20.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"
19#include "llvm/ADT/DenseSet.h"
20#include "llvm/ADT/SetVector.h"
21#include <optional>
22
23using namespace clang;
24using namespace ento;
25
26namespace {
27
28class 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
50public:
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 else {
71 auto name = safeGetName(D);
72 if (name == "ensureOnMainThread" || name == "ensureOnMainRunLoop") {
73 for (unsigned i = 0; i < CE->getNumArgs(); ++i) {
74 auto *Arg = CE->getArg(i);
75 if (VisitLambdaArgument(Arg))
76 return true;
77 }
78 }
79 }
80 return false;
81 }
82
83 bool VisitLambdaArgument(const Expr *E) {
84 E = E->IgnoreParenCasts();
85 if (auto *TempE = dyn_cast<CXXBindTemporaryExpr>(E))
86 E = TempE->getSubExpr();
87 E = E->IgnoreParenCasts();
88 if (auto *Ref = dyn_cast<DeclRefExpr>(E)) {
89 if (auto *VD = dyn_cast_or_null<VarDecl>(Ref->getDecl()))
90 return VisitLambdaArgument(VD->getInit());
91 return false;
92 }
93 if (auto *Lambda = dyn_cast<LambdaExpr>(E)) {
94 if (VisitBody(Lambda->getBody()))
95 return true;
96 }
97 if (auto *ConstructE = dyn_cast<CXXConstructExpr>(E)) {
98 for (unsigned i = 0; i < ConstructE->getNumArgs(); ++i) {
99 if (VisitLambdaArgument(ConstructE->getArg(i)))
100 return true;
101 }
102 }
103 return false;
104 }
105
106 bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
107 auto *Arg = E->getArgument();
108 while (Arg) {
109 if (auto *Paren = dyn_cast<ParenExpr>(Arg))
110 Arg = Paren->getSubExpr();
111 else if (auto *Cast = dyn_cast<CastExpr>(Arg)) {
112 Arg = Cast->getSubExpr();
113 auto CastType = Cast->getType();
114 if (auto *PtrType = dyn_cast<PointerType>(CastType)) {
115 auto PointeeType = PtrType->getPointeeType();
116 while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) {
117 if (ET->isSugared())
118 PointeeType = ET->desugar();
119 }
120 if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) {
121 if (ArgList) {
122 auto ParmIndex = ParmType->getIndex();
123 auto Type = ArgList->get(ParmIndex).getAsType();
124 if (Type->getAsCXXRecordDecl() == ClassDecl)
125 return true;
126 }
127 } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) {
128 if (RD->getDecl() == ClassDecl)
129 return true;
130 } else if (auto *ST =
131 dyn_cast<SubstTemplateTypeParmType>(PointeeType)) {
132 auto Type = ST->getReplacementType();
133 if (auto *RD = dyn_cast<RecordType>(Type)) {
134 if (RD->getDecl() == ClassDecl)
135 return true;
136 }
137 }
138 }
139 } else
140 break;
141 }
142 return false;
143 }
144
145 bool VisitStmt(const Stmt *S) { return VisitChildren(S); }
146
147 // Return false since the contents of lambda isn't necessarily executed.
148 // If it is executed, VisitCallExpr above will visit its body.
149 bool VisitLambdaExpr(const LambdaExpr *) { return false; }
150
151private:
152 const TemplateArgumentList *ArgList{nullptr};
153 const CXXRecordDecl *ClassDecl;
154 llvm::DenseSet<const Stmt *> VisitedBody;
155};
156
157class RefCntblBaseVirtualDtorChecker
158 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
159private:
160 BugType Bug;
161 mutable BugReporter *BR;
162
163public:
164 RefCntblBaseVirtualDtorChecker()
165 : Bug(this,
166 "Reference-countable base class doesn't have virtual destructor",
167 "WebKit coding guidelines") {}
168
169 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
170 BugReporter &BRArg) const {
171 BR = &BRArg;
172
173 // The calls to checkAST* from AnalysisConsumer don't
174 // visit template instantiations or lambda classes. We
175 // want to visit those, so we make our own RecursiveASTVisitor.
176 struct LocalVisitor : DynamicRecursiveASTVisitor {
177 const RefCntblBaseVirtualDtorChecker *Checker;
178 explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker)
179 : Checker(Checker) {
180 assert(Checker);
181 ShouldVisitTemplateInstantiations = true;
182 ShouldVisitImplicitCode = false;
183 }
184
185 bool VisitCXXRecordDecl(CXXRecordDecl *RD) override {
186 if (!RD->hasDefinition())
187 return true;
188
189 Decls.insert(RD);
190
191 for (auto &Base : RD->bases()) {
192 const auto AccSpec = Base.getAccessSpecifier();
193 if (AccSpec == AS_protected || AccSpec == AS_private ||
194 (AccSpec == AS_none && RD->isClass()))
195 continue;
196
197 QualType T = Base.getType();
198 if (T.isNull())
199 continue;
200
202 if (!C)
203 continue;
204
205 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
206 for (auto &Arg : CTSD->getTemplateArgs().asArray()) {
207 if (Arg.getKind() != TemplateArgument::Type)
208 continue;
209 auto TemplT = Arg.getAsType();
210 if (TemplT.isNull())
211 continue;
212
213 bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
214 if (!IsCRTP)
215 continue;
216 CRTPs.insert(C);
217 }
218 }
219 }
220
221 return true;
222 }
223
224 llvm::SetVector<const CXXRecordDecl *> Decls;
225 llvm::DenseSet<const CXXRecordDecl *> CRTPs;
226 };
227
228 LocalVisitor visitor(this);
229 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
230 for (auto *RD : visitor.Decls) {
231 if (visitor.CRTPs.contains(RD))
232 continue;
233 visitCXXRecordDecl(RD);
234 }
235 }
236
237 void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
238 if (shouldSkipDecl(RD))
239 return;
240
241 for (auto &Base : RD->bases()) {
242 const auto AccSpec = Base.getAccessSpecifier();
243 if (AccSpec == AS_protected || AccSpec == AS_private ||
244 (AccSpec == AS_none && RD->isClass()))
245 continue;
246
247 auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
248 auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
249
250 bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
251 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
252
253 QualType T = Base.getType();
254 if (T.isNull())
255 continue;
256
258 if (!C)
259 continue;
260
261 bool AnyInconclusiveBase = false;
262 const auto hasPublicRefInBase =
263 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
264 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
265 if (!hasRefInBase) {
266 AnyInconclusiveBase = true;
267 return false;
268 }
269 return (*hasRefInBase) != nullptr;
270 };
271 const auto hasPublicDerefInBase =
272 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
273 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
274 if (!hasDerefInBase) {
275 AnyInconclusiveBase = true;
276 return false;
277 }
278 return (*hasDerefInBase) != nullptr;
279 };
280 CXXBasePaths Paths;
281 Paths.setOrigin(C);
282 hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
283 /*LookupInDependent =*/true);
284 hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
285 /*LookupInDependent =*/true);
286 if (AnyInconclusiveBase || !hasRef || !hasDeref)
287 continue;
288
289 auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
290 if (!HasSpecializedDelete || *HasSpecializedDelete)
291 continue;
292 if (C->lookupInBases(
293 [&](const CXXBaseSpecifier *Base, CXXBasePath &) {
294 auto *T = Base->getType().getTypePtrOrNull();
295 if (!T)
296 return false;
297 auto *R = T->getAsCXXRecordDecl();
298 if (!R)
299 return false;
300 auto Result = isClassWithSpecializedDelete(R, RD);
301 if (!Result)
302 AnyInconclusiveBase = true;
303 return Result && *Result;
304 },
305 Paths, /*LookupInDependent =*/true))
306 continue;
307 if (AnyInconclusiveBase)
308 continue;
309
310 const auto *Dtor = C->getDestructor();
311 if (!Dtor || !Dtor->isVirtual()) {
312 auto *ProblematicBaseSpecifier = &Base;
313 auto *ProblematicBaseClass = C;
314 reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
315 }
316 }
317 }
318
319 bool shouldSkipDecl(const CXXRecordDecl *RD) const {
321 return true;
322
323 if (RD->isImplicit())
324 return true;
325
326 if (RD->isLambda())
327 return true;
328
329 // If the construct doesn't have a source file, then it's not something
330 // we want to diagnose.
331 const auto RDLocation = RD->getLocation();
332 if (!RDLocation.isValid())
333 return true;
334
335 const auto Kind = RD->getTagKind();
336 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
337 return true;
338
339 // Ignore CXXRecords that come from system headers.
340 if (BR->getSourceManager().getFileCharacteristic(RDLocation) !=
342 return true;
343
344 return false;
345 }
346
347 static bool isRefCountedClass(const CXXRecordDecl *D) {
348 if (!D->getTemplateInstantiationPattern())
349 return false;
350 auto *NsDecl = D->getParent();
351 if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
352 return false;
353 auto NamespaceName = safeGetName(NsDecl);
354 auto ClsNameStr = safeGetName(D);
355 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
356 return NamespaceName == "WTF" &&
357 (ClsName.ends_with("RefCounted") ||
358 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
359 }
360
361 static std::optional<bool>
362 isClassWithSpecializedDelete(const CXXRecordDecl *C,
363 const CXXRecordDecl *DerivedClass) {
364 if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
365 for (auto *MethodDecl : C->methods()) {
366 if (safeGetName(MethodDecl) == "deref") {
367 DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
368 DerivedClass);
369 auto Result = Visitor.HasSpecializedDelete(MethodDecl);
370 if (!Result || *Result)
371 return Result;
372 }
373 }
374 return false;
375 }
376 for (auto *MethodDecl : C->methods()) {
377 if (safeGetName(MethodDecl) == "deref") {
378 DerefFuncDeleteExprVisitor Visitor(DerivedClass);
379 auto Result = Visitor.HasSpecializedDelete(MethodDecl);
380 if (!Result || *Result)
381 return Result;
382 }
383 }
384 return false;
385 }
386
387 void reportBug(const CXXRecordDecl *DerivedClass,
388 const CXXBaseSpecifier *BaseSpec,
389 const CXXRecordDecl *ProblematicBaseClass) const {
390 assert(DerivedClass);
391 assert(BaseSpec);
392 assert(ProblematicBaseClass);
393
395 llvm::raw_svector_ostream Os(Buf);
396
397 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
398 printQuotedQualifiedName(Os, ProblematicBaseClass);
399
400 Os << " is used as a base of "
401 << (DerivedClass->isClass() ? "class" : "struct") << " ";
402 printQuotedQualifiedName(Os, DerivedClass);
403
404 Os << " but doesn't have virtual destructor";
405
407 BR->getSourceManager());
408 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
409 Report->addRange(BaseSpec->getSourceRange());
410 BR->emitReport(std::move(Report));
411 }
412};
413} // namespace
414
415void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
416 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
417}
418
419bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
420 const CheckerManager &mgr) {
421 return true;
422}
const Decl * D
Expr * E
CastType
Definition: SemaCast.cpp:48
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:2498
Represents a static or instance method of a struct/union/class.
Definition: DeclCXX.h:2078
Represents a C++ struct/union/class.
Definition: DeclCXX.h:258
base_class_range bases()
Definition: DeclCXX.h:620
bool isLambda() const
Determine whether this class describes a lambda function object.
Definition: DeclCXX.h:1030
bool hasDefinition() const
Definition: DeclCXX.h:572
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2874
Expr * getArg(unsigned Arg)
getArg - Return the specified argument.
Definition: Expr.h:3068
unsigned getNumArgs() const
getNumArgs - Return the number of actual arguments to this call.
Definition: Expr.h:3055
Decl * getCalleeDecl()
Definition: Expr.h:3041
ConstStmtVisitor - This class implements a simple visitor for Stmt subclasses.
Definition: StmtVisitor.h:195
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:596
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:1076
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:1082
SourceLocation getLocation() const
Definition: DeclBase.h:442
Recursive AST visitor that supports extension via dynamic dispatch.
This represents one expression.
Definition: Expr.h:110
Expr * IgnoreParenCasts() LLVM_READONLY
Skip past any parentheses and casts which might surround this expression until reaching a fixed point...
Definition: Expr.cpp:3095
A C++ lambda expression, which produces a function object (of unspecified type) that can be invoked l...
Definition: ExprCXX.h:1954
A (possibly-)qualified type.
Definition: Type.h:929
SrcMgr::CharacteristicKind getFileCharacteristic(SourceLocation Loc) const
Return the file characteristic of the specified source location, indicating whether this is a normal ...
SourceLocation getBegin() const
RetTy Visit(PTR(Stmt) S, ParamTys... P)
Definition: StmtVisitor.h:44
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:3662
bool isClass() const
Definition: Decl.h:3769
TagKind getTagKind() const
Definition: Decl.h:3759
A template argument list.
Definition: DeclTemplate.h:250
@ 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:1828
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition: Type.cpp:1916
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:2181
RangeSelector name(std::string ID)
Given a node with a "name", (like NamedDecl, DeclRefExpr, CxxCtorInitializer, and TypeLoc) selects th...
The JSON file list parser is used to communicate input to InstallAPI.
void printQuotedQualifiedName(llvm::raw_ostream &Os, const NamedDeclDerivedT &D)
std::optional< const clang::CXXRecordDecl * > hasPublicMethodInBase(const CXXBaseSpecifier *Base, StringRef NameToMatch)
const FunctionProtoType * T
std::string safeGetName(const T *ASTNode)
Definition: ASTUtils.h:81
@ AS_protected
Definition: Specifiers.h:125
@ AS_none
Definition: Specifiers.h:127
@ AS_private
Definition: Specifiers.h:126