Skip to content

[WebKit Checkers] Recognize Objective-C and CF pointer conversion functions. #132784

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 2 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,10 @@ bool isPtrConversion(const FunctionDecl *F) {
FunctionName == "dynamicDowncast" || FunctionName == "downcast" ||
FunctionName == "checkedDowncast" ||
FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast" ||
FunctionName == "bridge_cast")
FunctionName == "bridge_cast" || FunctionName == "bridge_id_cast" ||
FunctionName == "dynamic_cf_cast" || FunctionName == "checked_cf_cast" ||
FunctionName == "dynamic_objc_cast" ||
FunctionName == "checked_objc_cast")
return true;

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,12 @@ class RawPtrRefCallArgsChecker
overloadedOperatorType == OO_PipePipe)
return true;

if (isCtorOfSafePtr(Callee))
if (isCtorOfSafePtr(Callee) || isPtrConversion(Callee))
return true;

auto name = safeGetName(Callee);
if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" ||
name == "dynamicDowncast" || name == "downcast" ||
name == "checkedDowncast" || name == "uncheckedDowncast" ||
name == "bitwise_cast" || name == "is" || name == "equal" ||
name == "hash" || name == "isType" ||
name == "is" || name == "equal" || name == "hash" || name == "isType" ||
// FIXME: Most/all of these should be implemented via attributes.
name == "equalIgnoringASCIICase" ||
name == "equalIgnoringASCIICaseCommon" ||
Expand Down
143 changes: 143 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/objc-mock-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T)))
#define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T)))
typedef CF_BRIDGED_TYPE(id) void * CFTypeRef;
typedef unsigned long long CFTypeID;
typedef signed char BOOL;
typedef unsigned char Boolean;
typedef signed long CFIndex;
Expand All @@ -21,6 +22,8 @@ typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef;

extern const CFAllocatorRef kCFAllocatorDefault;
typedef struct _NSZone NSZone;
CFTypeID CFGetTypeID(CFTypeRef cf);
CFTypeID CFArrayGetTypeID();
CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity);
extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value);
CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues);
Expand All @@ -29,6 +32,7 @@ CFIndex CFArrayGetCount(CFArrayRef theArray);
typedef const struct CF_BRIDGED_TYPE(NSDictionary) __CFDictionary * CFDictionaryRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableDictionary) __CFDictionary * CFMutableDictionaryRef;

CFTypeID CFDictionaryGetTypeID();
CFDictionaryRef CFDictionaryCreate(CFAllocatorRef allocator, const void **keys, const void **values, CFIndex numValues);
CFDictionaryRef CFDictionaryCreateCopy(CFAllocatorRef allocator, CFDictionaryRef theDict);
CFDictionaryRef CFDictionaryCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFDictionaryRef theDict);
Expand Down Expand Up @@ -135,6 +139,8 @@ __attribute__((objc_root_class))

namespace WTF {

void WTFCrash(void);

template<typename T> class RetainPtr;
template<typename T> RetainPtr<T> adoptNS(T*);
template<typename T> RetainPtr<T> adoptCF(T);
Expand Down Expand Up @@ -273,11 +279,148 @@ inline CFTypeRef bridge_cast(NSObject *object)
return (__bridge CFTypeRef)object;
}

template <typename ExpectedType>
struct ObjCTypeCastTraits {
public:
static bool isType(id object) { return [object isKindOfClass:[ExpectedType class]]; }

template <typename ArgType>
static bool isType(const ArgType *object) { return [object isKindOfClass:[ExpectedType class]]; }
};

template <typename ExpectedType, typename ArgType>
inline bool is_objc(ArgType * source)
{
return source && ObjCTypeCastTraits<ExpectedType>::isType(source);
}

template<typename T> inline T *checked_objc_cast(id object)
{
if (!object)
return nullptr;

if (!is_objc<T>(object))
WTFCrash();

return reinterpret_cast<T*>(object);
}

template<typename T, typename U> inline T *checked_objc_cast(U *object)
{
if (!object)
return nullptr;

if (!is_objc<T>(object))
WTFCrash();

return static_cast<T*>(object);
}

template<typename T, typename U> RetainPtr<T> dynamic_objc_cast(RetainPtr<U>&& object)
{
if (!is_objc<T>(object.get()))
return nullptr;
return adoptNS(static_cast<T*>(object.leakRef()));
}

template<typename T> RetainPtr<T> dynamic_objc_cast(RetainPtr<id>&& object)
{
if (!is_objc<T>(object.get()))
return nullptr;
return adoptNS(reinterpret_cast<T*>(object.leakRef()));
}

