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