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