Skip to content

Commit 9cbdf82

Browse files
author
Ryosuke Niwa
committed
[webkit.RefCntblBaseVirtualDtor] Allow CRTP classes without a virtual destructor.
Exempt CRTP (Curiously Recurring Template Pattern) classes with a delete operation acting on "this" pointer with an appropriate cast from the requirement that a ref-countable superclass must have a virtual destructor. To do this, this PR introduces new DerefAnalysisVisitor, which looks for a delete operation with an explicit cast to the derived class in a base class. This PR also changes the checker so that we only check a given class's immediate base class instead of all ancestor base classes in the class hierarchy. This is sufficient because the checker will eventually see the definition for every class in the class hierarchy and transitively proves every ref-counted base class has a virtual destructor or deref function which casts this pointer back to the derived class before deleting. Without this change, we would keep traversing the same list of base classes whenever we encounter a new subclass, which is wholly unnecessary. It's possible for DerefAnalysisVisitor to come to a conclusoin that there isn't enough information to determine whether a given templated superclass invokes delete operation on a subclass when the template isn't fully specialized for the subclass. In this case, we return std::nullopt in HasSpecializedDelete, and visitCXXRecordDecl will skip this declaration. This is okay because the checker will eventually see a concreate fully specialized class definition if it ever gets instantiated.
1 parent 72200fc commit 9cbdf82

File tree

2 files changed

+566
-69
lines changed

2 files changed

+566
-69
lines changed

clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp

Lines changed: 250 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,134 @@
1111
#include "PtrTypesSemantics.h"
1212
#include "clang/AST/CXXInheritance.h"
1313
#include "clang/AST/RecursiveASTVisitor.h"
14+
#include "clang/AST/StmtVisitor.h"
1415
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1516
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
1617
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
1718
#include "clang/StaticAnalyzer/Core/Checker.h"
19+
#include "llvm/ADT/DenseSet.h"
20+
#include "llvm/ADT/SetVector.h"
1821
#include <optional>
1922

2023
using namespace clang;
2124
using namespace ento;
2225

2326
namespace {
27+
28+
class DerefAnalysisVisitor
29+
: public ConstStmtVisitor<DerefAnalysisVisitor, 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+
DerefAnalysisVisitor(const TemplateArgumentList &ArgList,
52+
const CXXRecordDecl *ClassDecl)
53+
: ArgList(&ArgList), ClassDecl(ClassDecl) {}
54+
55+
DerefAnalysisVisitor(const CXXRecordDecl *ClassDecl) : ClassDecl(ClassDecl) {}
56+
57+
std::optional<bool> HasSpecializedDelete(CXXMethodDecl *Decl) {
58+
if (auto *Body = Decl->getBody())
59+
return VisitBody(Body);
60+
if (auto *Tmpl = Decl->getTemplateInstantiationPattern())
61+
return std::nullopt; // Indeterminate. There was no concrete instance.
62+
return false;
63+
}
64+
65+
bool VisitCallExpr(const CallExpr *CE) {
66+
auto *Callee = CE->getCallee();
67+
while (auto *Expr = dyn_cast<CastExpr>(Callee))
68+
Callee = Expr->getSubExpr();
69+
if (auto *DeclRef = dyn_cast<DeclRefExpr>(Callee)) {
70+
auto *Decl = DeclRef->getDecl();
71+
if (auto *VD = dyn_cast<VarDecl>(Decl)) {
72+
if (auto *Init = VD->getInit()) {
73+
if (auto *Lambda = dyn_cast<LambdaExpr>(Init))
74+
return VisitBody(Lambda->getBody());
75+
}
76+
} else if (auto *FD = dyn_cast<FunctionDecl>(Decl))
77+
return VisitBody(FD->getBody());
78+
}
79+
return false;
80+
}
81+
82+
bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
83+
auto *Callee = MCE->getMethodDecl();
84+
if (!Callee)
85+
return false;
86+
return VisitBody(Callee->getBody());
87+
}
88+
89+
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
90+
auto *Arg = E->getArgument();
91+
while (Arg) {
92+
if (auto *Paren = dyn_cast<ParenExpr>(Arg))
93+
Arg = Paren->getSubExpr();
94+
else if (auto *Cast = dyn_cast<CastExpr>(Arg)) {
95+
Arg = Cast->getSubExpr();
96+
auto CastType = Cast->getType();
97+
if (auto *PtrType = dyn_cast<PointerType>(CastType)) {
98+
auto PointeeType = PtrType->getPointeeType();
99+
while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) {
100+
if (ET->isSugared())
101+
PointeeType = ET->desugar();
102+
}
103+
if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) {
104+
if (ArgList) {
105+
auto ParmIndex = ParmType->getIndex();
106+
auto Type = ArgList->get(ParmIndex).getAsType();
107+
if (auto *RD = dyn_cast<RecordType>(Type)) {
108+
if (RD->getDecl() == ClassDecl)
109+
return true;
110+
}
111+
}
112+
} else if (auto *RD = dyn_cast<RecordType>(PointeeType)) {
113+
if (RD->getDecl() == ClassDecl)
114+
return true;
115+
} else if (auto *ST =
116+
dyn_cast<SubstTemplateTypeParmType>(PointeeType)) {
117+
auto Type = ST->getReplacementType();
118+
if (auto *RD = dyn_cast<RecordType>(Type)) {
119+
if (RD->getDecl() == ClassDecl)
120+
return true;
121+
}
122+
}
123+
}
124+
} else
125+
break;
126+
}
127+
return false;
128+
}
129+
130+
bool VisitStmt(const Stmt *S) { return VisitChildren(S); }
131+
132+
// Return false since the contents of lambda isn't necessarily executed.
133+
// If it is executed, VisitCallExpr above will visit its body.
134+
bool VisitLambdaExpr(const LambdaExpr *) { return false; }
135+
136+
private:
137+
const TemplateArgumentList *ArgList{nullptr};
138+
const CXXRecordDecl *ClassDecl;
139+
llvm::DenseSet<const Stmt *> VisitedBody;
140+
};
141+
24142
class RefCntblBaseVirtualDtorChecker
25143
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
26144
private:
@@ -51,92 +169,137 @@ class RefCntblBaseVirtualDtorChecker
51169
bool shouldVisitImplicitCode() const { return false; }
52170

