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/DynamicRecursiveASTVisitor.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 else { 71 auto name = safeGetName(D); 72 if (name == "ensureOnMainThread" || name == "ensureOnMainRunLoop") { 73 for (unsigned i = 0; i < CE->getNumArgs(); ++i) { 74 auto *Arg = CE->getArg(i); 75 if (VisitLambdaArgument(Arg)) 76 return true; 77 } 78 } 79 } 80 return false; 81 } 82 83 bool VisitLambdaArgument(const Expr *E) { 84 E = E->IgnoreParenCasts(); 85 if (auto *TempE = dyn_cast<CXXBindTemporaryExpr>(E)) 86 E = TempE->getSubExpr(); 87 E = E->IgnoreParenCasts(); 88 if (auto *Ref = dyn_cast<DeclRefExpr>(E)) { 89 if (auto *VD = dyn_cast_or_null<VarDecl>(Ref->getDecl())) 90 return VisitLambdaArgument(VD->getInit()); 91 return false; 92 } 93 if (auto *Lambda = dyn_cast<LambdaExpr>(E)) { 94 if (VisitBody(Lambda->getBody())) 95 return true; 96 } 97 if (auto *ConstructE = dyn_cast<CXXConstructExpr>(E)) { 98 for (unsigned i = 0; i < ConstructE->getNumArgs(); ++i) { 99 if (VisitLambdaArgument(ConstructE->getArg(i))) 100 return true; 101 } 102 } 103 return false; 104 } 105 106 bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) { 107 auto *Arg = E->getArgument(); 108 while (Arg) { 109 if (auto *Paren = dyn_cast<ParenExpr>(Arg)) 110 Arg = Paren->getSubExpr(); 111 else if (auto *Cast = dyn_cast<CastExpr>(Arg)) { 112 Arg = Cast->getSubExpr(); 113 auto CastType = Cast->getType(); 114 if (auto *PtrType = dyn_cast<PointerType>(CastType)) { 115 auto PointeeType = PtrType->getPointeeType(); 116 while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) { 117 if (ET->isSugared()) 118 PointeeType = ET->desugar(); 119 } 120 if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) { 121 if (ArgList) { 122 auto ParmIndex = ParmType->getIndex(); 123 auto Type = ArgList->get(ParmIndex).getAsType(); 124 if (Type->getAsCXXRecordDecl() == ClassDecl) 125 return true; 126 } 127 } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) { 128 if (RD->getDecl() == ClassDecl) 129 return true; 130 } else if (auto *ST = 131 dyn_cast<SubstTemplateTypeParmType>(PointeeType)) { 132 auto Type = ST->getReplacementType(); 133 if (auto *RD = dyn_cast<RecordType>(Type)) { 134 if (RD->getDecl() == ClassDecl) 135 return true; 136 } 137 } 138 } 139 } else 140 break; 141 } 142 return false; 143 } 144 145 bool VisitStmt(const Stmt *S) { return VisitChildren(S); } 146 147 // Return false since the contents of lambda isn't necessarily executed. 148 // If it is executed, VisitCallExpr above will visit its body. 149 bool VisitLambdaExpr(const LambdaExpr *) { return false; } 150 151 private: 152 const TemplateArgumentList *ArgList{nullptr}; 153 const CXXRecordDecl *ClassDecl; 154 llvm::DenseSet<const Stmt *> VisitedBody; 155 }; 156 157 class RefCntblBaseVirtualDtorChecker 158 : public Checker<check::ASTDecl<TranslationUnitDecl>> { 159 private: 160 BugType Bug; 161 mutable BugReporter *BR; 162 163 public: 164 RefCntblBaseVirtualDtorChecker() 165 : Bug(this, 166 "Reference-countable base class doesn't have virtual destructor", 167 "WebKit coding guidelines") {} 168 169 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, 170 BugReporter &BRArg) const { 171 BR = &BRArg; 172 173 // The calls to checkAST* from AnalysisConsumer don't 174 // visit template instantiations or lambda classes. We 175 // want to visit those, so we make our own RecursiveASTVisitor. 176 struct LocalVisitor : DynamicRecursiveASTVisitor { 177 const RefCntblBaseVirtualDtorChecker *Checker; 178 explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker) 179 : Checker(Checker) { 180 assert(Checker); 181 ShouldVisitTemplateInstantiations = true; 182 ShouldVisitImplicitCode = false; 183 } 184 185 bool VisitCXXRecordDecl(CXXRecordDecl *RD) override { 186 if (!RD->hasDefinition()) 187 return true; 188 189 Decls.insert(RD); 190 191 for (auto &Base : RD->bases()) { 192 const auto AccSpec = Base.getAccessSpecifier(); 193 if (AccSpec == AS_protected || AccSpec == AS_private || 194 (AccSpec == AS_none && RD->isClass())) 195 continue; 196 197 QualType T = Base.getType(); 198 if (T.isNull()) 199 continue; 200 201 const CXXRecordDecl *C = T->getAsCXXRecordDecl(); 202 if (!C) 203 continue; 204 205 bool isExempt = T.getAsString() == "NoVirtualDestructorBase" && 206 safeGetName(C->getParent()) == "WTF"; 207 if (isExempt || ExemptDecls.contains(C)) { 208 ExemptDecls.insert(RD); 209 continue; 210 } 211 212 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) { 213 for (auto &Arg : CTSD->getTemplateArgs().asArray()) { 214 if (Arg.getKind() != TemplateArgument::Type) 215 continue; 216 auto TemplT = Arg.getAsType(); 217 if (TemplT.isNull()) 218 continue; 219 220 bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD; 221 if (!IsCRTP) 222 continue; 223 CRTPs.insert(C); 224 } 225 } 226 } 227 228 return true; 229 } 230 231 llvm::SetVector<const CXXRecordDecl *> Decls; 232 llvm::DenseSet<const CXXRecordDecl *> CRTPs; 233 llvm::DenseSet<const CXXRecordDecl *> ExemptDecls; 234 }; 235 236 LocalVisitor visitor(this); 237 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); 238 for (auto *RD : visitor.Decls) { 239 if (visitor.CRTPs.contains(RD) || visitor.ExemptDecls.contains(RD)) 240 continue; 241 visitCXXRecordDecl(RD); 242 } 243 } 244 245 void visitCXXRecordDecl(const CXXRecordDecl *RD) const { 246 if (shouldSkipDecl(RD)) 247 return; 248 249 for (auto &Base : RD->bases()) { 250 const auto AccSpec = Base.getAccessSpecifier(); 251 if (AccSpec == AS_protected || AccSpec == AS_private || 252 (AccSpec == AS_none && RD->isClass())) 253 continue; 254 255 auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref"); 256 auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref"); 257 258 bool hasRef = hasRefInBase && *hasRefInBase != nullptr; 259 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; 260 261 QualType T = Base.getType(); 262 if (T.isNull()) 263 continue; 264 265 const CXXRecordDecl *C = T->getAsCXXRecordDecl(); 266 if (!C) 267 continue; 268 269 bool AnyInconclusiveBase = false; 270 const auto hasPublicRefInBase = 271 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { 272 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); 273 if (!hasRefInBase) { 274 AnyInconclusiveBase = true; 275 return false; 276 } 277 return (*hasRefInBase) != nullptr; 278 }; 279 const auto hasPublicDerefInBase = 280 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { 281 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); 282 if (!hasDerefInBase) { 283 AnyInconclusiveBase = true; 284 return false; 285 } 286 return (*hasDerefInBase) != nullptr; 287 }; 288 CXXBasePaths Paths; 289 Paths.setOrigin(C); 290 hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, 291 /*LookupInDependent =*/true); 292 hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, 293 /*LookupInDependent =*/true); 294 if (AnyInconclusiveBase || !hasRef || !hasDeref) 295 continue; 296 297 auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD); 298 if (!HasSpecializedDelete || *HasSpecializedDelete) 299 continue; 300 if (C->lookupInBases( 301 [&](const CXXBaseSpecifier *Base, CXXBasePath &) { 302 auto *T = Base->getType().getTypePtrOrNull(); 303 if (!T) 304 return false; 305 auto *R = T->getAsCXXRecordDecl(); 306 if (!R) 307 return false; 308 auto Result = isClassWithSpecializedDelete(R, RD); 309 if (!Result) 310 AnyInconclusiveBase = true; 311 return Result && *Result; 312 }, 313 Paths, /*LookupInDependent =*/true)) 314 continue; 315 if (AnyInconclusiveBase) 316 continue; 317 318 const auto *Dtor = C->getDestructor(); 319 if (!Dtor || !Dtor->isVirtual()) { 320 auto *ProblematicBaseSpecifier = &Base; 321 auto *ProblematicBaseClass = C; 322 reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass); 323 } 324 } 325 } 326 327 bool shouldSkipDecl(const CXXRecordDecl *RD) const { 328 if (!RD->isThisDeclarationADefinition()) 329 return true; 330 331 if (RD->isImplicit()) 332 return true; 333 334 if (RD->isLambda()) 335 return true; 336 337 // If the construct doesn't have a source file, then it's not something 338 // we want to diagnose. 339 const auto RDLocation = RD->getLocation(); 340 if (!RDLocation.isValid()) 341 return true; 342 343 const auto Kind = RD->getTagKind(); 344 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) 345 return true; 346 347 // Ignore CXXRecords that come from system headers. 348 if (BR->getSourceManager().getFileCharacteristic(RDLocation) != 349 SrcMgr::C_User) 350 return true; 351 352 return false; 353 } 354 355 static bool isRefCountedClass(const CXXRecordDecl *D) { 356 if (!D->getTemplateInstantiationPattern()) 357 return false; 358 auto *NsDecl = D->getParent(); 359 if (!NsDecl || !isa<NamespaceDecl>(NsDecl)) 360 return false; 361 auto NamespaceName = safeGetName(NsDecl); 362 auto ClsNameStr = safeGetName(D); 363 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. 364 return NamespaceName == "WTF" && 365 (ClsName.ends_with("RefCounted") || 366 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr"); 367 } 368 369 static std::optional<bool> 370 isClassWithSpecializedDelete(const CXXRecordDecl *C, 371 const CXXRecordDecl *DerivedClass) { 372 if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) { 373 for (auto *MethodDecl : C->methods()) { 374 if (safeGetName(MethodDecl) == "deref") { 375 DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(), 376 DerivedClass); 377 auto Result = Visitor.HasSpecializedDelete(MethodDecl); 378 if (!Result || *Result) 379 return Result; 380 } 381 } 382 return false; 383 } 384 for (auto *MethodDecl : C->methods()) { 385 if (safeGetName(MethodDecl) == "deref") { 386 DerefFuncDeleteExprVisitor Visitor(DerivedClass); 387 auto Result = Visitor.HasSpecializedDelete(MethodDecl); 388 if (!Result || *Result) 389 return Result; 390 } 391 } 392 return false; 393 } 394 395 void reportBug(const CXXRecordDecl *DerivedClass, 396 const CXXBaseSpecifier *BaseSpec, 397 const CXXRecordDecl *ProblematicBaseClass) const { 398 assert(DerivedClass); 399 assert(BaseSpec); 400 assert(ProblematicBaseClass); 401 402 SmallString<100> Buf; 403 llvm::raw_svector_ostream Os(Buf); 404 405 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " "; 406 printQuotedQualifiedName(Os, ProblematicBaseClass); 407 408 Os << " is used as a base of " 409 << (DerivedClass->isClass() ? "class" : "struct") << " "; 410 printQuotedQualifiedName(Os, DerivedClass); 411 412 Os << " but doesn't have virtual destructor"; 413 414 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(), 415 BR->getSourceManager()); 416 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); 417 Report->addRange(BaseSpec->getSourceRange()); 418 BR->emitReport(std::move(Report)); 419 } 420 }; 421 } // namespace 422 423 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { 424 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); 425 } 426 427 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( 428 const CheckerManager &mgr) { 429 return true; 430 } 431