|
11 | 11 | #include "PtrTypesSemantics.h"
|
12 | 12 | #include "clang/AST/CXXInheritance.h"
|
13 | 13 | #include "clang/AST/RecursiveASTVisitor.h"
|
| 14 | +#include "clang/AST/StmtVisitor.h" |
14 | 15 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
15 | 16 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
|
16 | 17 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
17 | 18 | #include "clang/StaticAnalyzer/Core/Checker.h"
|
| 19 | +#include "llvm/ADT/DenseSet.h" |
| 20 | +#include "llvm/ADT/SetVector.h" |
18 | 21 | #include <optional>
|
19 | 22 |
|
20 | 23 | using namespace clang;
|
21 | 24 | using namespace ento;
|
22 | 25 |
|
23 | 26 | namespace {
|
| 27 | + |
| 28 | +class DerefAnalysisVisitor |
| 29 | + : public ConstStmtVisitor<DerefAnalysisVisitor, 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 | + DerefAnalysisVisitor(const TemplateArgumentList &ArgList, |
| 52 | + const CXXRecordDecl *ClassDecl) |
| 53 | + : ArgList(&ArgList), ClassDecl(ClassDecl) {} |
| 54 | + |
| 55 | + DerefAnalysisVisitor(const CXXRecordDecl *ClassDecl) : ClassDecl(ClassDecl) {} |
| 56 | + |
| 57 | + std::optional<bool> HasSpecializedDelete(CXXMethodDecl *Decl) { |
| 58 | + if (auto *Body = Decl->getBody()) |
| 59 | + return VisitBody(Body); |
| 60 | + if (auto *Tmpl = Decl->getTemplateInstantiationPattern()) |
| 61 | + return std::nullopt; // Indeterminate. There was no concrete instance. |
| 62 | + return false; |
| 63 | + } |
| 64 | + |
| 65 | + bool VisitCallExpr(const CallExpr *CE) { |
| 66 | + auto *Callee = CE->getCallee(); |
| 67 | + while (auto *Expr = dyn_cast<CastExpr>(Callee)) |
| 68 | + Callee = Expr->getSubExpr(); |
| 69 | + if (auto *DeclRef = dyn_cast<DeclRefExpr>(Callee)) { |
| 70 | + auto *Decl = DeclRef->getDecl(); |
| 71 | + if (auto *VD = dyn_cast<VarDecl>(Decl)) { |
| 72 | + if (auto *Init = VD->getInit()) { |
| 73 | + if (auto *Lambda = dyn_cast<LambdaExpr>(Init)) |
| 74 | + return VisitBody(Lambda->getBody()); |
| 75 | + } |
| 76 | + } else if (auto *FD = dyn_cast<FunctionDecl>(Decl)) |
| 77 | + return VisitBody(FD->getBody()); |
| 78 | + } |
| 79 | + return false; |
| 80 | + } |
| 81 | + |
| 82 | + bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { |
| 83 | + auto *Callee = MCE->getMethodDecl(); |
| 84 | + if (!Callee) |
| 85 | + return false; |
| 86 | + return VisitBody(Callee->getBody()); |
| 87 | + } |
| 88 | + |
| 89 | + bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) { |
| 90 | + auto *Arg = E->getArgument(); |
| 91 | + while (Arg) { |
| 92 | + if (auto *Paren = dyn_cast<ParenExpr>(Arg)) |
| 93 | + Arg = Paren->getSubExpr(); |
| 94 | + else if (auto *Cast = dyn_cast<CastExpr>(Arg)) { |
| 95 | + Arg = Cast->getSubExpr(); |
| 96 | + auto CastType = Cast->getType(); |
| 97 | + if (auto *PtrType = dyn_cast<PointerType>(CastType)) { |
| 98 | + auto PointeeType = PtrType->getPointeeType(); |
| 99 | + while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) { |
| 100 | + if (ET->isSugared()) |
| 101 | + PointeeType = ET->desugar(); |
| 102 | + } |
| 103 | + if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) { |
| 104 | + if (ArgList) { |
| 105 | + auto ParmIndex = ParmType->getIndex(); |
| 106 | + auto Type = ArgList->get(ParmIndex).getAsType(); |
| 107 | + if (auto *RD = dyn_cast<RecordType>(Type)) { |
| 108 | + if (RD->getDecl() == ClassDecl) |
| 109 | + return true; |
| 110 | + } |
| 111 | + } |
| 112 | + } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) { |
| 113 | + if (RD->getDecl() == ClassDecl) |
| 114 | + return true; |
| 115 | + } else if (auto *ST = |
| 116 | + dyn_cast<SubstTemplateTypeParmType>(PointeeType)) { |
| 117 | + auto Type = ST->getReplacementType(); |
| 118 | + if (auto *RD = dyn_cast<RecordType>(Type)) { |
| 119 | + if (RD->getDecl() == ClassDecl) |
| 120 | + return true; |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | + } else |
| 125 | + break; |
| 126 | + } |
| 127 | + return false; |
| 128 | + } |
| 129 | + |
| 130 | + bool VisitStmt(const Stmt *S) { return VisitChildren(S); } |
| 131 | + |
| 132 | + // Return false since the contents of lambda isn't necessarily executed. |
| 133 | + // If it is executed, VisitCallExpr above will visit its body. |
| 134 | + bool VisitLambdaExpr(const LambdaExpr *) { return false; } |
| 135 | + |
| 136 | +private: |
| 137 | + const TemplateArgumentList *ArgList{nullptr}; |
| 138 | + const CXXRecordDecl *ClassDecl; |
| 139 | + llvm::DenseSet<const Stmt *> VisitedBody; |
| 140 | +}; |
| 141 | + |
24 | 142 | class RefCntblBaseVirtualDtorChecker
|
25 | 143 | : public Checker<check::ASTDecl<TranslationUnitDecl>> {
|
26 | 144 | private:
|
@@ -51,92 +169,137 @@ class RefCntblBaseVirtualDtorChecker
|
51 | 169 | bool shouldVisitImplicitCode() const { return false; }
|
52 | 170 |
|
53 | 171 | bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
|
54 |
| - Checker->visitCXXRecordDecl(RD); |
| 172 | + if (!RD->hasDefinition()) |
| 173 | + return true; |
| 174 | + |
| 175 | + Decls.insert(RD); |
| 176 | + |
| 177 | + for (auto &Base : RD->bases()) { |
| 178 | + const auto AccSpec = Base.getAccessSpecifier(); |
| 179 | + if (AccSpec == AS_protected || AccSpec == AS_private || |
| 180 | + (AccSpec == AS_none && RD->isClass())) |
| 181 | + continue; |
| 182 | + |
| 183 | + QualType T = Base.getType(); |
| 184 | + if (T.isNull()) |
| 185 | + continue; |
| 186 | + |
| 187 | + const CXXRecordDecl *C = T->getAsCXXRecordDecl(); |
| 188 | + if (!C) |
| 189 | + continue; |
| 190 | + |
| 191 | + if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) { |
| 192 | + auto &Args = CTSD->getTemplateArgs(); |
| 193 | + for (unsigned i = 0; i < Args.size(); ++i) { |
| 194 | + if (Args[i].getKind() != TemplateArgument::Type) |
| 195 | + continue; |
| 196 | + auto TemplT = Args[i].getAsType(); |
| 197 | + if (TemplT.isNull()) |
| 198 | + continue; |
| 199 | + |
| 200 | + bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD; |
| 201 | + if (!IsCRTP) |
| 202 | + continue; |
| 203 | + CRTPs.insert(C); |
| 204 | + } |
| 205 | + } |
| 206 | + } |
| 207 | + |
55 | 208 | return true;
|
56 | 209 | }
|
| 210 | + |
| 211 | + llvm::SetVector<const CXXRecordDecl *> Decls; |
| 212 | + llvm::DenseSet<const CXXRecordDecl *> CRTPs; |
57 | 213 | };
|
58 | 214 |
|
59 | 215 | LocalVisitor visitor(this);
|
60 | 216 | visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
|
| 217 | + for (auto *RD : visitor.Decls) { |
| 218 | + if (visitor.CRTPs.contains(RD)) |
| 219 | + continue; |
| 220 | + visitCXXRecordDecl(RD); |
| 221 | + } |
61 | 222 | }
|
62 | 223 |
|
63 | 224 | void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
|
64 | 225 | if (shouldSkipDecl(RD))
|
65 | 226 | return;
|
66 | 227 |
|
67 |
| - CXXBasePaths Paths; |
68 |
| - Paths.setOrigin(RD); |
69 |
| - |
70 |
| - const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr; |
71 |
| - const CXXRecordDecl *ProblematicBaseClass = nullptr; |
| 228 | + for (auto &Base : RD->bases()) { |
| 229 | + const auto AccSpec = Base.getAccessSpecifier(); |
| 230 | + if (AccSpec == AS_protected || AccSpec == AS_private || |
| 231 | + (AccSpec == AS_none && RD->isClass())) |
| 232 | + continue; |
72 | 233 |
|
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; |
| 234 | + auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref"); |
| 235 | + auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref"); |
80 | 236 |
|
81 |
| - auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); |
82 |
| - auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); |
| 237 | + bool hasRef = hasRefInBase && *hasRefInBase != nullptr; |
| 238 | + bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; |
83 | 239 |
|
84 |
| - bool hasRef = hasRefInBase && *hasRefInBase != nullptr; |
85 |
| - bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; |
| 240 | + QualType T = Base.getType(); |
| 241 | + if (T.isNull()) |
| 242 | + continue; |
86 | 243 |
|
87 |
| - QualType T = Base->getType(); |
88 |
| - if (T.isNull()) |
89 |
| - return false; |
| 244 | + const CXXRecordDecl *C = T->getAsCXXRecordDecl(); |
| 245 | + if (!C) |
| 246 | + continue; |
90 | 247 |
|
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 &) { |
| 248 | + bool AnyInconclusiveBase = false; |
| 249 | + const auto hasPublicRefInBase = |
| 250 | + [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { |
| 251 | + auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); |
| 252 | + if (!hasRefInBase) { |
| 253 | + AnyInconclusiveBase = true; |
| 254 | + return false; |
| 255 | + } |
| 256 | + return (*hasRefInBase) != nullptr; |
| 257 | + }; |
| 258 | + const auto hasPublicDerefInBase = |
| 259 | + [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { |
111 | 260 | auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
|
112 | 261 | if (!hasDerefInBase) {
|
113 | 262 | AnyInconclusiveBase = true;
|
114 | 263 | return false;
|
115 | 264 | }
|
116 | 265 | return (*hasDerefInBase) != nullptr;
|
117 | 266 | };
|
118 |
| - CXXBasePaths Paths; |
119 |
| - Paths.setOrigin(C); |
120 |
| - hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, |
| 267 | + CXXBasePaths Paths; |
| 268 | + Paths.setOrigin(C); |
| 269 | + hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, |
| 270 | + /*LookupInDependent =*/true); |
| 271 | + hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, |
121 | 272 | /*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); |
| 273 | + if (AnyInconclusiveBase || !hasRef || !hasDeref) |
| 274 | + continue; |
| 275 | + |
| 276 | + auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD); |
| 277 | + if (!HasSpecializedDelete || *HasSpecializedDelete) |
| 278 | + continue; |
| 279 | + if (C->lookupInBases( |
| 280 | + [&](const CXXBaseSpecifier *Base, CXXBasePath &) { |
| 281 | + auto *T = Base->getType().getTypePtrOrNull(); |
| 282 | + if (!T) |
| 283 | + return false; |
| 284 | + auto *R = T->getAsCXXRecordDecl(); |
| 285 | + if (!R) |
| 286 | + return false; |
| 287 | + auto Result = isClassWithSpecializedDelete(R, RD); |
| 288 | + if (!Result) |
| 289 | + AnyInconclusiveBase = true; |
| 290 | + return Result && *Result; |
| 291 | + }, |
| 292 | + Paths, /*LookupInDependent =*/true)) |
| 293 | + continue; |
| 294 | + if (AnyInconclusiveBase) |
| 295 | + continue; |
| 296 | + |
| 297 | + const auto *Dtor = C->getDestructor(); |
| 298 | + if (!Dtor || !Dtor->isVirtual()) { |
| 299 | + auto *ProblematicBaseSpecifier = &Base; |
| 300 | + auto *ProblematicBaseClass = C; |
| 301 | + reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass); |
| 302 | + } |
140 | 303 | }
|
141 | 304 | }
|
142 | 305 |
|
@@ -182,6 +345,32 @@ class RefCntblBaseVirtualDtorChecker
|
182 | 345 | ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
|
183 | 346 | }
|
184 | 347 |
|
| 348 | + static std::optional<bool> |
| 349 | + isClassWithSpecializedDelete(const CXXRecordDecl *C, |
| 350 | + const CXXRecordDecl *DerivedClass) { |
| 351 | + if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) { |
| 352 | + for (auto *MethodDecl : C->methods()) { |
| 353 | + if (safeGetName(MethodDecl) == "deref") { |
| 354 | + DerefAnalysisVisitor DerefAnalysis(ClsTmplSpDecl->getTemplateArgs(), |
| 355 | + DerivedClass); |
| 356 | + auto Result = DerefAnalysis.HasSpecializedDelete(MethodDecl); |
| 357 | + if (!Result || *Result) |
| 358 | + return Result; |
| 359 | + } |
| 360 | + } |
| 361 | + return false; |
| 362 | + } |
| 363 | + for (auto *MethodDecl : C->methods()) { |
| 364 | + if (safeGetName(MethodDecl) == "deref") { |
| 365 | + DerefAnalysisVisitor DerefAnalysis(DerivedClass); |
| 366 | + auto Result = DerefAnalysis.HasSpecializedDelete(MethodDecl); |
| 367 | + if (!Result || *Result) |
| 368 | + return Result; |
| 369 | + } |
| 370 | + } |
| 371 | + return false; |
| 372 | + } |
| 373 | + |
185 | 374 | void reportBug(const CXXRecordDecl *DerivedClass,
|
186 | 375 | const CXXBaseSpecifier *BaseSpec,
|
187 | 376 | const CXXRecordDecl *ProblematicBaseClass) const {
|
|
0 commit comments