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 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
118private:
119 const TemplateArgumentList *ArgList{nullptr};
120 const CXXRecordDecl *ClassDecl;
121 llvm::DenseSet<const Stmt *> VisitedBody;
122};
123
124class RefCntblBaseVirtualDtorChecker
125 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
126private:
127 BugType Bug;
128 mutable BugReporter *BR;
129
130public:
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
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;
193 llvm::DenseSet<const CXXRecordDecl *> CRTPs;
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
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 {
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();
304 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
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) {
316 if (!D->getTemplateInstantiationPattern())
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
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
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
383void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
384 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
385}
386
387bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
388 const CheckerManager &mgr) {
389 return true;
390}
const Decl * D
Expr * E
CastType
Definition: SemaCast.cpp:49
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:2064
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:1023
bool hasDefinition() const
Definition: DeclCXX.h:572
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2830
Decl * getCalleeDecl()
Definition: Expr.h:2994
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:600
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:446
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:941
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
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:3659
bool isClass() const
Definition: Decl.h:3766
TagKind getTagKind() const
Definition: Decl.h:3756
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:1829
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition: Type.cpp:1882
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:2052
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)
const FunctionProtoType * T
std::string safeGetName(const T *ASTNode)
Definition: ASTUtils.h:68
@ AS_protected
Definition: Specifiers.h:125
@ AS_none
Definition: Specifiers.h:127
@ AS_private
Definition: Specifiers.h:126