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