clang 22.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 if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) {
117 if (ArgList) {
118 auto ParmIndex = ParmType->getIndex();
119 auto Type = ArgList->get(ParmIndex).getAsType();
120 if (Type->getAsCXXRecordDecl() == ClassDecl)
121 return true;
122 }
123 } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) {
124 if (declaresSameEntity(RD->getOriginalDecl(), ClassDecl))
125 return true;
126 } else if (auto *ST =
127 dyn_cast<SubstTemplateTypeParmType>(PointeeType)) {
128 auto Type = ST->getReplacementType();
129 if (auto *RD = dyn_cast<RecordType>(Type)) {
130 if (declaresSameEntity(RD->getOriginalDecl(), ClassDecl))
131 return true;
132 }
133 }
134 }
135 } else
136 break;
137 }
138 return false;
139 }
140
141 bool VisitStmt(const Stmt *S) { return VisitChildren(S); }
142
143 // Return false since the contents of lambda isn't necessarily executed.
144 // If it is executed, VisitCallExpr above will visit its body.
145 bool VisitLambdaExpr(const LambdaExpr *) { return false; }
146
147private:
148 const TemplateArgumentList *ArgList{nullptr};
149 const CXXRecordDecl *ClassDecl;
150 llvm::DenseSet<const Stmt *> VisitedBody;
151};
152
153class RefCntblBaseVirtualDtorChecker
154 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
155private:
156 BugType Bug;
157 mutable BugReporter *BR;
158
159public:
160 RefCntblBaseVirtualDtorChecker()
161 : Bug(this,
162 "Reference-countable base class doesn't have virtual destructor",
163 "WebKit coding guidelines") {}
164
165 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
166 BugReporter &BRArg) const {
167 BR = &BRArg;
168
169 // The calls to checkAST* from AnalysisConsumer don't
170 // visit template instantiations or lambda classes. We
171 // want to visit those, so we make our own RecursiveASTVisitor.
172 struct LocalVisitor : DynamicRecursiveASTVisitor {
173 const RefCntblBaseVirtualDtorChecker *Checker;
174 explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker)
175 : Checker(Checker) {
176 assert(Checker);
177 ShouldVisitTemplateInstantiations = true;
178 ShouldVisitImplicitCode = false;
179 }
180
181 bool VisitCXXRecordDecl(CXXRecordDecl *RD) override {
182 if (!RD->hasDefinition())
183 return true;
184
185 Decls.insert(RD);
186
187 for (auto &Base : RD->bases()) {
188 const auto AccSpec = Base.getAccessSpecifier();
189 if (AccSpec == AS_protected || AccSpec == AS_private ||
190 (AccSpec == AS_none && RD->isClass()))
191 continue;
192
193 QualType T = Base.getType();
194 if (T.isNull())
195 continue;
196
197 const CXXRecordDecl *C = T->getAsCXXRecordDecl();
198 if (!C)
199 continue;
200
201 bool isExempt = T.getAsString() == "NoVirtualDestructorBase" &&
202 safeGetName(C->getParent()) == "WTF";
203 if (isExempt || ExemptDecls.contains(C)) {
204 ExemptDecls.insert(RD);
205 continue;
206 }
207
208 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
209 for (auto &Arg : CTSD->getTemplateArgs().asArray()) {
210 if (Arg.getKind() != TemplateArgument::Type)
211 continue;
212 auto TemplT = Arg.getAsType();
213 if (TemplT.isNull())
214 continue;
215
216 bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
217 if (!IsCRTP)
218 continue;
219 CRTPs.insert(C);
220 }
221 }
222 }
223
224 return true;
225 }
226
227 llvm::SetVector<const CXXRecordDecl *> Decls;
228 llvm::DenseSet<const CXXRecordDecl *> CRTPs;
229 llvm::DenseSet<const CXXRecordDecl *> ExemptDecls;
230 };
231
232 LocalVisitor visitor(this);
233 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
234 for (auto *RD : visitor.Decls) {
235 if (visitor.CRTPs.contains(RD) || visitor.ExemptDecls.contains(RD))
236 continue;
237 visitCXXRecordDecl(RD);
238 }
239 }
240
241 void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
242 if (shouldSkipDecl(RD))
243 return;
244
245 for (auto &Base : RD->bases()) {
246 const auto AccSpec = Base.getAccessSpecifier();
247 if (AccSpec == AS_protected || AccSpec == AS_private ||
248 (AccSpec == AS_none && RD->isClass()))
249 continue;
250
251 auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
252 auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
253
254 bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
255 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
256
257 QualType T = Base.getType();
258 if (T.isNull())
259 continue;
260
261 const CXXRecordDecl *C = T->getAsCXXRecordDecl();
262 if (!C)
263 continue;
264
265 bool AnyInconclusiveBase = false;
266 const auto hasPublicRefInBase =
267 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
268 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
269 if (!hasRefInBase) {
270 AnyInconclusiveBase = true;
271 return false;
272 }
273 return (*hasRefInBase) != nullptr;
274 };
275 const auto hasPublicDerefInBase =
276 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
277 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
278 if (!hasDerefInBase) {
279 AnyInconclusiveBase = true;
280 return false;
281 }
282 return (*hasDerefInBase) != nullptr;
283 };
284 CXXBasePaths Paths;
285 Paths.setOrigin(C);
286 hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
287 /*LookupInDependent =*/true);
288 hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
289 /*LookupInDependent =*/true);
290 if (AnyInconclusiveBase || !hasRef || !hasDeref)
291 continue;
292
293 auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
294 if (!HasSpecializedDelete || *HasSpecializedDelete)
295 continue;
296 if (C->lookupInBases(
297 [&](const CXXBaseSpecifier *Base, CXXBasePath &) {
298 auto *T = Base->getType().getTypePtrOrNull();
299 if (!T)
300 return false;
301 auto *R = T->getAsCXXRecordDecl();
302 if (!R)
303 return false;
304 auto Result = isClassWithSpecializedDelete(R, RD);
305 if (!Result)
306 AnyInconclusiveBase = true;
307 return Result && *Result;
308 },
309 Paths, /*LookupInDependent =*/true))
310 continue;
311 if (AnyInconclusiveBase)
312 continue;
313
314 const auto *Dtor = C->getDestructor();
315 if (!Dtor || !Dtor->isVirtual()) {
316 auto *ProblematicBaseSpecifier = &Base;
317 auto *ProblematicBaseClass = C;
318 reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
319 }
320 }
321 }
322
323 bool shouldSkipDecl(const CXXRecordDecl *RD) const {
325 return true;
326
327 if (RD->isImplicit())
328 return true;
329
330 if (RD->isLambda())
331 return true;
332
333 // If the construct doesn't have a source file, then it's not something
334 // we want to diagnose.
335 const auto RDLocation = RD->getLocation();
336 if (!RDLocation.isValid())
337 return true;
338
339 const auto Kind = RD->getTagKind();
340 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
341 return true;
342
343 // Ignore CXXRecords that come from system headers.
344 if (BR->getSourceManager().getFileCharacteristic(RDLocation) !=
346 return true;
347
348 return false;
349 }
350
351 static bool isRefCountedClass(const CXXRecordDecl *D) {
353 return false;
354 auto *NsDecl = D->getParent();
355 if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
356 return false;
357 auto NamespaceName = safeGetName(NsDecl);
358 auto ClsNameStr = safeGetName(D);
359 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
360 return NamespaceName == "WTF" &&
361 (ClsName.ends_with("RefCounted") ||
362 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
363 }
364
365 static std::optional<bool>
366 isClassWithSpecializedDelete(const CXXRecordDecl *C,
367 const CXXRecordDecl *DerivedClass) {
368 if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
369 for (auto *MethodDecl : C->methods()) {
370 if (safeGetName(MethodDecl) == "deref") {
371 DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
372 DerivedClass);
373 auto Result = Visitor.HasSpecializedDelete(MethodDecl);
374 if (!Result || *Result)
375 return Result;
376 }
377 }
378 return false;
379 }
380 for (auto *MethodDecl : C->methods()) {
381 if (safeGetName(MethodDecl) == "deref") {
382 DerefFuncDeleteExprVisitor Visitor(DerivedClass);
383 auto Result = Visitor.HasSpecializedDelete(MethodDecl);
384 if (!Result || *Result)
385 return Result;
386 }
387 }
388 return false;
389 }
390
391 void reportBug(const CXXRecordDecl *DerivedClass,
392 const CXXBaseSpecifier *BaseSpec,
393 const CXXRecordDecl *ProblematicBaseClass) const {
394 assert(DerivedClass);
395 assert(BaseSpec);
396 assert(ProblematicBaseClass);
397
398 SmallString<100> Buf;
399 llvm::raw_svector_ostream Os(Buf);
400
401 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
402 printQuotedQualifiedName(Os, ProblematicBaseClass);
403
404 Os << " is used as a base of "
405 << (DerivedClass->isClass() ? "class" : "struct") << " ";
406 printQuotedQualifiedName(Os, DerivedClass);
407
408 Os << " but doesn't have virtual destructor";
409
410 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
411 BR->getSourceManager());
412 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
413 Report->addRange(BaseSpec->getSourceRange());
414 BR->emitReport(std::move(Report));
415 }
416};
417} // namespace
418
419void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
420 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
421}
422
423bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
424 const CheckerManager &mgr) {
425 return true;
426}
CastType
Definition SemaCast.cpp:49
void setOrigin(const CXXRecordDecl *Rec)
SourceRange getSourceRange() const LLVM_READONLY
Retrieves the source range that contains the entire base specifier.
Definition DeclCXX.h:193
base_class_range bases()
Definition DeclCXX.h:608
bool isLambda() const
Determine whether this class describes a lambda function object.
Definition DeclCXX.h:1018
const CXXRecordDecl * getTemplateInstantiationPattern() const
Retrieve the record declaration from which this record could be instantiated.
Definition DeclCXX.cpp:2075
bool hasDefinition() const
Definition DeclCXX.h:561
Expr * getArg(unsigned Arg)
getArg - Return the specified argument.
Definition Expr.h:3081
unsigned getNumArgs() const
getNumArgs - Return the number of actual arguments to this call.
Definition Expr.h:3068
Decl * getCalleeDecl()
Definition Expr.h:3054
ConstStmtVisitor - This class implements a simple visitor for Stmt subclasses.
DeclContext * getParent()
getParent - Returns the containing DeclContext.
Definition DeclBase.h:2109
bool isImplicit() const
isImplicit - Indicates whether the declaration was implicitly generated by the implementation.
Definition DeclBase.h:593
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:1087
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:1093
SourceLocation getLocation() const
Definition DeclBase.h:439
Expr * IgnoreParenCasts() LLVM_READONLY
Skip past any parentheses and casts which might surround this expression until reaching a fixed point...
Definition Expr.cpp:3078
SourceLocation getBegin() const
child_range children()
Definition Stmt.cpp:295
bool isThisDeclarationADefinition() const
Return true if this declaration is a completion definition of the type.
Definition Decl.h:3804
bool isClass() const
Definition Decl.h:3918
TagKind getTagKind() const
Definition Decl.h:3908
@ Type
The template argument is a type.
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition Type.h:26
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:553
bool Cast(InterpState &S, CodePtr OpPC)
Definition Interp.h:2500
std::variant< struct RequiresDecl, struct HeaderDecl, struct UmbrellaDirDecl, struct ModuleDecl, struct ExcludeDecl, struct ExportDecl, struct ExportAsDecl, struct ExternModuleDecl, struct UseDecl, struct LinkDecl, struct ConfigMacrosDecl, struct ConflictDecl > Decl
All declarations that can appear in a module declaration.
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.
bool isa(CodeGen::Address addr)
Definition Address.h:330
void printQuotedQualifiedName(llvm::raw_ostream &Os, const NamedDeclDerivedT &D)
@ AS_protected
Definition Specifiers.h:125
@ AS_none
Definition Specifiers.h:127
@ AS_private
Definition Specifiers.h:126
@ Result
The result type of a method or function.
Definition TypeBase.h:905
const FunctionProtoType * T
std::optional< const clang::CXXRecordDecl * > hasPublicMethodInBase(const CXXBaseSpecifier *Base, StringRef NameToMatch)
@ Type
The name was classified as a type.
Definition Sema.h:562
std::string safeGetName(const T *ASTNode)
Definition ASTUtils.h:90
bool declaresSameEntity(const Decl *D1, const Decl *D2)
Determine whether two declarations declare the same entity.
Definition DeclBase.h:1288
DynamicRecursiveASTVisitorBase< false > DynamicRecursiveASTVisitor