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 | |
23 | using namespace clang; |
24 | using namespace ento; |
25 | |
26 | 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(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 | |
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 (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 | |
151 | private: |
152 | const TemplateArgumentList *ArgList{nullptr}; |
153 | const CXXRecordDecl *ClassDecl; |
154 | llvm::DenseSet<const Stmt *> VisitedBody; |
155 | }; |
156 | |
157 | class RefCntblBaseVirtualDtorChecker |
158 | : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
159 | private: |
160 | BugType Bug; |
161 | mutable BugReporter *BR; |
162 | |
163 | public: |
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 | |
423 | void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { |
424 | Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); |
425 | } |
426 | |
427 | bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( |
428 | const CheckerManager &mgr) { |
429 | return true; |
430 | } |
431 | |