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 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) { 206 for (auto &Arg : CTSD->getTemplateArgs().asArray()) { 207 if (Arg.getKind() != TemplateArgument::Type) 208 continue; 209 auto TemplT = Arg.getAsType(); 210 if (TemplT.isNull()) 211 continue; 212 213 bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD; 214 if (!IsCRTP) 215 continue; 216 CRTPs.insert(C); 217 } 218 } 219 } 220 221 return true; 222 } 223 224 llvm::SetVector<const CXXRecordDecl *> Decls; 225 llvm::DenseSet<const CXXRecordDecl *> CRTPs; 226 }; 227 228 LocalVisitor visitor(this); 229 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); 230 for (auto *RD : visitor.Decls) { 231 if (visitor.CRTPs.contains(RD)) 232 continue; 233 visitCXXRecordDecl(RD); 234 } 235 } 236 237 void visitCXXRecordDecl(const CXXRecordDecl *RD) const { 238 if (shouldSkipDecl(RD)) 239 return; 240 241 for (auto &Base : RD->bases()) { 242 const auto AccSpec = Base.getAccessSpecifier(); 243 if (AccSpec == AS_protected || AccSpec == AS_private || 244 (AccSpec == AS_none && RD->isClass())) 245 continue; 246 247 auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref"); 248 auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref"); 249 250 bool hasRef = hasRefInBase && *hasRefInBase != nullptr; 251 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; 252 253 QualType T = Base.getType(); 254 if (T.isNull()) 255 continue; 256 257 const CXXRecordDecl *C = T->getAsCXXRecordDecl(); 258 if (!C) 259 continue; 260 261 bool AnyInconclusiveBase = false; 262 const auto hasPublicRefInBase = 263 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { 264 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); 265 if (!hasRefInBase) { 266 AnyInconclusiveBase = true; 267 return false; 268 } 269 return (*hasRefInBase) != nullptr; 270 }; 271 const auto hasPublicDerefInBase = 272 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { 273 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); 274 if (!hasDerefInBase) { 275 AnyInconclusiveBase = true; 276 return false; 277 } 278 return (*hasDerefInBase) != nullptr; 279 }; 280 CXXBasePaths Paths; 281 Paths.setOrigin(C); 282 hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, 283 /*LookupInDependent =*/true); 284 hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, 285 /*LookupInDependent =*/true); 286 if (AnyInconclusiveBase || !hasRef || !hasDeref) 287 continue; 288 289 auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD); 290 if (!HasSpecializedDelete || *HasSpecializedDelete) 291 continue; 292 if (C->lookupInBases( 293 [&](const CXXBaseSpecifier *Base, CXXBasePath &) { 294 auto *T = Base->getType().getTypePtrOrNull(); 295 if (!T) 296 return false; 297 auto *R = T->getAsCXXRecordDecl(); 298 if (!R) 299 return false; 300 auto Result = isClassWithSpecializedDelete(R, RD); 301 if (!Result) 302 AnyInconclusiveBase = true; 303 return Result && *Result; 304 }, 305 Paths, /*LookupInDependent =*/true)) 306 continue; 307 if (AnyInconclusiveBase) 308 continue; 309 310 const auto *Dtor = C->getDestructor(); 311 if (!Dtor || !Dtor->isVirtual()) { 312 auto *ProblematicBaseSpecifier = &Base; 313 auto *ProblematicBaseClass = C; 314 reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass); 315 } 316 } 317 } 318 319 bool shouldSkipDecl(const CXXRecordDecl *RD) const { 320 if (!RD->isThisDeclarationADefinition()) 321 return true; 322 323 if (RD->isImplicit()) 324 return true; 325 326 if (RD->isLambda()) 327 return true; 328 329 // If the construct doesn't have a source file, then it's not something 330 // we want to diagnose. 331 const auto RDLocation = RD->getLocation(); 332 if (!RDLocation.isValid()) 333 return true; 334 335 const auto Kind = RD->getTagKind(); 336 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) 337 return true; 338 339 // Ignore CXXRecords that come from system headers. 340 if (BR->getSourceManager().getFileCharacteristic(RDLocation) != 341 SrcMgr::C_User) 342 return true; 343 344 return false; 345 } 346 347 static bool isRefCountedClass(const CXXRecordDecl *D) { 348 if (!D->getTemplateInstantiationPattern()) 349 return false; 350 auto *NsDecl = D->getParent(); 351 if (!NsDecl || !isa<NamespaceDecl>(NsDecl)) 352 return false; 353 auto NamespaceName = safeGetName(NsDecl); 354 auto ClsNameStr = safeGetName(D); 355 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. 356 return NamespaceName == "WTF" && 357 (ClsName.ends_with("RefCounted") || 358 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr"); 359 } 360 361 static std::optional<bool> 362 isClassWithSpecializedDelete(const CXXRecordDecl *C, 363 const CXXRecordDecl *DerivedClass) { 364 if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) { 365 for (auto *MethodDecl : C->methods()) { 366 if (safeGetName(MethodDecl) == "deref") { 367 DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(), 368 DerivedClass); 369 auto Result = Visitor.HasSpecializedDelete(MethodDecl); 370 if (!Result || *Result) 371 return Result; 372 } 373 } 374 return false; 375 } 376 for (auto *MethodDecl : C->methods()) { 377 if (safeGetName(MethodDecl) == "deref") { 378 DerefFuncDeleteExprVisitor Visitor(DerivedClass); 379 auto Result = Visitor.HasSpecializedDelete(MethodDecl); 380 if (!Result || *Result) 381 return Result; 382 } 383 } 384 return false; 385 } 386 387 void reportBug(const CXXRecordDecl *DerivedClass, 388 const CXXBaseSpecifier *BaseSpec, 389 const CXXRecordDecl *ProblematicBaseClass) const { 390 assert(DerivedClass); 391 assert(BaseSpec); 392 assert(ProblematicBaseClass); 393 394 SmallString<100> Buf; 395 llvm::raw_svector_ostream Os(Buf); 396 397 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " "; 398 printQuotedQualifiedName(Os, ProblematicBaseClass); 399 400 Os << " is used as a base of " 401 << (DerivedClass->isClass() ? "class" : "struct") << " "; 402 printQuotedQualifiedName(Os, DerivedClass); 403 404 Os << " but doesn't have virtual destructor"; 405 406 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(), 407 BR->getSourceManager()); 408 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); 409 Report->addRange(BaseSpec->getSourceRange()); 410 BR->emitReport(std::move(Report)); 411 } 412 }; 413 } // namespace 414 415 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { 416 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); 417 } 418 419 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( 420 const CheckerManager &mgr) { 421 return true; 422 } 423