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.
VisitChildren(const Stmt * S)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 
VisitBody(const Stmt * Body)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:
DerefFuncDeleteExprVisitor(const TemplateArgumentList & ArgList,const CXXRecordDecl * ClassDecl)51   DerefFuncDeleteExprVisitor(const TemplateArgumentList &ArgList,
52                              const CXXRecordDecl *ClassDecl)
53       : ArgList(&ArgList), ClassDecl(ClassDecl) {}
54 
DerefFuncDeleteExprVisitor(const CXXRecordDecl * ClassDecl)55   DerefFuncDeleteExprVisitor(const CXXRecordDecl *ClassDecl)
56       : ClassDecl(ClassDecl) {}
57 
HasSpecializedDelete(CXXMethodDecl * Decl)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 
VisitCallExpr(const CallExpr * CE)66   bool VisitCallExpr(const CallExpr *CE) {
67     const Decl *D = CE->getCalleeDecl();
68     if (D && D->hasBody())
69       return VisitBody(D->getBody());
70     return false;
71   }
72 
VisitCXXDeleteExpr(const CXXDeleteExpr * E)73   bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
74     auto *Arg = E->getArgument();
75     while (Arg) {
76       if (auto *Paren = dyn_cast<ParenExpr>(Arg))
77         Arg = Paren->getSubExpr();
78       else if (auto *Cast = dyn_cast<CastExpr>(Arg)) {
79         Arg = Cast->getSubExpr();
80         auto CastType = Cast->getType();
81         if (auto *PtrType = dyn_cast<PointerType>(CastType)) {
82           auto PointeeType = PtrType->getPointeeType();
83           while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) {
84             if (ET->isSugared())
85               PointeeType = ET->desugar();
86           }
87           if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) {
88             if (ArgList) {
89               auto ParmIndex = ParmType->getIndex();
90               auto Type = ArgList->get(ParmIndex).getAsType();
91               if (Type->getAsCXXRecordDecl() == ClassDecl)
92                 return true;
93             }
94           } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) {
95             if (RD->getDecl() == ClassDecl)
96               return true;
97           } else if (auto *ST =
98                          dyn_cast<SubstTemplateTypeParmType>(PointeeType)) {
99             auto Type = ST->getReplacementType();
100             if (auto *RD = dyn_cast<RecordType>(Type)) {
101               if (RD->getDecl() == ClassDecl)
102                 return true;
103             }
104           }
105         }
106       } else
107         break;
108     }
109     return false;
110   }
111 
VisitStmt(const Stmt * S)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.
VisitLambdaExpr(const LambdaExpr *)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:
RefCntblBaseVirtualDtorChecker()131   RefCntblBaseVirtualDtorChecker()
132       : Bug(this,
133             "Reference-countable base class doesn't have virtual destructor",
134             "WebKit coding guidelines") {}
135 
checkASTDecl(const TranslationUnitDecl * TUD,AnalysisManager & MGR,BugReporter & BRArg) const136   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(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>(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(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(const_cast<TranslationUnitDecl *>(TUD));
198     for (auto *RD : visitor.Decls) {
199       if (visitor.CRTPs.contains(RD))
200         continue;
201       visitCXXRecordDecl(RD);
202     }
203   }
204 
visitCXXRecordDecl(const CXXRecordDecl * RD) const205   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, "ref");
216       auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "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, "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, "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(hasPublicRefInBase, Paths,
251                                           /*LookupInDependent =*/true);
252       hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
253                                               /*LookupInDependent =*/true);
254       if (AnyInconclusiveBase || !hasRef || !hasDeref)
255         continue;
256 
257       auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
258       if (!HasSpecializedDelete || *HasSpecializedDelete)
259         continue;
260       if (C->lookupInBases(
261               [&](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(R, 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(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
283       }
284     }
285   }
286 
shouldSkipDecl(const CXXRecordDecl * RD) const287   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(RDLocation) !=
309         SrcMgr::C_User)
310       return true;
311 
312     return false;
313   }
314 
isRefCountedClass(const CXXRecordDecl * D)315   static bool isRefCountedClass(const CXXRecordDecl *D) {
316     if (!D->getTemplateInstantiationPattern())
317       return false;
318     auto *NsDecl = D->getParent();
319     if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
320       return false;
321     auto NamespaceName = safeGetName(NsDecl);
322     auto ClsNameStr = safeGetName(D);
323     StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
324     return NamespaceName == "WTF" &&
325            (ClsName.ends_with("RefCounted") ||
326             ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
327   }
328 
329   static std::optional<bool>
isClassWithSpecializedDelete(const CXXRecordDecl * C,const CXXRecordDecl * DerivedClass)330   isClassWithSpecializedDelete(const CXXRecordDecl *C,
331                                const CXXRecordDecl *DerivedClass) {
332     if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
333       for (auto *MethodDecl : C->methods()) {
334         if (safeGetName(MethodDecl) == "deref") {
335           DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
336                                              DerivedClass);
337           auto Result = Visitor.HasSpecializedDelete(MethodDecl);
338           if (!Result || *Result)
339             return Result;
340         }
341       }
342       return false;
343     }
344     for (auto *MethodDecl : C->methods()) {
345       if (safeGetName(MethodDecl) == "deref") {
346         DerefFuncDeleteExprVisitor Visitor(DerivedClass);
347         auto Result = Visitor.HasSpecializedDelete(MethodDecl);
348         if (!Result || *Result)
349           return Result;
350       }
351     }
352     return false;
353   }
354 
reportBug(const CXXRecordDecl * DerivedClass,const CXXBaseSpecifier * BaseSpec,const CXXRecordDecl * ProblematicBaseClass) const355   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, ProblematicBaseClass);
367 
368     Os << " is used as a base of "
369        << (DerivedClass->isClass() ? "class" : "struct") << " ";
370     printQuotedQualifiedName(Os, 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>(Bug, Os.str(), BSLoc);
377     Report->addRange(BaseSpec->getSourceRange());
378     BR->emitReport(std::move(Report));
379   }
380 };
381 } // namespace
382 
registerRefCntblBaseVirtualDtorChecker(CheckerManager & Mgr)383 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
384   Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
385 }
386 
shouldRegisterRefCntblBaseVirtualDtorChecker(const CheckerManager & mgr)387 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
388     const CheckerManager &mgr) {
389   return true;
390 }
391