53171
bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
54-
Checker->visitCXXRecordDecl(RD);
172+
if (!RD->hasDefinition())
173+
return true;
174+
175+
Decls.insert(RD);
176+
177+
for (auto &Base : RD->bases()) {
178+
const auto AccSpec = Base.getAccessSpecifier();
179+
if (AccSpec == AS_protected || AccSpec == AS_private ||
180+
(AccSpec == AS_none && RD->isClass()))
181+
continue;
182+
183+
QualType T = Base.getType();
184+
if (T.isNull())
185+
continue;
186+
187+
const CXXRecordDecl *C = T->getAsCXXRecordDecl();
188+
if (!C)
189+
continue;
190+
191+
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
192+
auto &Args = CTSD->getTemplateArgs();
193+
for (unsigned i = 0; i < Args.size(); ++i) {
194+
if (Args[i].getKind() != TemplateArgument::Type)
195+
continue;
196+
auto TemplT = Args[i].getAsType();
197+
if (TemplT.isNull())
198+
continue;
199+
200+
bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
201+
if (!IsCRTP)
202+
continue;
203+
CRTPs.insert(C);
204+
}
205+
}
206+
}
207+
55208
return true;
56209
}
210+
211+
llvm::SetVector<const CXXRecordDecl *> Decls;
212+
llvm::DenseSet<const CXXRecordDecl *> CRTPs;
57213
};
58214

59215
LocalVisitor visitor(this);
60216
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
217+
for (auto *RD : visitor.Decls) {
218+
if (visitor.CRTPs.contains(RD))
219+
continue;
220+
visitCXXRecordDecl(RD);
221+
}
61222
}
62223

63224
void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
64225
if (shouldSkipDecl(RD))
65226
return;
66227

