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           bool isExempt = T.getAsString() == "NoVirtualDestructorBase" &&
206                           safeGetName(C->getParent()) == "WTF";
207           if (isExempt || ExemptDecls.contains(C)) {
208             ExemptDecls.insert(RD);
209             continue;
210           }
211 
212           if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(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(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(const_cast<TranslationUnitDecl *>(TUD));
238     for (auto *RD : visitor.Decls) {
239       if (visitor.CRTPs.contains(RD) || visitor.ExemptDecls.contains(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, "ref");
256       auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "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, "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, "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(hasPublicRefInBase, Paths,
291                                           /*LookupInDependent =*/true);
292       hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
293                                               /*LookupInDependent =*/true);
294       if (AnyInconclusiveBase || !hasRef || !hasDeref)
295         continue;
296 
297       auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
298       if (!HasSpecializedDelete || *HasSpecializedDelete)
299         continue;
300       if (C->lookupInBases(
301               [&](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(R, 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(RD, 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(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>(NsDecl))
360       return false;
361     auto NamespaceName = safeGetName(NsDecl);
362     auto ClsNameStr = safeGetName(D);
363     StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
364     return NamespaceName == "WTF" &&
365            (ClsName.ends_with("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>(C)) {
373       for (auto *MethodDecl : C->methods()) {
374         if (safeGetName(MethodDecl) == "deref") {
375           DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
376                                              DerivedClass);
377           auto Result = Visitor.HasSpecializedDelete(MethodDecl);
378           if (!Result || *Result)
379             return Result;
380         }
381       }
382       return false;
383     }
384     for (auto *MethodDecl : C->methods()) {
385       if (safeGetName(MethodDecl) == "deref") {
386         DerefFuncDeleteExprVisitor Visitor(DerivedClass);
387         auto Result = Visitor.HasSpecializedDelete(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, ProblematicBaseClass);
407 
408     Os << " is used as a base of "
409        << (DerivedClass->isClass() ? "class" : "struct") << " ";
410     printQuotedQualifiedName(Os, 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>(Bug, Os.str(), BSLoc);
417     Report->addRange(BaseSpec->getSourceRange());
418     BR->emitReport(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