xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp (revision 6d6693e9f5376ac8c809a36e1ba4a8c47f311a70)
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/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
16 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
17 #include "clang/StaticAnalyzer/Core/Checker.h"
18 #include <optional>
19 
20 using namespace clang;
21 using namespace ento;
22 
23 namespace {
24 class RefCntblBaseVirtualDtorChecker
25     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
26 private:
27   BugType Bug;
28   mutable BugReporter *BR;
29 
30 public:
31   RefCntblBaseVirtualDtorChecker()
32       : Bug(this,
33             "Reference-countable base class doesn't have virtual destructor",
34             "WebKit coding guidelines") {}
35 
36   void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
37                     BugReporter &BRArg) const {
38     BR = &BRArg;
39 
40     // The calls to checkAST* from AnalysisConsumer don't
41     // visit template instantiations or lambda classes. We
42     // want to visit those, so we make our own RecursiveASTVisitor.
43     struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
44       const RefCntblBaseVirtualDtorChecker *Checker;
45       explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker)
46           : Checker(Checker) {
47         assert(Checker);
48       }
49 
50       bool shouldVisitTemplateInstantiations() const { return true; }
51       bool shouldVisitImplicitCode() const { return false; }
52 
53       bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
54         Checker->visitCXXRecordDecl(RD);
55         return true;
56       }
57     };
58 
59     LocalVisitor visitor(this);
60     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
61   }
62 
63   void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
64     if (shouldSkipDecl(RD))
65       return;
66 
67     CXXBasePaths Paths;
68     Paths.setOrigin(RD);
69 
70     const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr;
71     const CXXRecordDecl *ProblematicBaseClass = nullptr;
72 
73     const auto IsPublicBaseRefCntblWOVirtualDtor =
74         [RD, &ProblematicBaseSpecifier,
75          &ProblematicBaseClass](const CXXBaseSpecifier *Base, CXXBasePath &) {
76           const auto AccSpec = Base->getAccessSpecifier();
77           if (AccSpec == AS_protected || AccSpec == AS_private ||
78               (AccSpec == AS_none && RD->isClass()))
79             return false;
80 
81           auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
82           auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
83 
84           bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
85           bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
86 
87           QualType T = Base->getType();
88           if (T.isNull())
89             return false;
90 
91           const CXXRecordDecl *C = T->getAsCXXRecordDecl();
92           if (!C)
93             return false;
94           if (isRefCountedClass(C))
95             return false;
96 
97           bool AnyInconclusiveBase = false;
98           const auto hasPublicRefInBase =
99               [&AnyInconclusiveBase](const CXXBaseSpecifier *Base,
100                                      CXXBasePath &) {
101                 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
102                 if (!hasRefInBase) {
103                   AnyInconclusiveBase = true;
104                   return false;
105                 }
106                 return (*hasRefInBase) != nullptr;
107               };
108           const auto hasPublicDerefInBase = [&AnyInconclusiveBase](
109                                                 const CXXBaseSpecifier *Base,
110                                                 CXXBasePath &) {
111             auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
112             if (!hasDerefInBase) {
113               AnyInconclusiveBase = true;
114               return false;
115             }
116             return (*hasDerefInBase) != nullptr;
117           };
118           CXXBasePaths Paths;
119           Paths.setOrigin(C);
120           hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
121                                               /*LookupInDependent =*/true);
122           hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
123                                                   /*LookupInDependent =*/true);
124           if (AnyInconclusiveBase || !hasRef || !hasDeref)
125             return false;
126 
127           const auto *Dtor = C->getDestructor();
128           if (!Dtor || !Dtor->isVirtual()) {
129             ProblematicBaseSpecifier = Base;
130             ProblematicBaseClass = C;
131             return true;
132           }
133 
134           return false;
135         };
136 
137     if (RD->lookupInBases(IsPublicBaseRefCntblWOVirtualDtor, Paths,
138                           /*LookupInDependent =*/true)) {
139       reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
140     }
141   }
142 
143   bool shouldSkipDecl(const CXXRecordDecl *RD) const {
144     if (!RD->isThisDeclarationADefinition())
145       return true;
146 
147     if (RD->isImplicit())
148       return true;
149 
150     if (RD->isLambda())
151       return true;
152 
153     // If the construct doesn't have a source file, then it's not something
154     // we want to diagnose.
155     const auto RDLocation = RD->getLocation();
156     if (!RDLocation.isValid())
157       return true;
158 
159     const auto Kind = RD->getTagKind();
160     if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
161       return true;
162 
163     // Ignore CXXRecords that come from system headers.
164     if (BR->getSourceManager().getFileCharacteristic(RDLocation) !=
165         SrcMgr::C_User)
166       return true;
167 
168     return false;
169   }
170 
171   static bool isRefCountedClass(const CXXRecordDecl *D) {
172     if (!D->getTemplateInstantiationPattern())
173       return false;
174     auto *NsDecl = D->getParent();
175     if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
176       return false;
177     auto NamespaceName = safeGetName(NsDecl);
178     auto ClsNameStr = safeGetName(D);
179     StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
180     return NamespaceName == "WTF" &&
181            (ClsName.ends_with("RefCounted") ||
182             ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
183   }
184 
185   void reportBug(const CXXRecordDecl *DerivedClass,
186                  const CXXBaseSpecifier *BaseSpec,
187                  const CXXRecordDecl *ProblematicBaseClass) const {
188     assert(DerivedClass);
189     assert(BaseSpec);
190     assert(ProblematicBaseClass);
191 
192     SmallString<100> Buf;
193     llvm::raw_svector_ostream Os(Buf);
194 
195     Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
196     printQuotedQualifiedName(Os, ProblematicBaseClass);
197 
198     Os << " is used as a base of "
199        << (DerivedClass->isClass() ? "class" : "struct") << " ";
200     printQuotedQualifiedName(Os, DerivedClass);
201 
202     Os << " but doesn't have virtual destructor";
203 
204     PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
205                                  BR->getSourceManager());
206     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
207     Report->addRange(BaseSpec->getSourceRange());
208     BR->emitReport(std::move(Report));
209   }
210 };
211 } // namespace
212 
213 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
214   Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
215 }
216 
217 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
218     const CheckerManager &mgr) {
219   return true;
220 }
221