Skip to content

Fix WebKit static analyzers to support ref and deref methods to be defined on different classes. #69985

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

Merged
merged 1 commit into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 34 additions & 28 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,15 @@ using namespace clang;

namespace {

bool hasPublicRefAndDeref(const CXXRecordDecl *R) {
bool hasPublicMethodInBaseClass(const CXXRecordDecl *R,
const char *NameToMatch) {
assert(R);
assert(R->hasDefinition());

bool hasRef = false;
bool hasDeref = false;
for (const CXXMethodDecl *MD : R->methods()) {
const auto MethodName = safeGetName(MD);

if (MethodName == "ref" && MD->getAccess() == AS_public) {
if (hasDeref)
return true;
hasRef = true;
} else if (MethodName == "deref" && MD->getAccess() == AS_public) {
if (hasRef)
return true;
hasDeref = true;
}
if (MethodName == NameToMatch && MD->getAccess() == AS_public)
return true;
}
return false;
}
Expand All @@ -44,9 +35,8 @@ bool hasPublicRefAndDeref(const CXXRecordDecl *R) {

namespace clang {

std::optional<const clang::CXXRecordDecl*>
isRefCountable(const CXXBaseSpecifier* Base)
{
std::optional<const clang::CXXRecordDecl *>
hasPublicMethodInBase(const CXXBaseSpecifier *Base, const char *NameToMatch) {
assert(Base);

const Type *T = Base->getType().getTypePtrOrNull();
Expand All @@ -59,7 +49,7 @@ isRefCountable(const CXXBaseSpecifier* Base)
if (!R->hasDefinition())
return std::nullopt;

return hasPublicRefAndDeref(R) ? R : nullptr;
return hasPublicMethodInBaseClass(R, NameToMatch) ? R : nullptr;
}

std::optional<bool> isRefCountable(const CXXRecordDecl* R)
Expand All @@ -70,29 +60,45 @@ std::optional<bool> isRefCountable(const CXXRecordDecl* R)
if (!R)
return std::nullopt;

if (hasPublicRefAndDeref(R))
bool hasRef = hasPublicMethodInBaseClass(R, "ref");
bool hasDeref = hasPublicMethodInBaseClass(R, "deref");
if (hasRef && hasDeref)
return true;

CXXBasePaths Paths;
Paths.setOrigin(const_cast<CXXRecordDecl *>(R));

bool AnyInconclusiveBase = false;
const auto isRefCountableBase =
[&AnyInconclusiveBase](const CXXBaseSpecifier* Base, CXXBasePath&) {
std::optional<const clang::CXXRecordDecl*> IsRefCountable = clang::isRefCountable(Base);
if (!IsRefCountable) {
AnyInconclusiveBase = true;
return false;
}
return (*IsRefCountable) != nullptr;
const auto hasPublicRefInBase =
[&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
if (!hasRefInBase) {
AnyInconclusiveBase = true;
return false;
}
return (*hasRefInBase) != nullptr;
};

bool BasesResult = R->lookupInBases(isRefCountableBase, Paths,
hasRef = hasRef || R->lookupInBases(hasPublicRefInBase, Paths,
/*LookupInDependent =*/true);
if (AnyInconclusiveBase)
return std::nullopt;

return BasesResult;
const auto hasPublicDerefInBase =
[&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) {
auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");
if (!hasDerefInBase) {
AnyInconclusiveBase = true;
return false;
}
return (*hasDerefInBase) != nullptr;
};
hasDeref = hasDeref || R->lookupInBases(hasPublicDerefInBase, Paths,
/*LookupInDependent =*/true);
if (AnyInconclusiveBase)
return std::nullopt;

return hasRef && hasDeref;
}

bool isCtorOfRefCounted(const clang::FunctionDecl *F) {
Expand Down
8 changes: 4 additions & 4 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ class Type;
// In WebKit there are two ref-counted templated smart pointers: RefPtr<T> and
// Ref<T>.

/// \returns CXXRecordDecl of the base if the type is ref-countable, nullptr if
/// not, std::nullopt if inconclusive.
std::optional<const clang::CXXRecordDecl*>
isRefCountable(const clang::CXXBaseSpecifier* Base);
/// \returns CXXRecordDecl of the base if the type has ref as a public method,
/// nullptr if not, std::nullopt if inconclusive.
std::optional<const clang::CXXRecordDecl *>
hasPublicMethodInBase(const CXXBaseSpecifier *Base, const char *NameToMatch);

/// \returns true if \p Class is ref-countable, false if not, std::nullopt if
/// inconclusive.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,53 @@ class RefCntblBaseVirtualDtorChecker
(AccSpec == AS_none && RD->isClass()))
return false;

std::optional<const CXXRecordDecl*> RefCntblBaseRD = isRefCountable(Base);
if (!RefCntblBaseRD || !(*RefCntblBaseRD))
auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this entire new section can be deduplicated with isRefCountable() in the other file. They look so similar!

auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref");

bool hasRef = hasRefInBase && *hasRefInBase != nullptr;
bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr;

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

const CXXRecordDecl *C = T->getAsCXXRecordDecl();
if (!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 &) {
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)
return false;

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.RefCntblBaseVirtualDtor -verify %s

struct RefCountedBase {
void ref() {}
};

template<typename T> struct RefCounted : RefCountedBase {
public:
void deref() const { }
};

struct Base : RefCounted<Base> {
// expected-warning@-1{{Struct 'RefCounted<Base>' is used as a base of struct 'Base' but doesn't have virtual destructor}}
virtual void foo() { }
};

struct Derived : Base { };
// expected-warning@-1{{Struct 'Base' is used as a base of struct 'Derived' but doesn't have virtual destructor}}

void foo () {
Derived d;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.NoUncountedMemberChecker -verify %s

#include "mock-types.h"

class RefCountedBase {
public:
void ref() const { }
};

template<typename T> class RefCounted : public RefCountedBase {
public:
virtual ~RefCounted() { }
void deref() const { }
};

class TreeNode : public RefCounted<TreeNode> {
public:
void setParent(TreeNode& parent) { m_parent = &parent; }

private:
TreeNode* m_parent;
// expected-warning@-1{{Member variable 'm_parent' in 'TreeNode' is a raw pointer to ref-countable type 'TreeNode'}}
};