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