aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp390
1 files changed, 390 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp
new file mode 100644
index 000000000000..9df108e28ecd
--- /dev/null
+++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp
@@ -0,0 +1,390 @@
+//=======- RefCntblBaseVirtualDtor.cpp ---------------------------*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ASTUtils.h"
+#include "DiagOutputUtils.h"
+#include "PtrTypesSemantics.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SetVector.h"
+#include <optional>
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+
+class DerefFuncDeleteExprVisitor
+ : public ConstStmtVisitor<DerefFuncDeleteExprVisitor, bool> {
+ // Returns true if any of child statements return true.
+ bool VisitChildren(const Stmt *S) {
+ for (const Stmt *Child : S->children()) {
+ if (Child && Visit(Child))
+ return true;
+ }
+ return false;
+ }
+
+ bool VisitBody(const Stmt *Body) {
+ if (!Body)
+ return false;
+
+ auto [It, IsNew] = VisitedBody.insert(Body);
+ if (!IsNew) // This body is recursive
+ return false;
+
+ return Visit(Body);
+ }
+
+public:
+ DerefFuncDeleteExprVisitor(const TemplateArgumentList &ArgList,
+ const CXXRecordDecl *ClassDecl)
+ : ArgList(&ArgList), ClassDecl(ClassDecl) {}
+
+ DerefFuncDeleteExprVisitor(const CXXRecordDecl *ClassDecl)
+ : ClassDecl(ClassDecl) {}
+
+ std::optional<bool> HasSpecializedDelete(CXXMethodDecl *Decl) {
+ if (auto *Body = Decl->getBody())
+ return VisitBody(Body);
+ if (Decl->getTemplateInstantiationPattern())
+ return std::nullopt; // Indeterminate. There was no concrete instance.
+ return false;
+ }
+
+ bool VisitCallExpr(const CallExpr *CE) {
+ const Decl *D = CE->getCalleeDecl();
+ if (D && D->hasBody())
+ return VisitBody(D->getBody());
+ return false;
+ }
+
+ bool VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
+ auto *Arg = E->getArgument();
+ while (Arg) {
+ if (auto *Paren = dyn_cast<ParenExpr>(Arg))
+ Arg = Paren->getSubExpr();
+ else if (auto *Cast = dyn_cast<CastExpr>(Arg)) {
+ Arg = Cast->getSubExpr();
+ auto CastType = Cast->getType();
+ if (auto *PtrType = dyn_cast<PointerType>(CastType)) {
+ auto PointeeType = PtrType->getPointeeType();
+ while (auto *ET = dyn_cast<ElaboratedType>(PointeeType)) {
+ if (ET->isSugared())
+ PointeeType = ET->desugar();
+ }
+ if (auto *ParmType = dyn_cast<TemplateTypeParmType>(PointeeType)) {
+ if (ArgList) {
+ auto ParmIndex = ParmType->getIndex();
+ auto Type = ArgList->get(ParmIndex).getAsType();
+ if (Type->getAsCXXRecordDecl() == ClassDecl)
+ return true;
+ }
+ } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) {
+ if (RD->getDecl() == ClassDecl)
+ return true;
+ } else if (auto *ST =
+ dyn_cast<SubstTemplateTypeParmType>(PointeeType)) {
+ auto Type = ST->getReplacementType();
+ if (auto *RD = dyn_cast<RecordType>(Type)) {
+ if (RD->getDecl() == ClassDecl)
+ return true;
+ }
+ }
+ }
+ } else
+ break;
+ }
+ return false;
+ }
+
+ bool VisitStmt(const Stmt *S) { return VisitChildren(S); }
+
+ // Return false since the contents of lambda isn't necessarily executed.
+ // If it is executed, VisitCallExpr above will visit its body.
+ bool VisitLambdaExpr(const LambdaExpr *) { return false; }
+
+private:
+ const TemplateArgumentList *ArgList{nullptr};
+ const CXXRecordDecl *ClassDecl;
+ llvm::DenseSet<const Stmt *> VisitedBody;
+};
+
+class RefCntblBaseVirtualDtorChecker
+ : public Checker<check::ASTDecl<TranslationUnitDecl>> {
+private:
+ BugType Bug;
+ mutable BugReporter *BR;
+
+public:
+ RefCntblBaseVirtualDtorChecker()
+ : Bug(this,
+ "Reference-countable base class doesn't have virtual destructor",
+ "WebKit coding guidelines") {}
+
+ void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
+ BugReporter &BRArg) const {
+ BR = &BRArg;
+
+ // The calls to checkAST* from AnalysisConsumer don't
+ // visit template instantiations or lambda classes. We
+ // want to visit those, so we make our own RecursiveASTVisitor.
+ struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
+ const RefCntblBaseVirtualDtorChecker *Checker;
+ explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker)
+ : Checker(Checker) {
+ assert(Checker);
+ }
+
+ bool shouldVisitTemplateInstantiations() const { return true; }
+ bool shouldVisitImplicitCode() const { return false; }
+
+ bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
+ if (!RD->hasDefinition())
+ return true;
+
+ Decls.insert(RD);
+
+ for (auto &Base : RD->bases()) {
+ const auto AccSpec = Base.getAccessSpecifier();
+ if (AccSpec == AS_protected || AccSpec == AS_private ||
+ (AccSpec == AS_none && RD->isClass()))
+ continue;
+
+ QualType T = Base.getType();
+ if (T.isNull())
+ continue;
+
+ const CXXRecordDecl *C = T->getAsCXXRecordDecl();
+ if (!C)
+ continue;
+
+ if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
+ for (auto &Arg : CTSD->getTemplateArgs().asArray()) {
+ if (Arg.getKind() != TemplateArgument::Type)
+ continue;
+ auto TemplT = Arg.getAsType();
+ if (TemplT.isNull())
+ continue;
+
+ bool IsCRTP = TemplT->getAsCXXRecordDecl() == RD;
+ if (!IsCRTP)
+ continue;
+ CRTPs.insert(C);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ llvm::SetVector<const CXXRecordDecl *> Decls;
+ llvm::DenseSet<const CXXRecordDecl *> CRTPs;
+ };
+
+ LocalVisitor visitor(this);
+ visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
+ for (auto *RD : visitor.Decls) {
+ if (visitor.CRTPs.contains(RD))
+ continue;
+ visitCXXRecordDecl(RD);
+ }
+ }
+
+ void visitCXXRecordDecl(const CXXRecordDecl *RD) const {
+ if (shouldSkipDecl(RD))
+ return;
+
+ for (auto &Base : RD->bases()) {
+ const auto AccSpec = Base.getAccessSpecifier();
+ if (AccSpec == AS_protected || AccSpec == AS_private ||
+ (AccSpec == AS_none && RD->isClass()))
+ continue;
+
+ auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
+ auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");
+
+ bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
+ bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
+
+ QualType T = Base.getType();
+ if (T.isNull())
+ continue;
+
+ const CXXRecordDecl *C = T->getAsCXXRecordDecl();
+ if (!C)
+ continue;
+
+ bool AnyInconclusiveBase = false;
+ const auto hasPublicRefInBase =
+ [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
+ auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
+ if (!hasRefInBase) {
+ AnyInconclusiveBase = true;
+ return false;
+ }
+ return (*hasRefInBase) != nullptr;
+ };
+ const auto hasPublicDerefInBase =
+ [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
+ auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
+ if (!hasDerefInBase) {
+ AnyInconclusiveBase = true;
+ return false;
+ }
+ return (*hasDerefInBase) != nullptr;
+ };
+ CXXBasePaths Paths;
+ Paths.setOrigin(C);
+ hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
+ /*LookupInDependent =*/true);
+ hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
+ /*LookupInDependent =*/true);
+ if (AnyInconclusiveBase || !hasRef || !hasDeref)
+ continue;
+
+ auto HasSpecializedDelete = isClassWithSpecializedDelete(C, RD);
+ if (!HasSpecializedDelete || *HasSpecializedDelete)
+ continue;
+ if (C->lookupInBases(
+ [&](const CXXBaseSpecifier *Base, CXXBasePath &) {
+ auto *T = Base->getType().getTypePtrOrNull();
+ if (!T)
+ return false;
+ auto *R = T->getAsCXXRecordDecl();
+ if (!R)
+ return false;
+ auto Result = isClassWithSpecializedDelete(R, RD);
+ if (!Result)
+ AnyInconclusiveBase = true;
+ return Result && *Result;
+ },
+ Paths, /*LookupInDependent =*/true))
+ continue;
+ if (AnyInconclusiveBase)
+ continue;
+
+ const auto *Dtor = C->getDestructor();
+ if (!Dtor || !Dtor->isVirtual()) {
+ auto *ProblematicBaseSpecifier = &Base;
+ auto *ProblematicBaseClass = C;
+ reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
+ }
+ }
+ }
+
+ bool shouldSkipDecl(const CXXRecordDecl *RD) const {
+ if (!RD->isThisDeclarationADefinition())
+ return true;
+
+ if (RD->isImplicit())
+ return true;
+
+ if (RD->isLambda())
+ return true;
+
+ // If the construct doesn't have a source file, then it's not something
+ // we want to diagnose.
+ const auto RDLocation = RD->getLocation();
+ if (!RDLocation.isValid())
+ return true;
+
+ const auto Kind = RD->getTagKind();
+ if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
+ return true;
+
+ // Ignore CXXRecords that come from system headers.
+ if (BR->getSourceManager().getFileCharacteristic(RDLocation) !=
+ SrcMgr::C_User)
+ return true;
+
+ return false;
+ }
+
+ static bool isRefCountedClass(const CXXRecordDecl *D) {
+ if (!D->getTemplateInstantiationPattern())
+ return false;
+ auto *NsDecl = D->getParent();
+ if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
+ return false;
+ auto NamespaceName = safeGetName(NsDecl);
+ auto ClsNameStr = safeGetName(D);
+ StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
+ return NamespaceName == "WTF" &&
+ (ClsName.ends_with("RefCounted") ||
+ ClsName == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr");
+ }
+
+ static std::optional<bool>
+ isClassWithSpecializedDelete(const CXXRecordDecl *C,
+ const CXXRecordDecl *DerivedClass) {
+ if (auto *ClsTmplSpDecl = dyn_cast<ClassTemplateSpecializationDecl>(C)) {
+ for (auto *MethodDecl : C->methods()) {
+ if (safeGetName(MethodDecl) == "deref") {
+ DerefFuncDeleteExprVisitor Visitor(ClsTmplSpDecl->getTemplateArgs(),
+ DerivedClass);
+ auto Result = Visitor.HasSpecializedDelete(MethodDecl);
+ if (!Result || *Result)
+ return Result;
+ }
+ }
+ return false;
+ }
+ for (auto *MethodDecl : C->methods()) {
+ if (safeGetName(MethodDecl) == "deref") {
+ DerefFuncDeleteExprVisitor Visitor(DerivedClass);
+ auto Result = Visitor.HasSpecializedDelete(MethodDecl);
+ if (!Result || *Result)
+ return Result;
+ }
+ }
+ return false;
+ }
+
+ void reportBug(const CXXRecordDecl *DerivedClass,
+ const CXXBaseSpecifier *BaseSpec,
+ const CXXRecordDecl *ProblematicBaseClass) const {
+ assert(DerivedClass);
+ assert(BaseSpec);
+ assert(ProblematicBaseClass);
+
+ SmallString<100> Buf;
+ llvm::raw_svector_ostream Os(Buf);
+
+ Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " ";
+ printQuotedQualifiedName(Os, ProblematicBaseClass);
+
+ Os << " is used as a base of "
+ << (DerivedClass->isClass() ? "class" : "struct") << " ";
+ printQuotedQualifiedName(Os, DerivedClass);
+
+ Os << " but doesn't have virtual destructor";
+
+ PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(),
+ BR->getSourceManager());
+ auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
+ Report->addRange(BaseSpec->getSourceRange());
+ BR->emitReport(std::move(Report));
+ }
+};
+} // namespace
+
+void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) {
+ Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>();
+}
+
+bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker(
+ const CheckerManager &mgr) {
+ return true;
+}