[clang] Support ptr to ptr and union in webkit member checker (PR #137566)
Ryosuke Niwa via cfe-commits
cfe-commits at lists.llvm.org
Sun Apr 27 17:04:50 PDT 2025
https://github.com/rniwa created https://github.com/llvm/llvm-project/pull/137566
None
>From a04f65339530bea920c80dc1b2b0f3c9bab488c3 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Sun, 20 Apr 2025 18:38:31 -0700
Subject: [PATCH 1/2] [RawPtrRefMemberChecker] Make RawPtrRefMemberChecker
consistent with other checkers
Refactor RawPtrRefMemberChecker so that each subclass override isUnsafePtr like other
WebKit checkers instead of overriding isPtrCompatible.
---
.../WebKit/RawPtrRefMemberChecker.cpp | 98 +++++++++----------
1 file changed, 45 insertions(+), 53 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
index 10b9749319a57..e8992f58273bb 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
@@ -38,9 +38,7 @@ class RawPtrRefMemberChecker
RawPtrRefMemberChecker(const char *description)
: Bug(this, description, "WebKit coding guidelines") {}
- virtual std::optional<bool>
- isPtrCompatible(const clang::QualType,
- const clang::CXXRecordDecl *R) const = 0;
+ virtual std::optional<bool> isUnsafePtr(QualType) const = 0;
virtual const char *typeName() const = 0;
virtual const char *invariant() const = 0;
@@ -93,22 +91,30 @@ class RawPtrRefMemberChecker
if (!MemberType)
continue;
- if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) {
- std::optional<bool> IsCompatible = isPtrCompatible(QT, MemberCXXRD);
- if (IsCompatible && *IsCompatible)
- reportBug(Member, MemberType, MemberCXXRD, RD);
- } else {
- std::optional<bool> IsCompatible = isPtrCompatible(QT, nullptr);
- auto *PointeeType = MemberType->getPointeeType().getTypePtrOrNull();
- if (IsCompatible && *IsCompatible) {
- auto *Desugared = PointeeType->getUnqualifiedDesugaredType();
- if (auto *ObjCType = dyn_cast_or_null<ObjCInterfaceType>(Desugared))
- reportBug(Member, MemberType, ObjCType->getDecl(), RD);
- }
- }
+ auto IsUnsafePtr = isUnsafePtr(QT);
+ if (!IsUnsafePtr || !*IsUnsafePtr)
+ continue;
+
+ if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl())
+ reportBug(Member, MemberType, MemberCXXRD, RD);
+ else if (auto *ObjCDecl = getObjCDecl(MemberType))
+ reportBug(Member, MemberType, ObjCDecl, RD);
}
}
+ ObjCInterfaceDecl *getObjCDecl(const Type *TypePtr) const {
+ auto *PointeeType = TypePtr->getPointeeType().getTypePtrOrNull();
+ if (!PointeeType)
+ return nullptr;
+ auto *Desugared = PointeeType->getUnqualifiedDesugaredType();
+ if (!Desugared)
+ return nullptr;
+ auto *ObjCType = dyn_cast<ObjCInterfaceType>(Desugared);
+ if (!ObjCType)
+ return nullptr;
+ return ObjCType->getDecl();
+ }
+
void visitObjCDecl(const ObjCContainerDecl *CD) const {
if (BR->getSourceManager().isInSystemHeader(CD->getLocation()))
return;
@@ -138,19 +144,15 @@ class RawPtrRefMemberChecker
const Type *IvarType = QT.getTypePtrOrNull();
if (!IvarType)
return;
- if (auto *IvarCXXRD = IvarType->getPointeeCXXRecordDecl()) {
- std::optional<bool> IsCompatible = isPtrCompatible(QT, IvarCXXRD);
- if (IsCompatible && *IsCompatible)
- reportBug(Ivar, IvarType, IvarCXXRD, CD);
- } else {
- std::optional<bool> IsCompatible = isPtrCompatible(QT, nullptr);
- auto *PointeeType = IvarType->getPointeeType().getTypePtrOrNull();
- if (IsCompatible && *IsCompatible) {
- auto *Desugared = PointeeType->getUnqualifiedDesugaredType();
- if (auto *ObjCType = dyn_cast_or_null<ObjCInterfaceType>(Desugared))
- reportBug(Ivar, IvarType, ObjCType->getDecl(), CD);
- }
- }
+
+ auto IsUnsafePtr = isUnsafePtr(QT);
+ if (!IsUnsafePtr || !*IsUnsafePtr)
+ return;
+
+ if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl())
+ reportBug(Ivar, IvarType, MemberCXXRD, CD);
+ else if (auto *ObjCDecl = getObjCDecl(IvarType))
+ reportBug(Ivar, IvarType, ObjCDecl, CD);
}
void visitObjCPropertyDecl(const ObjCContainerDecl *CD,
@@ -161,19 +163,15 @@ class RawPtrRefMemberChecker
const Type *PropType = QT.getTypePtrOrNull();
if (!PropType)
return;
- if (auto *PropCXXRD = PropType->getPointeeCXXRecordDecl()) {
- std::optional<bool> IsCompatible = isPtrCompatible(QT, PropCXXRD);
- if (IsCompatible && *IsCompatible)
- reportBug(PD, PropType, PropCXXRD, CD);
- } else {
- std::optional<bool> IsCompatible = isPtrCompatible(QT, nullptr);
- auto *PointeeType = PropType->getPointeeType().getTypePtrOrNull();
- if (IsCompatible && *IsCompatible) {
- auto *Desugared = PointeeType->getUnqualifiedDesugaredType();
- if (auto *ObjCType = dyn_cast_or_null<ObjCInterfaceType>(Desugared))
- reportBug(PD, PropType, ObjCType->getDecl(), CD);
- }
- }
+
+ auto IsUnsafePtr = isUnsafePtr(QT);
+ if (!IsUnsafePtr || !*IsUnsafePtr)
+ return;
+
+ if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
+ reportBug(PD, PropType, MemberCXXRD, CD);
+ else if (auto *ObjCDecl = getObjCDecl(PropType))
+ reportBug(PD, PropType, ObjCDecl, CD);
}
bool shouldSkipDecl(const RecordDecl *RD) const {
@@ -263,10 +261,8 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
: RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
"reference-countable type") {}
- std::optional<bool>
- isPtrCompatible(const clang::QualType,
- const clang::CXXRecordDecl *R) const final {
- return R ? isRefCountable(R) : std::nullopt;
+ std::optional<bool> isUnsafePtr(QualType QT) const final {
+ return isUncountedPtr(QT.getCanonicalType());
}
const char *typeName() const final { return "ref-countable type"; }
@@ -282,10 +278,8 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
: RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
"checked-pointer capable type") {}
- std::optional<bool>
- isPtrCompatible(const clang::QualType,
- const clang::CXXRecordDecl *R) const final {
- return R ? isCheckedPtrCapable(R) : std::nullopt;
+ std::optional<bool> isUnsafePtr(QualType QT) const final {
+ return isUncheckedPtr(QT.getCanonicalType());
}
const char *typeName() const final { return "CheckedPtr capable type"; }
@@ -304,9 +298,7 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
RTC = RetainTypeChecker();
}
- std::optional<bool>
- isPtrCompatible(const clang::QualType QT,
- const clang::CXXRecordDecl *) const final {
+ std::optional<bool> isUnsafePtr(QualType QT) const final {
return RTC->isUnretained(QT);
}
>From 0281e11cd8ba878e1d755fdef781250652fd6e91 Mon Sep 17 00:00:00 2001
From: Ryosuke Niwa <rniwa at webkit.org>
Date: Sun, 27 Apr 2025 16:52:13 -0700
Subject: [PATCH 2/2] [RawPtrRefMemberChecker] Add the support for union and
pointers to unsafe pointers.
This PR adds support for detecting unsafe union members and pointers to unsafe pointers
(e.g. T** where T* is an unsafe pointer type).
---
.../WebKit/RawPtrRefMemberChecker.cpp | 42 ++++++++++++-------
.../Checkers/WebKit/unchecked-members.cpp | 26 +++++++++++-
.../Checkers/WebKit/uncounted-members.cpp | 30 +++++++++++--
.../Checkers/WebKit/unretained-members-arc.mm | 27 ++++++++++++
.../Checkers/WebKit/unretained-members.mm | 34 +++++++++++++--
5 files changed, 136 insertions(+), 23 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
index e8992f58273bb..d1a4f203d7a49 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
@@ -85,21 +85,31 @@ class RawPtrRefMemberChecker
if (shouldSkipDecl(RD))
return;
- for (auto *Member : RD->fields()) {
- auto QT = Member->getType();
- const Type *MemberType = QT.getTypePtrOrNull();
- if (!MemberType)
- continue;
+ for (auto *Member : RD->fields())
+ visitMember(Member, RD);
+ }
- auto IsUnsafePtr = isUnsafePtr(QT);
- if (!IsUnsafePtr || !*IsUnsafePtr)
- continue;
+ void visitMember(const FieldDecl *Member, const RecordDecl *RD) const {
+ auto QT = Member->getType();
+ const Type *MemberType = QT.getTypePtrOrNull();
- if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl())
- reportBug(Member, MemberType, MemberCXXRD, RD);
- else if (auto *ObjCDecl = getObjCDecl(MemberType))
- reportBug(Member, MemberType, ObjCDecl, RD);
+ while (MemberType) {
+ auto IsUnsafePtr = isUnsafePtr(QT);
+ if (IsUnsafePtr && *IsUnsafePtr)
+ break;
+ if (!MemberType->isPointerType())
+ return;
+ QT = MemberType->getPointeeType();
+ MemberType = QT.getTypePtrOrNull();
}
+
+ if (!MemberType)
+ return;
+
+ if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl())
+ reportBug(Member, MemberType, MemberCXXRD, RD);
+ else if (auto *ObjCDecl = getObjCDecl(MemberType))
+ reportBug(Member, MemberType, ObjCDecl, RD);
}
ObjCInterfaceDecl *getObjCDecl(const Type *TypePtr) const {
@@ -192,7 +202,8 @@ class RawPtrRefMemberChecker
const auto Kind = RD->getTagKind();
// FIMXE: Should we check union members too?
- if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
+ if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class &&
+ Kind != TagTypeKind::Union)
return true;
// Ignore CXXRecords that come from system headers.
@@ -229,7 +240,10 @@ class RawPtrRefMemberChecker
printQuotedName(Os, Member);
Os << " in ";
printQuotedQualifiedName(Os, ClassCXXRD);
- Os << " is a ";
+ if (Member->getType().getTypePtrOrNull() == MemberType)
+ Os << " is a ";
+ else
+ Os << " contains a ";
if (printPointer(Os, MemberType) == PrintDeclKind::Pointer) {
auto Typedef = MemberType->getAs<TypedefType>();
assert(Typedef);
diff --git a/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp b/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp
index 048ffbffcdefb..3fe15d88ff312 100644
--- a/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp
@@ -34,10 +34,11 @@ namespace members {
} // namespace members
-namespace ignore_unions {
+namespace unions {
union Foo {
CheckedObj* a;
+ // expected-warning at -1{{Member variable 'a' in 'unions::Foo' is a raw pointer to CheckedPtr capable type 'CheckedObj'}}
CheckedPtr<CheckedObj> c;
CheckedRef<CheckedObj> d;
};
@@ -45,11 +46,12 @@ namespace ignore_unions {
template<class T>
union FooTmpl {
T* a;
+ // expected-warning at -1{{Member variable 'a' in 'unions::FooTmpl<CheckedObj>' is a raw pointer to CheckedPtr capable type 'CheckedObj'}}
};
void forceTmplToInstantiate(FooTmpl<CheckedObj>) { }
-} // namespace ignore_unions
+} // namespace unions
namespace checked_ptr_ref_ptr_capable {
@@ -59,3 +61,23 @@ namespace checked_ptr_ref_ptr_capable {
}
} // checked_ptr_ref_ptr_capable
+
+namespace ptr_to_ptr_to_checked_ptr_capable {
+
+ struct List {
+ CheckedObj** elements;
+ // expected-warning at -1{{Member variable 'elements' in 'ptr_to_ptr_to_checked_ptr_capable::List' contains a raw pointer to CheckedPtr capable type 'CheckedObj'}}
+ };
+
+ template <typename T>
+ struct TemplateList {
+ T** elements;
+ // expected-warning at -1{{Member variable 'elements' in 'ptr_to_ptr_to_checked_ptr_capable::TemplateList<CheckedObj>' contains a raw pointer to CheckedPtr capable type 'CheckedObj'}}
+ };
+ TemplateList<CheckedObj> list;
+
+ struct SafeList {
+ CheckedPtr<CheckedObj>* elements;
+ };
+
+} // namespace ptr_to_ptr_to_checked_ptr_capable
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp
index 130777a9a5fee..b8c443cda4f8e 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp
@@ -36,20 +36,22 @@ namespace members {
};
} // members
-namespace ignore_unions {
+namespace unions {
union Foo {
RefCountable* a;
+ // expected-warning at -1{{Member variable 'a' in 'unions::Foo' is a raw pointer to ref-countable type 'RefCountable'}}
RefPtr<RefCountable> b;
Ref<RefCountable> c;
};
template<class T>
- union RefPtr {
+ union FooTmpl {
T* a;
+ // expected-warning at -1{{Member variable 'a' in 'unions::FooTmpl<RefCountable>' is a raw pointer to ref-countable type 'RefCountable'}}
};
- void forceTmplToInstantiate(RefPtr<RefCountable>) {}
-} // ignore_unions
+ void forceTmplToInstantiate(FooTmpl<RefCountable>) {}
+} // unions
namespace ignore_system_header {
@@ -77,3 +79,23 @@ namespace checked_ptr_ref_ptr_capable {
}
} // checked_ptr_ref_ptr_capable
+
+namespace ptr_to_ptr_to_ref_counted {
+
+ struct List {
+ RefCountable** elements;
+ // expected-warning at -1{{Member variable 'elements' in 'ptr_to_ptr_to_ref_counted::List' contains a raw pointer to ref-countable type 'RefCountable'}}
+ };
+
+ template <typename T>
+ struct TemplateList {
+ T** elements;
+ // expected-warning at -1{{Member variable 'elements' in 'ptr_to_ptr_to_ref_counted::TemplateList<RefCountable>' contains a raw pointer to ref-countable type 'RefCountable'}}
+ };
+ TemplateList<RefCountable> list;
+
+ struct SafeList {
+ RefPtr<RefCountable>* elements;
+ };
+
+} // namespace ptr_to_ptr_to_ref_counted
diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm
index 9820c875b87c0..3491bc93ed98a 100644
--- a/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm
+++ b/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm
@@ -21,6 +21,12 @@
CFMutableArrayRef e = nullptr;
// expected-warning at -1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}}
};
+
+ union FooUnion {
+ SomeObj* a;
+ CFMutableArrayRef b;
+ // expected-warning at -1{{Member variable 'b' in 'members::FooUnion' is a retainable type 'CFMutableArrayRef'}}
+ };
template<class T, class S>
struct FooTmpl {
@@ -37,3 +43,24 @@ void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
};
}
+
+namespace ptr_to_ptr_to_retained {
+
+ struct List {
+ CFMutableArrayRef* elements2;
+ // expected-warning at -1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::List' contains a retainable type 'CFMutableArrayRef'}}
+ };
+
+ template <typename T, typename S>
+ struct TemplateList {
+ S* elements2;
+ // expected-warning at -1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::TemplateList<SomeObj, __CFArray *>' contains a raw pointer to retainable type '__CFArray'}}
+ };
+ TemplateList<SomeObj, CFMutableArrayRef> list;
+
+ struct SafeList {
+ RetainPtr<SomeObj>* elements1;
+ RetainPtr<CFMutableArrayRef>* elements2;
+ };
+
+} // namespace ptr_to_ptr_to_retained
diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm
index fff1f8ede091b..0cb4c4ac0f6a0 100644
--- a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm
+++ b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm
@@ -47,21 +47,49 @@ void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
}
-namespace ignore_unions {
+namespace unions {
union Foo {
SomeObj* a;
+ // expected-warning at -1{{Member variable 'a' in 'unions::Foo' is a raw pointer to retainable type 'SomeObj'}}
RetainPtr<SomeObj> b;
CFMutableArrayRef c;
+ // expected-warning at -1{{Member variable 'c' in 'unions::Foo' is a retainable type 'CFMutableArrayRef'}}
};
template<class T>
- union RefPtr {
+ union FooTempl {
T* a;
+ // expected-warning at -1{{Member variable 'a' in 'unions::FooTempl<SomeObj>' is a raw pointer to retainable type 'SomeObj'}}
};
- void forceTmplToInstantiate(RefPtr<SomeObj>) {}
+ void forceTmplToInstantiate(FooTempl<SomeObj>) {}
}
+namespace ptr_to_ptr_to_retained {
+
+ struct List {
+ SomeObj** elements1;
+ // expected-warning at -1{{Member variable 'elements1' in 'ptr_to_ptr_to_retained::List' contains a raw pointer to retainable type 'SomeObj'}}
+ CFMutableArrayRef* elements2;
+ // expected-warning at -1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::List' contains a retainable type 'CFMutableArrayRef'}}
+ };
+
+ template <typename T, typename S>
+ struct TemplateList {
+ T** elements1;
+ // expected-warning at -1{{Member variable 'elements1' in 'ptr_to_ptr_to_retained::TemplateList<SomeObj, __CFArray *>' contains a raw pointer to retainable type 'SomeObj'}}
+ S* elements2;
+ // expected-warning at -1{{Member variable 'elements2' in 'ptr_to_ptr_to_retained::TemplateList<SomeObj, __CFArray *>' contains a raw pointer to retainable type '__CFArray'}}
+ };
+ TemplateList<SomeObj, CFMutableArrayRef> list;
+
+ struct SafeList {
+ RetainPtr<SomeObj>* elements1;
+ RetainPtr<CFMutableArrayRef>* elements2;
+ };
+
+} // namespace ptr_to_ptr_to_retained
+
@interface AnotherObject : NSObject {
NSString *ns_string;
// expected-warning at -1{{Instance variable 'ns_string' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
More information about the cfe-commits
mailing list