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/AST/StmtVisitor.h" 15 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 16 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 17 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 18 #include "clang/StaticAnalyzer/Core/Checker.h" 19 #include "llvm/ADT/DenseSet.h" 20 #include "llvm/ADT/SetVector.h" 21 #include <optional> 22 23 using namespace clang; 24 using namespace ento; 25 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 (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 124 class RefCntblBaseVirtualDtorChecker 125 : public Checker<check::ASTDecl<TranslationUnitDecl>> { 126 private: 127 BugType Bug; 128 mutable BugReporter *BR; 129 130 public: 131 RefCntblBaseVirtualDtorChecker() 132 : Bug(this, 133 "Reference-countable base class doesn't have virtual destructor", 134 "WebKit coding guidelines") {} 135 136 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, 137 BugReporter &BRArg) const { 138 BR = &BRArg; 139 140 // The calls to checkAST* from AnalysisConsumer don't 141 // visit template instantiations or lambda classes. We 142 // want to visit those, so we make our own RecursiveASTVisitor. 143 struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { 144 const RefCntblBaseVirtualDtorChecker *Checker; 145 explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker) 146 : Checker(Checker) { 147 assert(Checker); 148 } 149 150 bool shouldVisitTemplateInstantiations() const { return true; } 151 bool shouldVisitImplicitCode() const { return false; } 152 153 bool VisitCXXRecordDecl(const CXXRecordDecl *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 189 return true; 190 } 191 192 llvm::SetVector<const CXXRecordDecl *> Decls; 193 llvm::DenseSet<const CXXRecordDecl *> CRTPs; 194 }; 195 196 LocalVisitor visitor(this); 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 } 203 } 204 205 void visitCXXRecordDecl(const CXXRecordDecl *RD) const { 206 if (shouldSkipDecl(RD)) 207 return; 208 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; 214 215 auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref"); 216 auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref"); 217 218 bool hasRef = hasRefInBase && *hasRefInBase != nullptr; 219 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; 220 221 QualType T = Base.getType(); 222 if (T.isNull()) 223 continue; 224 225 const CXXRecordDecl *C = T->getAsCXXRecordDecl(); 226 if (!C) 227 continue; 228 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 &) { 241 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); 242 if (!hasDerefInBase) { 243 AnyInconclusiveBase = true; 244 return false; 245 } 246 return (*hasDerefInBase) != nullptr; 247 }; 248 CXXBasePaths Paths; 249 Paths.setOrigin(C); 250 hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, 251 /*LookupInDependent =*/true); 252 hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, 253 /*LookupInDependent =*/true); 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 } 284 } 285 } 286 287 bool shouldSkipDecl(const CXXRecordDecl *RD) const { 288 if (!RD->isThisDeclarationADefinition()) 289 return true; 290 291 if (RD->isImplicit()) 292 return true; 293 294 if (RD->isLambda()) 295 return true; 296 297 // If the construct doesn't have a source file, then it's not something 298 // we want to diagnose. 299 const auto RDLocation = RD->getLocation(); 300 if (!RDLocation.isValid()) 301 return true; 302 303 const auto Kind = RD->getTagKind(); 304 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) 305 return true; 306 307 // Ignore CXXRecords that come from system headers. 308 if (BR->getSourceManager().getFileCharacteristic(RDLocation) != 309 SrcMgr::C_User) 310 return true; 311 312 return false; 313 } 314 315 static bool isRefCountedClass(const CXXRecordDecl *D) { 316 if (!D->getTemplateInstantiationPattern()) 317 return false; 318 auto *NsDecl = D->getParent(); 319 if (!NsDecl || !isa<NamespaceDecl>(NsDecl)) 320 return false; 321 auto NamespaceName = safeGetName(NsDecl); 322 auto ClsNameStr = safeGetName(D); 323 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. 324 return NamespaceName == "WTF" && 325 (ClsName.ends_with("RefCounted") || 326 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr"); 327 } 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 355 void reportBug(const CXXRecordDecl *DerivedClass, 356 const CXXBaseSpecifier *BaseSpec, 357 const CXXRecordDecl *ProblematicBaseClass) const { 358 assert(DerivedClass); 359 assert(BaseSpec); 360 assert(ProblematicBaseClass); 361 362 SmallString<100> Buf; 363 llvm::raw_svector_ostream Os(Buf); 364 365 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " "; 366 printQuotedQualifiedName(Os, ProblematicBaseClass); 367 368 Os << " is used as a base of " 369 << (DerivedClass->isClass() ? "class" : "struct") << " "; 370 printQuotedQualifiedName(Os, DerivedClass); 371 372 Os << " but doesn't have virtual destructor"; 373 374 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(), 375 BR->getSourceManager()); 376 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); 377 Report->addRange(BaseSpec->getSourceRange()); 378 BR->emitReport(std::move(Report)); 379 } 380 }; 381 } // namespace 382 383 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { 384 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); 385 } 386 387 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( 388 const CheckerManager &mgr) { 389 return true; 390 } 391