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 | |
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 | 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 | |
118 | private: |
119 | const TemplateArgumentList *ArgList{nullptr}; |
120 | const CXXRecordDecl *ClassDecl; |
121 | llvm::DenseSet<const Stmt *> VisitedBody; |
122 | }; |
123 | |
124 | class RefCntblBaseVirtualDtorChecker |
125 | : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
126 | private: |
127 | BugType Bug; |
128 | mutable BugReporter *BR; |
129 | |
130 | public: |
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 | |
383 | void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { |
384 | Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); |
385 | } |
386 | |
387 | bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( |
388 | const CheckerManager &mgr) { |
389 | return true; |
390 | } |
391 | |