1 //=======- RefCntblBaseVirtualDtor.cpp ---------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
10 #include "DiagOutputUtils.h"
11 #include "PtrTypesSemantics.h"
12 #include "clang/AST/CXXInheritance.h"
13 #include "clang/AST/DynamicRecursiveASTVisitor.h"
14 #include "clang/AST/StmtVisitor.h"
15 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
17 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
18 #include "clang/StaticAnalyzer/Core/Checker.h"
19 #include "llvm/ADT/DenseSet.h"
20 #include "llvm/ADT/SetVector.h"
23 using namespace clang
;
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
))
39 bool VisitBody(const Stmt
*Body
) {
43 auto [It
, IsNew
] = VisitedBody
.insert(Body
);
44 if (!IsNew
) // This body is recursive
51 DerefFuncDeleteExprVisitor(const TemplateArgumentList
&ArgList
,
52 const CXXRecordDecl
*ClassDecl
)
53 : ArgList(&ArgList
), ClassDecl(ClassDecl
) {}
55 DerefFuncDeleteExprVisitor(const CXXRecordDecl
*ClassDecl
)
56 : ClassDecl(ClassDecl
) {}
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.
66 bool VisitCallExpr(const CallExpr
*CE
) {
67 const Decl
*D
= CE
->getCalleeDecl();
68 if (D
&& D
->hasBody())
69 return VisitBody(D
->getBody());
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
))
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());
93 if (auto *Lambda
= dyn_cast
<LambdaExpr
>(E
)) {
94 if (VisitBody(Lambda
->getBody()))
97 if (auto *ConstructE
= dyn_cast
<CXXConstructExpr
>(E
)) {
98 for (unsigned i
= 0; i
< ConstructE
->getNumArgs(); ++i
) {
99 if (VisitLambdaArgument(ConstructE
->getArg(i
)))
106 bool VisitCXXDeleteExpr(const CXXDeleteExpr
*E
) {
107 auto *Arg
= E
->getArgument();
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
)) {
118 PointeeType
= ET
->desugar();
120 if (auto *ParmType
= dyn_cast
<TemplateTypeParmType
>(PointeeType
)) {
122 auto ParmIndex
= ParmType
->getIndex();
123 auto Type
= ArgList
->get(ParmIndex
).getAsType();
124 if (Type
->getAsCXXRecordDecl() == ClassDecl
)
127 } else if (auto *RD
= dyn_cast
<RecordType
>(PointeeType
)) {
128 if (RD
->getDecl() == ClassDecl
)
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
)
145 bool VisitStmt(const Stmt
*S
) { return VisitChildren(S
); }
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; }
152 const TemplateArgumentList
*ArgList
{nullptr};
153 const CXXRecordDecl
*ClassDecl
;
154 llvm::DenseSet
<const Stmt
*> VisitedBody
;
157 class RefCntblBaseVirtualDtorChecker
158 : public Checker
<check::ASTDecl
<TranslationUnitDecl
>> {
161 mutable BugReporter
*BR
;
164 RefCntblBaseVirtualDtorChecker()
166 "Reference-countable base class doesn't have virtual destructor",
167 "WebKit coding guidelines") {}
169 void checkASTDecl(const TranslationUnitDecl
*TUD
, AnalysisManager
&MGR
,
170 BugReporter
&BRArg
) const {
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
)
181 ShouldVisitTemplateInstantiations
= true;
182 ShouldVisitImplicitCode
= false;
185 bool VisitCXXRecordDecl(CXXRecordDecl
*RD
) override
{
186 if (!RD
->hasDefinition())
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()))
197 QualType T
= Base
.getType();
201 const CXXRecordDecl
*C
= T
->getAsCXXRecordDecl();
205 if (auto *CTSD
= dyn_cast
<ClassTemplateSpecializationDecl
>(C
)) {
206 for (auto &Arg
: CTSD
->getTemplateArgs().asArray()) {
207 if (Arg
.getKind() != TemplateArgument::Type
)
209 auto TemplT
= Arg
.getAsType();
213 bool IsCRTP
= TemplT
->getAsCXXRecordDecl() == RD
;
224 llvm::SetVector
<const CXXRecordDecl
*> Decls
;
225 llvm::DenseSet
<const CXXRecordDecl
*> CRTPs
;
228 LocalVisitor
visitor(this);
229 visitor
.TraverseDecl(const_cast<TranslationUnitDecl
*>(TUD
));
230 for (auto *RD
: visitor
.Decls
) {
231 if (visitor
.CRTPs
.contains(RD
))
233 visitCXXRecordDecl(RD
);
237 void visitCXXRecordDecl(const CXXRecordDecl
*RD
) const {
238 if (shouldSkipDecl(RD
))
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()))
247 auto hasRefInBase
= clang::hasPublicMethodInBase(&Base
, "ref");
248 auto hasDerefInBase
= clang::hasPublicMethodInBase(&Base
, "deref");
250 bool hasRef
= hasRefInBase
&& *hasRefInBase
!= nullptr;
251 bool hasDeref
= hasDerefInBase
&& *hasDerefInBase
!= nullptr;
253 QualType T
= Base
.getType();
257 const CXXRecordDecl
*C
= T
->getAsCXXRecordDecl();
261 bool AnyInconclusiveBase
= false;
262 const auto hasPublicRefInBase
=
263 [&AnyInconclusiveBase
](const CXXBaseSpecifier
*Base
, CXXBasePath
&) {
264 auto hasRefInBase
= clang::hasPublicMethodInBase(Base
, "ref");
266 AnyInconclusiveBase
= true;
269 return (*hasRefInBase
) != nullptr;
271 const auto hasPublicDerefInBase
=
272 [&AnyInconclusiveBase
](const CXXBaseSpecifier
*Base
, CXXBasePath
&) {
273 auto hasDerefInBase
= clang::hasPublicMethodInBase(Base
, "deref");
274 if (!hasDerefInBase
) {
275 AnyInconclusiveBase
= true;
278 return (*hasDerefInBase
) != nullptr;
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
)
289 auto HasSpecializedDelete
= isClassWithSpecializedDelete(C
, RD
);
290 if (!HasSpecializedDelete
|| *HasSpecializedDelete
)
292 if (C
->lookupInBases(
293 [&](const CXXBaseSpecifier
*Base
, CXXBasePath
&) {
294 auto *T
= Base
->getType().getTypePtrOrNull();
297 auto *R
= T
->getAsCXXRecordDecl();
300 auto Result
= isClassWithSpecializedDelete(R
, RD
);
302 AnyInconclusiveBase
= true;
303 return Result
&& *Result
;
305 Paths
, /*LookupInDependent =*/true))
307 if (AnyInconclusiveBase
)
310 const auto *Dtor
= C
->getDestructor();
311 if (!Dtor
|| !Dtor
->isVirtual()) {
312 auto *ProblematicBaseSpecifier
= &Base
;
313 auto *ProblematicBaseClass
= C
;
314 reportBug(RD
, ProblematicBaseSpecifier
, ProblematicBaseClass
);
319 bool shouldSkipDecl(const CXXRecordDecl
*RD
) const {
320 if (!RD
->isThisDeclarationADefinition())
323 if (RD
->isImplicit())
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())
335 const auto Kind
= RD
->getTagKind();
336 if (Kind
!= TagTypeKind::Struct
&& Kind
!= TagTypeKind::Class
)
339 // Ignore CXXRecords that come from system headers.
340 if (BR
->getSourceManager().getFileCharacteristic(RDLocation
) !=
347 static bool isRefCountedClass(const CXXRecordDecl
*D
) {
348 if (!D
->getTemplateInstantiationPattern())
350 auto *NsDecl
= D
->getParent();
351 if (!NsDecl
|| !isa
<NamespaceDecl
>(NsDecl
))
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");
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(),
369 auto Result
= Visitor
.HasSpecializedDelete(MethodDecl
);
370 if (!Result
|| *Result
)
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
)
387 void reportBug(const CXXRecordDecl
*DerivedClass
,
388 const CXXBaseSpecifier
*BaseSpec
,
389 const CXXRecordDecl
*ProblematicBaseClass
) const {
390 assert(DerivedClass
);
392 assert(ProblematicBaseClass
);
394 SmallString
<100> Buf
;
395 llvm::raw_svector_ostream
Os(Buf
);
397 Os
<< (ProblematicBaseClass
->isClass() ? "Class" : "Struct") << " ";
398 printQuotedQualifiedName(Os
, ProblematicBaseClass
);
400 Os
<< " is used as a base of "
401 << (DerivedClass
->isClass() ? "class" : "struct") << " ";
402 printQuotedQualifiedName(Os
, DerivedClass
);
404 Os
<< " but doesn't have virtual destructor";
406 PathDiagnosticLocation
BSLoc(BaseSpec
->getSourceRange().getBegin(),
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
));
415 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager
&Mgr
) {
416 Mgr
.registerChecker
<RefCntblBaseVirtualDtorChecker
>();
419 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
420 const CheckerManager
&mgr
) {