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/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 15 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 16 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 17 #include "clang/StaticAnalyzer/Core/Checker.h" 18 #include <optional> 19 20 using namespace clang; 21 using namespace ento; 22 23 namespace { 24 class RefCntblBaseVirtualDtorChecker 25 : public Checker<check::ASTDecl<TranslationUnitDecl>> { 26 private: 27 BugType Bug; 28 mutable BugReporter *BR; 29 30 public: 31 RefCntblBaseVirtualDtorChecker() 32 : Bug(this, 33 "Reference-countable base class doesn't have virtual destructor", 34 "WebKit coding guidelines") {} 35 36 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, 37 BugReporter &BRArg) const { 38 BR = &BRArg; 39 40 // The calls to checkAST* from AnalysisConsumer don't 41 // visit template instantiations or lambda classes. We 42 // want to visit those, so we make our own RecursiveASTVisitor. 43 struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { 44 const RefCntblBaseVirtualDtorChecker *Checker; 45 explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker) 46 : Checker(Checker) { 47 assert(Checker); 48 } 49 50 bool shouldVisitTemplateInstantiations() const { return true; } 51 bool shouldVisitImplicitCode() const { return false; } 52 53 bool VisitCXXRecordDecl(const CXXRecordDecl *RD) { 54 Checker->visitCXXRecordDecl(RD); 55 return true; 56 } 57 }; 58 59 LocalVisitor visitor(this); 60 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); 61 } 62 63 void visitCXXRecordDecl(const CXXRecordDecl *RD) const { 64 if (shouldSkipDecl(RD)) 65 return; 66 67 CXXBasePaths Paths; 68 Paths.setOrigin(RD); 69 70 const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr; 71 const CXXRecordDecl *ProblematicBaseClass = nullptr; 72 73 const auto IsPublicBaseRefCntblWOVirtualDtor = 74 [RD, &ProblematicBaseSpecifier, 75 &ProblematicBaseClass](const CXXBaseSpecifier *Base, CXXBasePath &) { 76 const auto AccSpec = Base->getAccessSpecifier(); 77 if (AccSpec == AS_protected || AccSpec == AS_private || 78 (AccSpec == AS_none && RD->isClass())) 79 return false; 80 81 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); 82 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); 83 84 bool hasRef = hasRefInBase && *hasRefInBase != nullptr; 85 bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; 86 87 QualType T = Base->getType(); 88 if (T.isNull()) 89 return false; 90 91 const CXXRecordDecl *C = T->getAsCXXRecordDecl(); 92 if (!C) 93 return false; 94 if (isRefCountedClass(C)) 95 return false; 96 97 bool AnyInconclusiveBase = false; 98 const auto hasPublicRefInBase = 99 [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, 100 CXXBasePath &) { 101 auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); 102 if (!hasRefInBase) { 103 AnyInconclusiveBase = true; 104 return false; 105 } 106 return (*hasRefInBase) != nullptr; 107 }; 108 const auto hasPublicDerefInBase = [&AnyInconclusiveBase]( 109 const CXXBaseSpecifier *Base, 110 CXXBasePath &) { 111 auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); 112 if (!hasDerefInBase) { 113 AnyInconclusiveBase = true; 114 return false; 115 } 116 return (*hasDerefInBase) != nullptr; 117 }; 118 CXXBasePaths Paths; 119 Paths.setOrigin(C); 120 hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, 121 /*LookupInDependent =*/true); 122 hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, 123 /*LookupInDependent =*/true); 124 if (AnyInconclusiveBase || !hasRef || !hasDeref) 125 return false; 126 127 const auto *Dtor = C->getDestructor(); 128 if (!Dtor || !Dtor->isVirtual()) { 129 ProblematicBaseSpecifier = Base; 130 ProblematicBaseClass = C; 131 return true; 132 } 133 134 return false; 135 }; 136 137 if (RD->lookupInBases(IsPublicBaseRefCntblWOVirtualDtor, Paths, 138 /*LookupInDependent =*/true)) { 139 reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass); 140 } 141 } 142 143 bool shouldSkipDecl(const CXXRecordDecl *RD) const { 144 if (!RD->isThisDeclarationADefinition()) 145 return true; 146 147 if (RD->isImplicit()) 148 return true; 149 150 if (RD->isLambda()) 151 return true; 152 153 // If the construct doesn't have a source file, then it's not something 154 // we want to diagnose. 155 const auto RDLocation = RD->getLocation(); 156 if (!RDLocation.isValid()) 157 return true; 158 159 const auto Kind = RD->getTagKind(); 160 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) 161 return true; 162 163 // Ignore CXXRecords that come from system headers. 164 if (BR->getSourceManager().getFileCharacteristic(RDLocation) != 165 SrcMgr::C_User) 166 return true; 167 168 return false; 169 } 170 171 static bool isRefCountedClass(const CXXRecordDecl *D) { 172 if (!D->getTemplateInstantiationPattern()) 173 return false; 174 auto *NsDecl = D->getParent(); 175 if (!NsDecl || !isa<NamespaceDecl>(NsDecl)) 176 return false; 177 auto NamespaceName = safeGetName(NsDecl); 178 auto ClsNameStr = safeGetName(D); 179 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. 180 return NamespaceName == "WTF" && 181 (ClsName.ends_with("RefCounted") || 182 ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr"); 183 } 184 185 void reportBug(const CXXRecordDecl *DerivedClass, 186 const CXXBaseSpecifier *BaseSpec, 187 const CXXRecordDecl *ProblematicBaseClass) const { 188 assert(DerivedClass); 189 assert(BaseSpec); 190 assert(ProblematicBaseClass); 191 192 SmallString<100> Buf; 193 llvm::raw_svector_ostream Os(Buf); 194 195 Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " "; 196 printQuotedQualifiedName(Os, ProblematicBaseClass); 197 198 Os << " is used as a base of " 199 << (DerivedClass->isClass() ? "class" : "struct") << " "; 200 printQuotedQualifiedName(Os, DerivedClass); 201 202 Os << " but doesn't have virtual destructor"; 203 204 PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(), 205 BR->getSourceManager()); 206 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); 207 Report->addRange(BaseSpec->getSourceRange()); 208 BR->emitReport(std::move(Report)); 209 } 210 }; 211 } // namespace 212 213 void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { 214 Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); 215 } 216 217 bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( 218 const CheckerManager &mgr) { 219 return true; 220 } 221