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