Skip to content

[alpha.webkit.NoUnretainedMemberChecker] Recognize NS_REQUIRES_PROPERTY_DEFINITIONS #143408

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
Jun 10, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ std::optional<bool> isUnchecked(const QualType T) {
void RetainTypeChecker::visitTranslationUnitDecl(
const TranslationUnitDecl *TUD) {
IsARCEnabled = TUD->getLangOpts().ObjCAutoRefCount;
DefaultSynthProperties = TUD->getLangOpts().ObjCDefaultSynthProperties;
}

void RetainTypeChecker::visitTypedef(const TypedefDecl *TD) {
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,14 @@ class RetainTypeChecker {
llvm::DenseSet<const RecordType *> CFPointees;
llvm::DenseSet<const Type *> RecordlessTypes;
bool IsARCEnabled{false};
bool DefaultSynthProperties{true};

public:
void visitTranslationUnitDecl(const TranslationUnitDecl *);
void visitTypedef(const TypedefDecl *);
bool isUnretained(const QualType, bool ignoreARC = false);
bool isARCEnabled() const { return IsARCEnabled; }
bool defaultSynthProperties() const { return DefaultSynthProperties; }
};

/// \returns true if \p Class is NS or CF objects AND not retained, false if
Expand Down
75 changes: 64 additions & 11 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class RawPtrRefMemberChecker
private:
BugType Bug;
mutable BugReporter *BR;
mutable llvm::DenseSet<const ObjCIvarDecl *> IvarDeclsToIgnore;

protected:
mutable std::optional<RetainTypeChecker> RTC;
Expand All @@ -36,7 +37,8 @@ class RawPtrRefMemberChecker
RawPtrRefMemberChecker(const char *description)
: Bug(this, description, "WebKit coding guidelines") {}

virtual std::optional<bool> isUnsafePtr(QualType) const = 0;
virtual std::optional<bool> isUnsafePtr(QualType,
bool ignoreARC = false) const = 0;
virtual const char *typeName() const = 0;
virtual const char *invariant() const = 0;

Expand Down Expand Up @@ -138,6 +140,8 @@ class RawPtrRefMemberChecker
return;
}
if (auto *ID = dyn_cast<ObjCImplementationDecl>(CD)) {
for (auto *PropImpl : ID->property_impls())
visitPropImpl(CD, PropImpl);
for (auto *Ivar : ID->ivars())
visitIvarDecl(CD, Ivar);
return;
Expand All @@ -148,6 +152,10 @@ class RawPtrRefMemberChecker
const ObjCIvarDecl *Ivar) const {
if (BR->getSourceManager().isInSystemHeader(Ivar->getLocation()))
return;

if (IvarDeclsToIgnore.contains(Ivar))
return;

auto QT = Ivar->getType();
const Type *IvarType = QT.getTypePtrOrNull();
if (!IvarType)
Expand All @@ -157,6 +165,8 @@ class RawPtrRefMemberChecker
if (!IsUnsafePtr || !*IsUnsafePtr)
return;

IvarDeclsToIgnore.insert(Ivar);

if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl())
reportBug(Ivar, IvarType, MemberCXXRD, CD);
else if (auto *ObjCDecl = getObjCDecl(IvarType))
Expand All @@ -167,13 +177,15 @@ class RawPtrRefMemberChecker
const ObjCPropertyDecl *PD) const {
if (BR->getSourceManager().isInSystemHeader(PD->getLocation()))
return;
auto QT = PD->getType();
const Type *PropType = QT.getTypePtrOrNull();
if (!PropType)
return;

auto IsUnsafePtr = isUnsafePtr(QT);
if (!IsUnsafePtr || !*IsUnsafePtr)
if (const ObjCInterfaceDecl *ID = dyn_cast<ObjCInterfaceDecl>(CD)) {
if (!RTC || !RTC->defaultSynthProperties() ||
ID->isObjCRequiresPropertyDefs())
return;
}

auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD);
if (!IsUnsafe)
return;

if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
Expand All @@ -182,6 +194,47 @@ class RawPtrRefMemberChecker
reportBug(PD, PropType, ObjCDecl, CD);
}

void visitPropImpl(const ObjCContainerDecl *CD,
const ObjCPropertyImplDecl *PID) const {
if (BR->getSourceManager().isInSystemHeader(PID->getLocation()))
return;

if (PID->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
return;

auto *PropDecl = PID->getPropertyDecl();
if (auto *IvarDecl = PID->getPropertyIvarDecl()) {
if (IvarDeclsToIgnore.contains(IvarDecl))
return;
IvarDeclsToIgnore.insert(IvarDecl);
}
auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PropDecl);
if (!IsUnsafe)
return;

if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())
reportBug(PropDecl, PropType, MemberCXXRD, CD);
else if (auto *ObjCDecl = getObjCDecl(PropType))
reportBug(PropDecl, PropType, ObjCDecl, CD);
}

