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/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"
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 else {
71 auto name = safeGetName(ASTNode: D);
72 if (name == "ensureOnMainThread" || name == "ensureOnMainRunLoop") {
73 for (unsigned i = 0; i < CE->getNumArgs(); ++i) {
74 auto *Arg = CE->getArg(Arg: i);
75 if (VisitLambdaArgument(E: Arg))
76 return true;
77 }
78 }
79 }
80 return false;
81 }
82
83 bool VisitLambdaArgument(const Expr *E) {
84 E = E->IgnoreParenCasts();
85 if (auto *TempE = dyn_cast<CXXBindTemporaryExpr>(Val: E))
86 E = TempE->getSubExpr();
87 E = E->IgnoreParenCasts();
88 if (auto *Ref = dyn_cast<DeclRefExpr>(Val: E)) {
89 if (auto *VD = dyn_cast_or_null<VarDecl>(Val: Ref->getDecl()))
90 return VisitLambdaArgument(E: VD->getInit());
91 return false;
92 }
93 if (auto *Lambda = dyn_cast<LambdaExpr>(Val: E)) {
94 if (VisitBody(Body: Lambda->getBody()))
95 return true;
96 }
97 if (auto *ConstructE = dyn_cast<CXXConstructExpr>(Val: E)) {
98 for (unsigned i = 0; i < ConstructE->getNumArgs(); ++i) {
99 if (VisitLambdaArgument(E: ConstructE->getArg(Arg: i)))
100 return true;
101 }
102 }
103 return false;
104 }
105
106 bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
107 auto *Arg = E->getArgument();
108 while (Arg) {
109 if (auto *Paren = dyn_cast<ParenExpr>(Val: Arg))
110 Arg = Paren->getSubExpr();
111 else if (auto *Cast = dyn_cast<CastExpr>(Val: Arg)) {
112 Arg = Cast->getSubExpr();
113 auto CastType = Cast->getType();
114 if (auto *PtrType = dyn_cast<PointerType>(Val&: CastType)) {
115 auto PointeeType = PtrType->getPointeeType();
116 while (auto *ET = dyn_cast<ElaboratedType>(Val&: PointeeType)) {
117 if (ET->isSugared())
118 PointeeType = ET->desugar();
119 }
120 if (auto *ParmType = dyn_cast<TemplateTypeParmType>(Val&: PointeeType)) {
121 if (ArgList) {
122 auto ParmIndex = ParmType->getIndex();
123 auto Type = ArgList->get(Idx: ParmIndex).getAsType();
124 if (Type->getAsCXXRecordDecl() == ClassDecl)
125 return true;
126 }
127 } else if (auto *RD = dyn_cast<RecordType>(Val&: PointeeType)) {
128 if (RD->getDecl() == ClassDecl)
129 return true;
130 } else if (auto *ST =
131 dyn_cast<SubstTemplateTypeParmType>(Val&: PointeeType)) {
132 auto Type = ST->getReplacementType();
133 if (auto *RD = dyn_cast<RecordType>(Val&: Type)) {
134 if (RD->getDecl() == ClassDecl)
135 return true;
136 }
137 }
138 }
139 } else
140 break;
141 }
142 return false;
143 }
144
145 bool VisitStmt(const Stmt *S) { return VisitChildren(S); }
146
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; }
150
151private:
152 const TemplateArgumentList *ArgList{nullptr};
153 const CXXRecordDecl *ClassDecl;
154 llvm::DenseSet<const Stmt *> VisitedBody;
155};
156
157class RefCntblBaseVirtualDtorChecker
158 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
159private:
160 BugType Bug;
161 mutable BugReporter *BR;
162
163public:
164 RefCntblBaseVirtualDtorChecker()
165 : Bug(this,
166 "Reference-countable base class doesn't have virtual destructor",
167 "WebKit coding guidelines") {}
168
169 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
170 BugReporter &BRArg) const {
171 BR = &BRArg;
172
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)
179 : Checker(Checker) {
180 assert(Checker);
181 ShouldVisitTemplateInstantiations = true;
182 ShouldVisitImplicitCode = false;
183 }
184
185 bool VisitCXXRecordDecl(CXXRecordDecl *RD) override {
186 if (!RD->hasDefinition())
187 return true;
188
189 Decls.insert(X: RD);
190
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()))
195 continue;
196
197 QualType T = Base.getType();
198 if (T.isNull())
199 continue;
200
201 const CXXRecordDecl *C = T->getAsCXXRecordDecl();
202 if (!C)
203 continue;
204
205 bool isExempt = T.getAsString() == "NoVirtualDestructorBase" &&
206 safeGetName(ASTNode: C->getParent()) == "WTF";
207 if (isExempt || ExemptDecls.contains(V: C)) {
208 ExemptDecls.insert(V: RD);
209 continue;
210 }
211
212 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: C)) {
213 for (auto &Arg : CTSD->getTemplateArgs().asArray()) {
214 if (Arg.getKind() != TemplateArgument::Type)
215 continue;
216 auto TemplT = Arg.getAsType();
217 if (TemplT.isNull())
218 continue;
219
220 bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
221 if (!IsCRTP)
222 continue;
223 CRTPs.insert(V: C);
224 }
225 }
226 }
227
228 return true;
229 }
230
231 llvm::SetVector<const CXXRecordDecl *> Decls;
232 llvm::DenseSet<const CXXRecordDecl *> CRTPs;
233 llvm::DenseSet<const CXXRecordDecl *> ExemptDecls;
234 };
235
236 LocalVisitor visitor(this);
237 visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD));
238 for (auto *RD : visitor.Decls) {
239 if (visitor.CRTPs.contains(V: RD) || visitor.ExemptDecls.contains(V: RD))
240 continue;
241 visitCXXRecordDecl(RD);
242 }
243 }
244
245 void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
246 if (shouldSkipDecl(RD))
247 return;
248
249 for (auto &Base : RD->bases()) {
250 const auto AccSpec = Base.getAccessSpecifier();
251 if (AccSpec == AS_protected || AccSpec == AS_private ||
252 (AccSpec == AS_none && RD->isClass()))
253 continue;
254
255 auto hasRefInBase = clang::hasPublicMethodInBase(Base: &Base, NameToMatch: "ref");
256 auto hasDerefInBase = clang::hasPublicMethodInBase(Base: &Base, NameToMatch: "deref");
257
258 bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
259 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
260
261 QualType T = Base.getType();
262 if (T.isNull())
263 continue;
264
265 const CXXRecordDecl *C = T->getAsCXXRecordDecl();
266 if (!C)
267 continue;
268
269 bool AnyInconclusiveBase = false;
270 const auto hasPublicRefInBase =
271 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
272 auto hasRefInBase = clang::hasPublicMethodInBase(Base, NameToMatch: "ref");
273 if (!hasRefInBase) {
274 AnyInconclusiveBase = true;
275 return false;
276 }
277 return (*hasRefInBase) != nullptr;
278 };
279 const auto hasPublicDerefInBase =
280 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
281 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, NameToMatch: "deref");
282 if (!hasDerefInBase) {
283 AnyInconclusiveBase = true;
284 return false;
285 }
286 return (*hasDerefInBase) != nullptr;
287 };
288 CXXBasePaths Paths;
289 Paths.setOrigin(C);
290 hasRef = hasRef || C->lookupInBases(BaseMatches: hasPublicRefInBase, Paths,
291 /*LookupInDependent =*/true);
292 hasDeref = hasDeref || C->lookupInBases(BaseMatches: hasPublicDerefInBase, Paths,
293 /*LookupInDependent =*/true);
294 if (AnyInconclusiveBase || !hasRef || !hasDeref)
295 continue;
296
297 auto HasSpecializedDelete = isClassWithSpecializedDelete(C, DerivedClass: RD);
298 if (!HasSpecializedDelete || *HasSpecializedDelete)
299 continue;
300 if (C->lookupInBases(
301 BaseMatches: [&](const CXXBaseSpecifier *Base, CXXBasePath &) {
302 auto *T = Base->getType().getTypePtrOrNull();
303 if (!T)
304 return false;
305 auto *R = T->getAsCXXRecordDecl();
306 if (!R)
307 return false;
308 auto Result = isClassWithSpecializedDelete(C: R, DerivedClass: RD);
309 if (!Result)
310 AnyInconclusiveBase = true;
311 return Result && *Result;
312 },
313 Paths, /*LookupInDependent =*/true))
314 continue;
315 if (AnyInconclusiveBase)
316 continue;
317
318 const auto *Dtor = C->getDestructor();
319 if (!Dtor || !Dtor->isVirtual()) {
320 auto *ProblematicBaseSpecifier = &Base;
321 auto *ProblematicBaseClass = C;
322 reportBug(DerivedClass: RD, BaseSpec: ProblematicBaseSpecifier, ProblematicBaseClass);
323 }
324 }
325 }
326
327 bool shouldSkipDecl(const CXXRecordDecl *RD) const {
328 if (!RD->isThisDeclarationADefinition())
329 return true;
330
331 if (RD->isImplicit())
332 return true;
333
334 if (RD->isLambda())
335 return true;
336
337 // If the construct doesn't have a source file, then it's not something
338 // we want to diagnose.
339 const auto RDLocation = RD->getLocation();
340 if (!RDLocation.isValid())
341 return true;
342
343 const auto Kind = RD->getTagKind();
344 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
345 return true;
346
347 // Ignore CXXRecords that come from system headers.
348 if (BR->getSourceManager().getFileCharacteristic(Loc: RDLocation) !=
349 SrcMgr::C_User)
350 return true;
351
352 return false;
353 }
354
355 static bool isRefCountedClass(const CXXRecordDecl *D) {
356 if (!D->getTemplateInstantiationPattern())
357 return false;
358 auto *NsDecl = D->getParent();
359 if (!NsDecl || !isa<NamespaceDecl>(Val: NsDecl))
360 return false;
361 auto NamespaceName = safeGetName(ASTNode: NsDecl);
362 auto ClsNameStr = safeGetName(ASTNode: D);
363 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
364 return NamespaceName == "WTF" &&
365 (ClsName.ends_with(Suffix: "RefCounted") ||
366 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
367 }
368
369 static std::optional<bool>
370 isClassWithSpecializedDelete(const CXXRecordDecl *C,
371 const CXXRecordDecl *DerivedClass) {
372 if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(Val: C)) {
373 for (auto *MethodDecl : C->methods()) {
374 if (safeGetName(ASTNode: MethodDecl) == "deref") {
375 DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
376 DerivedClass);
377 auto Result = Visitor.HasSpecializedDelete(Decl: MethodDecl);
378 if (!Result || *Result)
379 return Result;
380 }
381 }
382 return false;
383 }
384 for (auto *MethodDecl : C->methods()) {
385 if (safeGetName(ASTNode: MethodDecl) == "deref") {
386 DerefFuncDeleteExprVisitor Visitor(DerivedClass);
387 auto Result = Visitor.HasSpecializedDelete(Decl: MethodDecl);
388 if (!Result || *Result)
389 return Result;
390 }
391 }
392 return false;
393 }
394
395 void reportBug(const CXXRecordDecl *DerivedClass,
396 const CXXBaseSpecifier *BaseSpec,
397 const CXXRecordDecl *ProblematicBaseClass) const {
398 assert(DerivedClass);
399 assert(BaseSpec);
400 assert(ProblematicBaseClass);
401
402 SmallString<100> Buf;
403 llvm::raw_svector_ostream Os(Buf);
404
405 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
406 printQuotedQualifiedName(Os, D: ProblematicBaseClass);
407
408 Os << " is used as a base of "
409 << (DerivedClass->isClass() ? "class" : "struct") << " ";
410 printQuotedQualifiedName(Os, D: DerivedClass);
411
412 Os << " but doesn't have virtual destructor";
413
414 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
415 BR->getSourceManager());
416 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
417 Report->addRange(R: BaseSpec->getSourceRange());
418 BR->emitReport(R: std::move(Report));
419 }
420};
421} // namespace
422
423void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
424 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
425}
426
427bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
428 const CheckerManager &mgr) {
429 return true;
430}
431