template<typename T, typename U> RetainPtr<T> dynamic_objc_cast(const RetainPtr<U>& object)
{
if (!is_objc<T>(object.get()))
return nullptr;
return static_cast<T*>(object.get());
}

template<typename T> RetainPtr<T> dynamic_objc_cast(const RetainPtr<id>& object)
{
if (!is_objc<T>(object.get()))
return nullptr;
return reinterpret_cast<T*>(object.get());
}

template<typename T> T *dynamic_objc_cast(NSObject *object)
{
if (!is_objc<T>(object))
return nullptr;
return static_cast<T*>(object);
}

template<typename T> T *dynamic_objc_cast(id object)
{
if (!is_objc<T>(object))
return nullptr;
return reinterpret_cast<T*>(object);
}

template <typename> struct CFTypeTrait;

template<typename T> T dynamic_cf_cast(CFTypeRef object)
{
if (!object)
return nullptr;

if (CFGetTypeID(object) != CFTypeTrait<T>::typeID())
return nullptr;

return static_cast<T>(const_cast<CF_BRIDGED_TYPE(id) void*>(object));
}

template<typename T> T checked_cf_cast(CFTypeRef object)
{
if (!object)
return nullptr;

if (CFGetTypeID(object) != CFTypeTrait<T>::typeID())
WTFCrash();

return static_cast<T>(const_cast<CF_BRIDGED_TYPE(id) void*>(object));
}

template<typename T, typename U> RetainPtr<T> dynamic_cf_cast(RetainPtr<U>&& object)
{
if (!object)
return nullptr;

if (CFGetTypeID(object.get()) != CFTypeTrait<T>::typeID())
return nullptr;

return adoptCF(static_cast<T>(const_cast<CF_BRIDGED_TYPE(id) void*>(object.leakRef())));
}

} // namespace WTF

#define WTF_DECLARE_CF_TYPE_TRAIT(ClassName) \
template <> \
struct WTF::CFTypeTrait<ClassName##Ref> { \
static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \
};

WTF_DECLARE_CF_TYPE_TRAIT(CFArray);
WTF_DECLARE_CF_TYPE_TRAIT(CFDictionary);

#define WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(ClassName, MutableClassName) \
template <> \
struct WTF::CFTypeTrait<MutableClassName##Ref> { \
static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \
};

WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFArray, CFMutableArray);
WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFDictionary, CFMutableDictionary);

using WTF::RetainPtr;
using WTF::adoptNS;
using WTF::adoptCF;
using WTF::retainPtr;
using WTF::downcast;
using WTF::bridge_cast;
using WTF::is_objc;
using WTF::checked_objc_cast;
using WTF::dynamic_objc_cast;
using WTF::checked_cf_cast;
using WTF::dynamic_cf_cast;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this missing bridge_id_cast? I also don't seem to find any test case using bridge_id_cast.

Copy link
Contributor Author

@rniwa rniwa Mar 27, 2025

Choose a reason for hiding this comment

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

oh yeah, indeed, I'm missing bridge_id_cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a new test case for this.

22 changes: 22 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,28 @@ bool baz(NSObject *obj) {
}
}

namespace ptr_conversion {

SomeObj *provide_obj();

void dobjc(SomeObj* obj) {
[dynamic_objc_cast<OtherObj>(obj) doMoreWork:nil];
}

void cobjc(SomeObj* obj) {
[checked_objc_cast<OtherObj>(obj) doMoreWork:nil];
}

unsigned dcf(CFTypeRef obj) {
return CFArrayGetCount(dynamic_cf_cast<CFArrayRef>(obj));
}

unsigned ccf(CFTypeRef obj) {
return CFArrayGetCount(checked_cf_cast<CFArrayRef>(obj));
}

} // ptr_conversion

@interface TestObject : NSObject
- (void)doWork:(NSString *)msg, ...;
- (void)doWorkOnSelf;
Expand Down
27 changes: 27 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,33 @@ void bar() {
}
}

namespace ptr_conversion {

SomeObj *provide_obj();

void dobjc(SomeObj* obj) {
if (auto *otherObj = dynamic_objc_cast<OtherObj>(obj))
[otherObj doMoreWork:nil];
}

void cobjc(SomeObj* obj) {
auto *otherObj = checked_objc_cast<OtherObj>(obj);
[otherObj doMoreWork:nil];
}

unsigned dcf(CFTypeRef obj) {
if (CFArrayRef array = dynamic_cf_cast<CFArrayRef>(obj))
return CFArrayGetCount(array);
return 0;
}

unsigned ccf(CFTypeRef obj) {
CFArrayRef array = checked_cf_cast<CFArrayRef>(obj);
return CFArrayGetCount(array);
}

} // ptr_conversion

bool doMoreWorkOpaque(OtherObj*);

@implementation OtherObj
Expand Down