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, NameToMatch: "ref" ); |
81 | auto hasDerefInBase = clang::hasPublicMethodInBase(Base, NameToMatch: "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, NameToMatch: "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, NameToMatch: "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(BaseMatches: hasPublicRefInBase, Paths, |
117 | /*LookupInDependent =*/true); |
118 | hasDeref = hasDeref || C->lookupInBases(BaseMatches: 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(BaseMatches: IsPublicBaseRefCntblWOVirtualDtor, Paths, |
134 | /*LookupInDependent =*/true)) { |
135 | reportBug(DerivedClass: RD, BaseSpec: 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(Loc: 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, D: ProblematicBaseClass); |
179 | |
180 | Os << " is used as a base of " |
181 | << (DerivedClass->isClass() ? "class" : "struct" ) << " " ; |
182 | printQuotedQualifiedName(Os, D: 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>(args: Bug, args: Os.str(), args&: BSLoc); |
189 | Report->addRange(R: BaseSpec->getSourceRange()); |
190 | BR->emitReport(R: 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 | |