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