67-
CXXBasePaths Paths;
68-
Paths.setOrigin(RD);
69-
70-
const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr;
71-
const CXXRecordDecl *ProblematicBaseClass = nullptr;
228+
for (auto &Base : RD->bases()) {
229+
const auto AccSpec = Base.getAccessSpecifier();
230+
if (AccSpec == AS_protected || AccSpec == AS_private ||
231+
(AccSpec == AS_none && RD->isClass()))
232+
continue;
72233

73-
const auto IsPublicBaseRefCntblWOVirtualDtor =
74-
[RD, &ProblematicBaseSpecifier,
75-
&ProblematicBaseClass](const CXXBaseSpecifier *Base, CXXBasePath &) {
76-
const auto AccSpec = Base->getAccessSpecifier();
77-
if (AccSpec == AS_protected || AccSpec == AS_private ||
78-
(AccSpec == AS_none && RD->isClass()))
79-
return false;
234+
auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
235+
auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
80236

81-
auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
82-
auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
237+
bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
238+
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
83239

84-
bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
85-
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
240+
QualType T = Base.getType();
241+
if (T.isNull())
242+
continue;
86243

87-
QualType T = Base->getType();
88-
if (T.isNull())
89-
return false;
244+
const CXXRecordDecl *C = T->getAsCXXRecordDecl();
245+
if (!C)
246+
continue;
90247

91-
const CXXRecordDecl *C = T->getAsCXXRecordDecl();
92-
if (!C)
93-
return false;
94-
if (isRefCountedClass(C))
95-
return false;
96-
97-
bool AnyInconclusiveBase = false;
98-
const auto hasPublicRefInBase =
99-
[&AnyInconclusiveBase](const CXXBaseSpecifier *Base,
100-
CXXBasePath &) {
101-
auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
102-
if (!hasRefInBase) {
103-
AnyInconclusiveBase = true;
104-
return false;
105-
}
106-
return (*hasRefInBase) != nullptr;
107-
};
108-
const auto hasPublicDerefInBase = [&AnyInconclusiveBase](
109-
const CXXBaseSpecifier *Base,
110-
CXXBasePath &) {
248+
bool AnyInconclusiveBase = false;
249+
const auto hasPublicRefInBase =
250+
[&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
251+
auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
252+
if (!hasRefInBase) {
253+
AnyInconclusiveBase = true;
254+
return false;
255+
}
256+
return (*hasRefInBase) != nullptr;
257+
};
258+
const auto hasPublicDerefInBase =
259+
[&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
111260
auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
112261
if (!hasDerefInBase) {
113262
AnyInconclusiveBase = true;
114263
return false;
115264
}
116265
return (*hasDerefInBase) != nullptr;
117266
};
118-
CXXBasePaths Paths;
119-
Paths.setOrigin(C);
120-
hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
267+
CXXBasePaths Paths;
268+
Paths.setOrigin(C);
269+
hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
270+
/*LookupInDependent =*/true);
271+
hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
121272
/*LookupInDependent =*/true);
122-
hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
123-
/*LookupInDependent =*/true);
124-
if (AnyInconclusiveBase || !hasRef || !hasDeref)
125-
return false;
126-
127-
const auto *Dtor = C->getDestructor();
128-
if (!Dtor || !Dtor->isVirtual()) {
129-
ProblematicBaseSpecifier = Base;
130-
ProblematicBaseClass = C;
131-
return true;
132-
}
133-
134-
return false;
135-
};
136-
137-
if (RD->lookupInBases(IsPublicBaseRefCntblWOVirtualDtor, Paths,
138-
/*LookupInDependent =*/true)) {
139-
reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
273+
if (AnyInconclusiveBase || !hasRef || !hasDeref)
274+
continue;
275+
276+
auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
277+
if (!HasSpecializedDelete || *HasSpecializedDelete)
278+
continue;
279+
if (C->lookupInBases(
280+
[&](const CXXBaseSpecifier *Base, CXXBasePath &) {
281+
auto *T = Base->getType().getTypePtrOrNull();
282+
if (!T)
283+
return false;
284+
auto *R = T->getAsCXXRecordDecl();
285+
if (!R)
286+
return false;
287+
auto Result = isClassWithSpecializedDelete(R, RD);
288+
if (!Result)
289+
AnyInconclusiveBase = true;
290+
return Result && *Result;
291+
},
292+
Paths, /*LookupInDependent =*/true))
293+
continue;
294+
if (AnyInconclusiveBase)
295+
continue;
296+
297+
const auto *Dtor = C->getDestructor();
298+
if (!Dtor || !Dtor->isVirtual()) {
299+
auto *ProblematicBaseSpecifier = &Base;
300+
auto *ProblematicBaseClass = C;
301+
reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
302+
}
140303
}
141304
}
142305

@@ -182,6 +345,32 @@ class RefCntblBaseVirtualDtorChecker
182345
ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
183346
}
184347

348+
static std::optional<bool>
349+
isClassWithSpecializedDelete(const CXXRecordDecl *C,
350+
const CXXRecordDecl *DerivedClass) {
351+
if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
352+
for (auto *MethodDecl : C->methods()) {
353+
if (safeGetName(MethodDecl) == "deref") {
354+
DerefAnalysisVisitor DerefAnalysis(ClsTmplSpDecl->getTemplateArgs(),
355+
DerivedClass);
356+
auto Result = DerefAnalysis.HasSpecializedDelete(MethodDecl);
357+
if (!Result || *Result)
358+
return Result;
359+
}
360+
}
361+
return false;
362+
}
363+
for (auto *MethodDecl : C->methods()) {
364+
if (safeGetName(MethodDecl) == "deref") {
365+
DerefAnalysisVisitor DerefAnalysis(DerivedClass);
366+
auto Result = DerefAnalysis.HasSpecializedDelete(MethodDecl);
367+
if (!Result || *Result)
368+
return Result;
369+
}
370+
}
371+
return false;
372+
}
373+
185374
void reportBug(const CXXRecordDecl *DerivedClass,
186375
const CXXBaseSpecifier *BaseSpec,
187376
const CXXRecordDecl *ProblematicBaseClass) const {

0 commit comments

Comments
 (0)