Skip to content

Commit faef8b4

Browse files
authored
[webkit.RefCntblBaseVirtualDtor] Allow CRTP classes without a virtual destructor. (#92837)
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 DerefFuncDeleteExprVisitor, 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 DerefFuncDeleteExprVisitor 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 c16538f commit faef8b4

File tree

3 files changed

+548
-69
lines changed

3 files changed

+548
-69
lines changed

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

Lines changed: 231 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,116 @@
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 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+
50+
public:
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 (auto *Tmpl = 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+
118+
private:
119+
const TemplateArgumentList *ArgList{nullptr};
120+
const CXXRecordDecl *ClassDecl;
121+
llvm::DenseSet<const Stmt *> VisitedBody;
122+
};
123+
24124
class RefCntblBaseVirtualDtorChecker
25125
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
26126
private:
@@ -51,92 +151,136 @@ class RefCntblBaseVirtualDtorChecker
51151
bool shouldVisitImplicitCode() const { return false; }
52152

53153
bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
54-
Checker->visitCXXRecordDecl(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+
169+
const CXXRecordDecl *C = T->getAsCXXRecordDecl();
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+
55189
return true;
56190
}
191+
192+
llvm::SetVector<const CXXRecordDecl *> Decls;
193+
llvm::DenseSet<const CXXRecordDecl *> CRTPs;
57194
};
58195

59196
LocalVisitor visitor(this);
60197
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
198+
for (auto *RD : visitor.Decls) {
199+
if (visitor.CRTPs.contains(RD))
200+
continue;
201+
visitCXXRecordDecl(RD);
202+
}
61203
}
62204

63205
void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
64206
if (shouldSkipDecl(RD))
65207
return;
66208

67-
CXXBasePaths Paths;
68-
Paths.setOrigin(RD);
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;
69214

70-
const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr;
71-
const CXXRecordDecl *ProblematicBaseClass = nullptr;
215+
auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
216+
auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
72217

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;
218+
bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
219+
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
80220

81-
auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
82-
auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
221+
QualType T = Base.getType();
222+
if (T.isNull())
223+
continue;
83224

84-
bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
85-
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
225+
const CXXRecordDecl *C = T->getAsCXXRecordDecl();
226+
if (!C)
227+
continue;
86228

87-
QualType T = Base->getType();
88-
if (T.isNull())
89-
return false;
90-
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 &) {
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 &) {
111241
auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
112242
if (!hasDerefInBase) {
113243
AnyInconclusiveBase = true;
114244
return false;
115245
}
116246
return (*hasDerefInBase) != nullptr;
117247
};
118-
CXXBasePaths Paths;
119-
Paths.setOrigin(C);
120-
hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
248+
CXXBasePaths Paths;
249+
Paths.setOrigin(C);
250+
hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
251+
/*LookupInDependent =*/true);
252+
hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
121253
/*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);
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+
}
140284
}
141285
}
142286

@@ -182,6 +326,32 @@ class RefCntblBaseVirtualDtorChecker
182326
ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
183327
}
184328

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+
185355
void reportBug(const CXXRecordDecl *DerivedClass,
186356
const CXXBaseSpecifier *BaseSpec,
187357
const CXXRecordDecl *ProblematicBaseClass) const {

clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor-ref-deref-on-diff-classes.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ struct Derived : Base { };
1919

2020
void foo () {
2121
Derived d;
22+
d.deref();
2223
}

0 commit comments

Comments
 (0)