std::pair<bool, const Type *>
isPropImplUnsafePtr(const ObjCPropertyDecl *PD) const {
if (!PD)
return {false, nullptr};

auto QT = PD->getType();
const Type *PropType = QT.getTypePtrOrNull();
if (!PropType)
return {false, nullptr};

// "assign" property doesn't retain even under ARC so treat it as unsafe.
bool ignoreARC =
!PD->isReadOnly() && PD->getSetterKind() == ObjCPropertyDecl::Assign;
auto IsUnsafePtr = isUnsafePtr(QT, ignoreARC);
return {IsUnsafePtr && *IsUnsafePtr, PropType};
}

bool shouldSkipDecl(const RecordDecl *RD) const {
if (!RD->isThisDeclarationADefinition())
return true;
Expand Down Expand Up @@ -272,7 +325,7 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {
: RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
"reference-countable type") {}

std::optional<bool> isUnsafePtr(QualType QT) const final {
std::optional<bool> isUnsafePtr(QualType QT, bool) const final {
return isUncountedPtr(QT.getCanonicalType());
}

Expand All @@ -289,7 +342,7 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {
: RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "
"checked-pointer capable type") {}

std::optional<bool> isUnsafePtr(QualType QT) const final {
std::optional<bool> isUnsafePtr(QualType QT, bool) const final {
return isUncheckedPtr(QT.getCanonicalType());
}

Expand All @@ -309,8 +362,8 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {
RTC = RetainTypeChecker();
}

std::optional<bool> isUnsafePtr(QualType QT) const final {
return RTC->isUnretained(QT);
std::optional<bool> isUnsafePtr(QualType QT, bool ignoreARC) const final {
return RTC->isUnretained(QT, ignoreARC);
}

const char *typeName() const final { return "retainable type"; }
Expand Down
1 change: 1 addition & 0 deletions clang/test/Analysis/Checkers/WebKit/objc-mock-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ typedef struct CF_BRIDGED_TYPE(id) CGImage *CGImageRef;
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#define CF_CONSUMED __attribute__((cf_consumed))
#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
#define NS_REQUIRES_PROPERTY_DEFINITIONS __attribute__((objc_requires_property_definitions))

extern const CFAllocatorRef kCFAllocatorDefault;
typedef struct _NSZone NSZone;
Expand Down
36 changes: 36 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,39 @@ void forceTmplToInstantiate(FooTmpl<SomeObj, CFMutableArrayRef>) {}
};

} // namespace ptr_to_ptr_to_retained

@interface AnotherObject : NSObject {
NSString *ns_string;
CFStringRef cf_string;
// expected-warning@-1{{Instance variable 'cf_string' in 'AnotherObject' is a retainable type 'CFStringRef'; member variables must be a RetainPtr}}
}
@property(nonatomic, strong) NSString *prop_string1;
@property(nonatomic, assign) NSString *prop_string2;
// expected-warning@-1{{Property 'prop_string2' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@property(nonatomic, unsafe_unretained) NSString *prop_string3;
// expected-warning@-1{{Property 'prop_string3' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@property(nonatomic, readonly) NSString *prop_string4;
@end

NS_REQUIRES_PROPERTY_DEFINITIONS
@interface NoSynthObject : NSObject {
NSString *ns_string;
CFStringRef cf_string;
// expected-warning@-1{{Instance variable 'cf_string' in 'NoSynthObject' is a retainable type 'CFStringRef'; member variables must be a RetainPtr}}
}
@property(nonatomic, readonly, strong) NSString *prop_string1;
@property(nonatomic, readonly, strong) NSString *prop_string2;
@property(nonatomic, assign) NSString *prop_string3;
// expected-warning@-1{{Property 'prop_string3' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@property(nonatomic, unsafe_unretained) NSString *prop_string4;
// expected-warning@-1{{Property 'prop_string4' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@end

@implementation NoSynthObject
- (NSString *)prop_string1 {
return nil;
}
@synthesize prop_string2;
@synthesize prop_string3;
@synthesize prop_string4;
@end
25 changes: 25 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/unretained-members.mm
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,28 @@ @interface AnotherObject : NSObject {
@property(nonatomic, strong) NSString *prop_string;
// expected-warning@-1{{Property 'prop_string' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@end

NS_REQUIRES_PROPERTY_DEFINITIONS
@interface NoSynthObject : NSObject {
NSString *ns_string;
// expected-warning@-1{{Instance variable 'ns_string' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
CFStringRef cf_string;
// expected-warning@-1{{Instance variable 'cf_string' in 'NoSynthObject' is a retainable type 'CFStringRef'; member variables must be a RetainPtr}}
}
@property(nonatomic, readonly, strong) NSString *prop_string1;
@property(nonatomic, readonly, strong) NSString *prop_string2;
// expected-warning@-1{{Property 'prop_string2' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'}}
@property(nonatomic, assign) NSString *prop_string3;
// expected-warning@-1{{Property 'prop_string3' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@property(nonatomic, unsafe_unretained) NSString *prop_string4;
// expected-warning@-1{{Property 'prop_string4' in 'NoSynthObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}}
@end

@implementation NoSynthObject
- (NSString *)prop_string1 {
return nil;
}
@synthesize prop_string2;
@synthesize prop_string3;
@synthesize prop_string4;
@end
Loading