Skip to content

[webkit.RefCntblBaseVirtualDtor] Ignore a base class which has a specialized deref function for a given derived class. #92501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,134 @@
#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 DerefAnalysisVisitor
: public ConstStmtVisitor<DerefAnalysisVisitor, 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:
DerefAnalysisVisitor(const TemplateArgumentList &ArgList,
const CXXRecordDecl *ClassDecl)
: ArgList(&ArgList), ClassDecl(ClassDecl) {}

DerefAnalysisVisitor(const CXXRecordDecl *ClassDecl) : ClassDecl(ClassDecl) {}

std::optional<bool> HasSpecializedDelete(CXXMethodDecl *Decl) {
if (auto *Body = Decl->getBody())
return VisitBody(Body);
if (auto *Tmpl = Decl->getTemplateInstantiationPattern())
return std::nullopt; // Indeterminate. There was no concrete instance.
return false;
}

bool VisitCallExpr(const CallExpr *CE) {
auto *Callee = CE->getCallee();
while (auto *Expr = dyn_cast<CastExpr>(Callee))
Callee = Expr->getSubExpr();
if (auto *DeclRef = dyn_cast<DeclRefExpr>(Callee)) {
auto *Decl = DeclRef->getDecl();
if (auto *VD = dyn_cast<VarDecl>(Decl)) {
if (auto *Init = VD->getInit()) {
if (auto *Lambda = dyn_cast<LambdaExpr>(Init))
return VisitBody(Lambda->getBody());
}
} else if (auto *FD = dyn_cast<FunctionDecl>(Decl))
return VisitBody(FD->getBody());
}
return false;
}

bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
auto *Callee = MCE->getMethodDecl();
if (!Callee)
return false;
return VisitBody(Callee->getBody());
}

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 (auto *RD = dyn_cast<RecordType>(Type)) {
if (RD->getDecl() == 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:
Expand Down Expand Up @@ -51,92 +169,133 @@ class RefCntblBaseVirtualDtorChecker
bool shouldVisitImplicitCode() const { return false; }

bool VisitCXXRecordDecl(const CXXRecordDecl *RD) {
Checker->visitCXXRecordDecl(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)) {
auto &Args = CTSD->getTemplateArgs();
for (unsigned i = 0; i < Args.size(); ++i) {
if (Args[i].getKind() != TemplateArgument::Type)
continue;
auto TemplT = Args[i].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;

CXXBasePaths Paths;
Paths.setOrigin(RD);
for (auto &Base : RD->bases()) {
const auto AccSpec = Base.getAccessSpecifier();
if (AccSpec == AS_protected || AccSpec == AS_private ||
(AccSpec == AS_none && RD->isClass()))
continue;

const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr;
const CXXRecordDecl *ProblematicBaseClass = nullptr;
auto hasRefInBase = clang::hasPublicMethodInBase(&Base, "ref");
auto hasDerefInBase = clang::hasPublicMethodInBase(&Base, "deref");

const auto IsPublicBaseRefCntblWOVirtualDtor =
[RD, &ProblematicBaseSpecifier,
&ProblematicBaseClass](const CXXBaseSpecifier *Base, CXXBasePath &) {
const auto AccSpec = Base->getAccessSpecifier();
if (AccSpec == AS_protected || AccSpec == AS_private ||
(AccSpec == AS_none && RD->isClass()))
return false;
bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;

auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
QualType T = Base.getType();
if (T.isNull())
continue;

bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;
const CXXRecordDecl *C = T->getAsCXXRecordDecl();
if (!C)
continue;

QualType T = Base->getType();
if (T.isNull())
return false;

const CXXRecordDecl *C = T->getAsCXXRecordDecl();
if (!C)
return false;
if (isRefCountedClass(C))
return false;

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 &) {
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,
CXXBasePaths Paths;
Paths.setOrigin(C);
hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths,
/*LookupInDependent =*/true);
hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
/*LookupInDependent =*/true);
hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths,
/*LookupInDependent =*/true);
if (AnyInconclusiveBase || !hasRef || !hasDeref)
return false;

const auto *Dtor = C->getDestructor();
if (!Dtor || !Dtor->isVirtual()) {
ProblematicBaseSpecifier = Base;
ProblematicBaseClass = C;
return true;
}

return false;
};

if (RD->lookupInBases(IsPublicBaseRefCntblWOVirtualDtor, Paths,
/*LookupInDependent =*/true)) {
reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
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);
return Result && *Result;
},
Paths, /*LookupInDependent =*/true))
continue;

const auto *Dtor = C->getDestructor();
if (!Dtor || !Dtor->isVirtual()) {
auto *ProblematicBaseSpecifier = &Base;
auto *ProblematicBaseClass = C;
reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass);
}
}
}

Expand Down Expand Up @@ -182,6 +341,32 @@ class RefCntblBaseVirtualDtorChecker
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") {
DerefAnalysisVisitor DerefAnalysis(ClsTmplSpDecl->getTemplateArgs(),
DerivedClass);
auto Result = DerefAnalysis.HasSpecializedDelete(MethodDecl);
if (!Result || *Result)
return Result;
}
}
return false;
}
for (auto *MethodDecl : C->methods()) {
if (safeGetName(MethodDecl) == "deref") {
DerefAnalysisVisitor DerefAnalysis(DerivedClass);
auto Result = DerefAnalysis.HasSpecializedDelete(MethodDecl);
if (!Result || *Result)
return Result;
}
}
return false;
}

void reportBug(const CXXRecordDecl *DerivedClass,
const CXXBaseSpecifier *BaseSpec,
const CXXRecordDecl *ProblematicBaseClass) const {
Expand Down
Loading
Loading