|
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 DerefFuncDeleteExprVisitor |
| 29 | + : public ConstStmtVisitor<DerefFuncDeleteExprVisitor, 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 | + DerefFuncDeleteExprVisitor(const TemplateArgumentList &ArgList, |
| 52 | + const CXXRecordDecl *ClassDecl) |
| 53 | + : ArgList(&ArgList), ClassDecl(ClassDecl) {} |
| 54 | + |
| 55 | + DerefFuncDeleteExprVisitor(const CXXRecordDecl *ClassDecl) |
| 56 | + : ClassDecl(ClassDecl) {} |
| 57 | + |
| 58 | + std::optional<bool> HasSpecializedDelete(CXXMethodDecl *Decl) { |
| 59 | + if (auto *Body = Decl->getBody()) |
| 60 | + return VisitBody(Body); |
| 61 | + if (auto *Tmpl = Decl->getTemplateInstantiationPattern()) |
| 62 | + return std::nullopt; // Indeterminate. There was no concrete instance. |
| 63 | + return false; |
| 64 | + } |
| 65 | + |
| 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 | + |
| 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 | + |
| 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. |
| 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 | + |
24 | 124 | class RefCntblBaseVirtualDtorChecker
|
25 | 125 | : public Checker<check::ASTDecl<TranslationUnitDecl>> {
|
26 | 126 | private:
|
@@ -51,92 +151,136 @@ class RefCntblBaseVirtualDtorChecker
|
51 | 151 | bool shouldVisitImplicitCode() const { return false; }
|
52 | 152 |
|
53 | 153 | bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
|
54 |
| - Checker->visitCXXRecordDecl(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 | + |
55 | 189 | return true;
|
56 | 190 | }
|
| 191 | + |
| 192 | + llvm::SetVector<const CXXRecordDecl *> Decls; |
| 193 | + llvm::DenseSet<const CXXRecordDecl *> CRTPs; |
57 | 194 | };
|
58 | 195 |
|
59 | 196 | LocalVisitor visitor(this);
|
60 | 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 | + } |
61 | 203 | }
|
62 | 204 |
|
63 | 205 | void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
|
64 | 206 | if (shouldSkipDecl(RD))
|
65 | 207 | return;
|
66 | 208 |
|
67 |
| - CXXBasePaths Paths; |
68 |
| - Paths.setOrigin(RD); |
| 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; |
69 | 214 |
|
70 |
| - const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr; |
71 |
| - const CXXRecordDecl *ProblematicBaseClass = nullptr; |
| 215 | + auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref"); |
| 216 | + auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref"); |
72 | 217 |
|
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; |
| 218 | + bool hasRef = hasRefInBase && *hasRefInBase != nullptr; |
| 219 | + bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; |
80 | 220 |
|
81 |
| - auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); |
82 |
| - auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); |
| 221 | + QualType T = Base.getType(); |
| 222 | + if (T.isNull()) |
| 223 | + continue; |
83 | 224 |
|
84 |
| - bool hasRef = hasRefInBase && *hasRefInBase != nullptr; |
85 |
| - bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; |
| 225 | + const CXXRecordDecl *C = T->getAsCXXRecordDecl(); |
| 226 | + if (!C) |
| 227 | + continue; |
86 | 228 |
|
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 &) { |
| 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 &) { |
111 | 241 | auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
|
112 | 242 | if (!hasDerefInBase) {
|
113 | 243 | AnyInconclusiveBase = true;
|
114 | 244 | return false;
|
115 | 245 | }
|
116 | 246 | return (*hasDerefInBase) != nullptr;
|
117 | 247 | };
|
118 |
| - CXXBasePaths Paths; |
119 |
| - Paths.setOrigin(C); |
120 |
| - hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, |
| 248 | + CXXBasePaths Paths; |
| 249 | + Paths.setOrigin(C); |
| 250 | + hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, |
| 251 | + /*LookupInDependent =*/true); |
| 252 | + hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, |
121 | 253 | /*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); |
| 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 | + } |
140 | 284 | }
|
141 | 285 | }
|
142 | 286 |
|
@@ -182,6 +326,32 @@ class RefCntblBaseVirtualDtorChecker
|
182 | 326 | ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
|
183 | 327 | }
|
184 | 328 |
|
| 329 | + static std::optional<bool> |
| 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 | + |
185 | 355 | void reportBug(const CXXRecordDecl *DerivedClass,
|
186 | 356 | const CXXBaseSpecifier *BaseSpec,
|
187 | 357 | const CXXRecordDecl *ProblematicBaseClass) const {
|
|
0 commit comments