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"
12#include "clang/AST/CXXInheritance.h"
13#include "clang/AST/RecursiveASTVisitor.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"
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(S: 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(V: Body);
44 if (!IsNew) // This body is recursive
45 return false;
46
47 return Visit(S: 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(Body: 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>(Val: Arg))
77 Arg = Paren->getSubExpr();
78 else if (auto *Cast = dyn_cast<CastExpr>(Val: Arg)) {
79 Arg = Cast->getSubExpr();
80 auto CastType = Cast->getType();
81 if (auto *PtrType = dyn_cast<PointerType>(Val&: CastType)) {
82 auto PointeeType = PtrType->getPointeeType();
83 while (auto *ET = dyn_cast<ElaboratedType>(Val&: PointeeType)) {
84 if (ET->isSugared())
85 PointeeType = ET->desugar();
86 }
87 if (auto *ParmType = dyn_cast<TemplateTypeParmType>(Val&: PointeeType)) {
88 if (ArgList) {
89 auto ParmIndex = ParmType->getIndex();
90 auto Type = ArgList->get(Idx: ParmIndex).getAsType();
91 if (Type->getAsCXXRecordDecl() == ClassDecl)
92 return true;
93 }
94 } else if (auto *RD = dyn_cast<RecordType>(Val&: PointeeType)) {
95 if (RD->getDecl() == ClassDecl)
96 return true;
97 } else if (auto *ST =
98 dyn_cast<SubstTemplateTypeParmType>(Val&: PointeeType)) {
99 auto Type = ST->getReplacementType();
100 if (auto *RD = dyn_cast<RecordType>(Val&: 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(X: 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>(Val: 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(V: 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(D: const_cast<TranslationUnitDecl *>(TUD));
198 for (auto *RD : visitor.Decls) {
199 if (visitor.CRTPs.contains(V: 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: &Base, NameToMatch: "ref");
216 auto hasDerefInBase = clang::hasPublicMethodInBase(Base: &Base, NameToMatch: "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
225 const CXXRecordDecl *C = T->getAsCXXRecordDecl();
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, NameToMatch: "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, NameToMatch: "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(BaseMatches: hasPublicRefInBase, Paths,
251 /*LookupInDependent =*/true);
252 hasDeref = hasDeref || C->lookupInBases(BaseMatches: hasPublicDerefInBase, Paths,
253 /*LookupInDependent =*/true);
254 if (AnyInconclusiveBase || !hasRef || !hasDeref)
255 continue;
256
257 auto HasSpecializedDelete = isClassWithSpecializedDelete(C, DerivedClass: RD);
258 if (!HasSpecializedDelete || *HasSpecializedDelete)
259 continue;
260 if (C->lookupInBases(
261 BaseMatches: [&](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(C: R, DerivedClass: 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(DerivedClass: RD, BaseSpec: ProblematicBaseSpecifier, ProblematicBaseClass);
283 }
284 }
285 }
286
287 bool shouldSkipDecl(const CXXRecordDecl *RD) const {
288 if (!RD->isThisDeclarationADefinition())
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(Loc: RDLocation) !=
309 SrcMgr::C_User)
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>(Val: NsDecl))
320 return false;
321 auto NamespaceName = safeGetName(ASTNode: NsDecl);
322 auto ClsNameStr = safeGetName(ASTNode: D);
323 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
324 return NamespaceName == "WTF" &&
325 (ClsName.ends_with(Suffix: "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>(Val: C)) {
333 for (auto *MethodDecl : C->methods()) {
334 if (safeGetName(ASTNode: MethodDecl) == "deref") {
335 DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
336 DerivedClass);
337 auto Result = Visitor.HasSpecializedDelete(Decl: MethodDecl);
338 if (!Result || *Result)
339 return Result;
340 }
341 }
342 return false;
343 }
344 for (auto *MethodDecl : C->methods()) {
345 if (safeGetName(ASTNode: MethodDecl) == "deref") {
346 DerefFuncDeleteExprVisitor Visitor(DerivedClass);
347 auto Result = Visitor.HasSpecializedDelete(Decl: 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
362 SmallString<100> Buf;
363 llvm::raw_svector_ostream Os(Buf);
364
365 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
366 printQuotedQualifiedName(Os, D: ProblematicBaseClass);
367
368 Os << " is used as a base of "
369 << (DerivedClass->isClass() ? "class" : "struct") << " ";
370 printQuotedQualifiedName(Os, D: DerivedClass);
371
372 Os << " but doesn't have virtual destructor";
373
374 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
375 BR->getSourceManager());
376 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
377 Report->addRange(R: BaseSpec->getSourceRange());
378 BR->emitReport(R: 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}
391