xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp (revision dde802b153d5cb41505bf4d377be753576991297)
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(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 : 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(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           if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
206             for (auto &Arg : CTSD->getTemplateArgs().asArray()) {
207               if (Arg.getKind() != TemplateArgument::Type)
208                 continue;
209               auto TemplT = Arg.getAsType();
210               if (TemplT.isNull())
211                 continue;
212 
213               bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
214               if (!IsCRTP)
215                 continue;
216               CRTPs.insert(C);
217             }
218           }
219         }
220 
221         return true;
222       }
223 
224       llvm::SetVector<const CXXRecordDecl *> Decls;
225       llvm::DenseSet<const CXXRecordDecl *> CRTPs;
226     };
227 
228     LocalVisitor visitor(this);
229     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
230     for (auto *RD : visitor.Decls) {
231       if (visitor.CRTPs.contains(RD))
232         continue;
233       visitCXXRecordDecl(RD);
234     }
235   }
236 
237   void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
238     if (shouldSkipDecl(RD))
239       return;
240 
241     for (auto &Base : RD->bases()) {
242       const auto AccSpec = Base.getAccessSpecifier();
243       if (AccSpec == AS_protected || AccSpec == AS_private ||
244           (AccSpec == AS_none && RD->isClass()))
245         continue;
246 
247       auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
248       auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
249 
250       bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
251       bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
252 
253       QualType T = Base.getType();
254       if (T.isNull())
255         continue;
256 
257       const CXXRecordDecl *C = T->getAsCXXRecordDecl();
258       if (!C)
259         continue;
260 
261       bool AnyInconclusiveBase = false;
262       const auto hasPublicRefInBase =
263           [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
264             auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
265             if (!hasRefInBase) {
266               AnyInconclusiveBase = true;
267               return false;
268             }
269             return (*hasRefInBase) != nullptr;
270           };
271       const auto hasPublicDerefInBase =
272           [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
273             auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
274             if (!hasDerefInBase) {
275               AnyInconclusiveBase = true;
276               return false;
277             }
278             return (*hasDerefInBase) != nullptr;
279           };
280       CXXBasePaths Paths;
281       Paths.setOrigin(C);
282       hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
283                                           /*LookupInDependent =*/true);
284       hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
285                                               /*LookupInDependent =*/true);
286       if (AnyInconclusiveBase || !hasRef || !hasDeref)
287         continue;
288 
289       auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
290       if (!HasSpecializedDelete || *HasSpecializedDelete)
291         continue;
292       if (C->lookupInBases(
293               [&](const CXXBaseSpecifier *Base, CXXBasePath &) {
294                 auto *T = Base->getType().getTypePtrOrNull();
295                 if (!T)
296                   return false;
297                 auto *R = T->getAsCXXRecordDecl();
298                 if (!R)
299                   return false;
300                 auto Result = isClassWithSpecializedDelete(R, RD);
301                 if (!Result)
302                   AnyInconclusiveBase = true;
303                 return Result && *Result;
304               },
305               Paths, /*LookupInDependent =*/true))
306         continue;
307       if (AnyInconclusiveBase)
308         continue;
309 
310       const auto *Dtor = C->getDestructor();
311       if (!Dtor || !Dtor->isVirtual()) {
312         auto *ProblematicBaseSpecifier = &Base;
313         auto *ProblematicBaseClass = C;
314         reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
315       }
316     }
317   }
318 
319   bool shouldSkipDecl(const CXXRecordDecl *RD) const {
320     if (!RD->isThisDeclarationADefinition())
321       return true;
322 
323     if (RD->isImplicit())
324       return true;
325 
326     if (RD->isLambda())
327       return true;
328 
329     // If the construct doesn't have a source file, then it's not something
330     // we want to diagnose.
331     const auto RDLocation = RD->getLocation();
332     if (!RDLocation.isValid())
333       return true;
334 
335     const auto Kind = RD->getTagKind();
336     if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
337       return true;
338 
339     // Ignore CXXRecords that come from system headers.
340     if (BR->getSourceManager().getFileCharacteristic(RDLocation) !=
341         SrcMgr::C_User)
342       return true;
343 
344     return false;
345   }
346 
347   static bool isRefCountedClass(const CXXRecordDecl *D) {
348     if (!D->getTemplateInstantiationPattern())
349       return false;
350     auto *NsDecl = D->getParent();
351     if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
352       return false;
353     auto NamespaceName = safeGetName(NsDecl);
354     auto ClsNameStr = safeGetName(D);
355     StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
356     return NamespaceName == "WTF" &&
357            (ClsName.ends_with("RefCounted") ||
358             ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
359   }
360 
361   static std::optional<bool>
362   isClassWithSpecializedDelete(const CXXRecordDecl *C,
363                                const CXXRecordDecl *DerivedClass) {
364     if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
365       for (auto *MethodDecl : C->methods()) {
366         if (safeGetName(MethodDecl) == "deref") {
367           DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
368                                              DerivedClass);
369           auto Result = Visitor.HasSpecializedDelete(MethodDecl);
370           if (!Result || *Result)
371             return Result;
372         }
373       }
374       return false;
375     }
376     for (auto *MethodDecl : C->methods()) {
377       if (safeGetName(MethodDecl) == "deref") {
378         DerefFuncDeleteExprVisitor Visitor(DerivedClass);
379         auto Result = Visitor.HasSpecializedDelete(MethodDecl);
380         if (!Result || *Result)
381           return Result;
382       }
383     }
384     return false;
385   }
386 
387   void reportBug(const CXXRecordDecl *DerivedClass,
388                  const CXXBaseSpecifier *BaseSpec,
389                  const CXXRecordDecl *ProblematicBaseClass) const {
390     assert(DerivedClass);
391     assert(BaseSpec);
392     assert(ProblematicBaseClass);
393 
394     SmallString<100> Buf;
395     llvm::raw_svector_ostream Os(Buf);
396 
397     Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
398     printQuotedQualifiedName(Os, ProblematicBaseClass);
399 
400     Os << " is used as a base of "
401        << (DerivedClass->isClass() ? "class" : "struct") << " ";
402     printQuotedQualifiedName(Os, DerivedClass);
403 
404     Os << " but doesn't have virtual destructor";
405 
406     PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
407                                  BR->getSourceManager());
408     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
409     Report->addRange(BaseSpec->getSourceRange());
410     BR->emitReport(std::move(Report));
411   }
412 };
413 } // namespace
414 
415 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
416   Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
417 }
418 
419 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
420     const CheckerManager &mgr) {
421   return true;
422 }
423