Implement the 'null_resettable' attribute for Objective-C properties.

'null_resettable' properties are those whose getters return nonnull
but whose setters take nil, to "reset" the property to some
default. Implements rdar://problem/19051334.

llvm-svn: 240155
This commit is contained in:
Douglas Gregor 2015-06-19 18:14:46 +00:00
parent 813a066f16
commit 849ebc269f
9 changed files with 134 additions and 8 deletions

View File

@ -2206,13 +2206,14 @@ public:
OBJC_PR_unsafe_unretained = 0x800,
/// Indicates that the nullability of the type was spelled with a
/// property attribute rather than a type qualifier.
OBJC_PR_nullability = 0x1000
OBJC_PR_nullability = 0x1000,
OBJC_PR_null_resettable = 0x2000
// Adding a property should change NumPropertyAttrsBits
};
enum {
/// \brief Number of bits fitting all the property attributes.
NumPropertyAttrsBits = 13
NumPropertyAttrsBits = 14
};
enum SetterKind { Assign, Retain, Copy, Weak };

View File

@ -7714,6 +7714,10 @@ def note_nullability_type_specifier : Note<
"'%select{__nonnull|__nullable|__null_unspecified}0' to affect the innermost "
"pointer type of %1">;
def warn_null_resettable_setter : Warning<
"synthesized setter %0 for null_resettable property %1 does not handle nil">,
InGroup<Nullability>;
}
} // end of sema component.

View File

@ -805,7 +805,8 @@ public:
DQ_PR_weak = 0x200,
DQ_PR_strong = 0x400,
DQ_PR_unsafe_unretained = 0x800,
DQ_PR_nullability = 0x1000
DQ_PR_nullability = 0x1000,
DQ_PR_null_resettable = 0x2000
};
ObjCDeclSpec()
@ -865,7 +866,7 @@ private:
ObjCDeclQualifier objcDeclQualifier : 7;
// NOTE: VC++ treats enums as signed, avoid using ObjCPropertyAttributeKind
unsigned PropertyAttributes : 13;
unsigned PropertyAttributes : 14;
unsigned Nullability : 2;

View File

@ -2918,6 +2918,9 @@ public:
ObjCContainerDecl *CDecl,
bool SynthesizeProperties);
/// Diagnose any null-resettable synthesized setters.
void diagnoseNullResettableSynthesizedSetters(ObjCImplDecl *impDecl);
/// DefaultSynthesizeProperties - This routine default synthesizes all
/// properties which must be synthesized in the class's \@implementation.
void DefaultSynthesizeProperties (Scope *S, ObjCImplDecl* IMPDecl,

View File

@ -1213,8 +1213,14 @@ void DeclPrinter::VisitObjCPropertyDecl(ObjCPropertyDecl *PDecl) {
if (PDecl->getPropertyAttributes() &
ObjCPropertyDecl::OBJC_PR_nullability) {
if (auto nullability = stripOuterNullability(T)) {
Out << (first ? ' ' : ',')
<< getNullabilitySpelling(*nullability).substr(2);
if (*nullability == NullabilityKind::Unspecified &&
(PDecl->getPropertyAttributes() &
ObjCPropertyDecl::OBJC_PR_null_resettable)) {
Out << (first ? ' ' : ',') << "null_resettable";
} else {
Out << (first ? ' ' : ',')
<< getNullabilitySpelling(*nullability).substr(2);
}
first = false;
}
}

View File

@ -611,6 +611,7 @@ static void diagnoseRedundantPropertyNullability(Parser &P,
/// nonnull
/// nullable
/// null_unspecified
/// null_resettable
///
void Parser::ParseObjCPropertyAttribute(ObjCDeclSpec &DS) {
assert(Tok.getKind() == tok::l_paren);
@ -717,6 +718,16 @@ void Parser::ParseObjCPropertyAttribute(ObjCDeclSpec &DS) {
Tok.getLocation());
DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_nullability);
DS.setNullability(Tok.getLocation(), NullabilityKind::Unspecified);
} else if (II->isStr("null_resettable")) {
if (DS.getPropertyAttributes() & ObjCDeclSpec::DQ_PR_nullability)
diagnoseRedundantPropertyNullability(*this, DS,
NullabilityKind::Unspecified,
Tok.getLocation());
DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_nullability);
DS.setNullability(Tok.getLocation(), NullabilityKind::Unspecified);
// Also set the null_resettable bit.
DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_null_resettable);
} else {
Diag(AttrName, diag::err_objc_expected_property_attr) << II;
SkipUntil(tok::r_paren, StopAtSemi);

View File

@ -2024,6 +2024,9 @@ void Sema::ImplMethodsVsClassMethods(Scope *S, ObjCImplDecl* IMPDecl,
DiagnoseUnimplementedProperties(S, IMPDecl, CDecl, SynthesizeProperties);
}
// Diagnose null-resettable synthesized setters.
diagnoseNullResettableSynthesizedSetters(IMPDecl);
SelectorSet ClsMap;
for (const auto *I : IMPDecl->class_methods())
ClsMap.insert(I->getSelector());

View File

@ -361,6 +361,9 @@ Sema::HandlePropertyInClassExtension(Scope *S,
PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_atomic);
if (Attributes & ObjCDeclSpec::DQ_PR_nullability)
PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_nullability);
if (Attributes & ObjCDeclSpec::DQ_PR_null_resettable)
PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_null_resettable);
// Set setter/getter selector name. Needed later.
PDecl->setGetterName(GetterSel);
PDecl->setSetterName(SetterSel);
@ -646,6 +649,9 @@ ObjCPropertyDecl *Sema::CreatePropertyDecl(Scope *S,
if (Attributes & ObjCDeclSpec::DQ_PR_nullability)
PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_nullability);
if (Attributes & ObjCDeclSpec::DQ_PR_null_resettable)
PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_null_resettable);
return PDecl;
}
@ -1760,6 +1766,33 @@ void Sema::DiagnoseUnimplementedProperties(Scope *S, ObjCImplDecl* IMPDecl,
}
}
void Sema::diagnoseNullResettableSynthesizedSetters(ObjCImplDecl *impDecl) {
for (const auto *propertyImpl : impDecl->property_impls()) {
const auto *property = propertyImpl->getPropertyDecl();
// Warn about null_resettable properties with synthesized setters,
// because the setter won't properly handle nil.
if (propertyImpl->getPropertyImplementation()
== ObjCPropertyImplDecl::Synthesize &&
(property->getPropertyAttributes() &
ObjCPropertyDecl::OBJC_PR_null_resettable) &&
property->getGetterMethodDecl() &&
property->getSetterMethodDecl()) {
auto *getterMethod = property->getGetterMethodDecl();
auto *setterMethod = property->getSetterMethodDecl();
if (!impDecl->getInstanceMethod(setterMethod->getSelector()) &&
!impDecl->getInstanceMethod(getterMethod->getSelector())) {
SourceLocation loc = propertyImpl->getLocation();
if (loc.isInvalid())
loc = impDecl->getLocStart();
Diag(loc, diag::warn_null_resettable_setter)
<< setterMethod->getSelector() << property->getDeclName();
}
}
}
}
void
Sema::AtomicPropertySetterGetterRules (ObjCImplDecl* IMPDecl,
ObjCContainerDecl* IDecl) {
@ -1995,9 +2028,21 @@ void Sema::ProcessPropertyDecl(ObjCPropertyDecl *property,
redeclaredProperty->getLocation() :
property->getLocation();
// If the property is null_resettable, the getter returns nonnull.
QualType resultTy = property->getType();
if (property->getPropertyAttributes() &
ObjCPropertyDecl::OBJC_PR_null_resettable) {
QualType modifiedTy = resultTy;
if (auto nullability = AttributedType::stripOuterNullability(modifiedTy)){
if (*nullability == NullabilityKind::Unspecified)
resultTy = Context.getAttributedType(AttributedType::attr_nonnull,
modifiedTy, modifiedTy);
}
}
GetterMethod = ObjCMethodDecl::Create(Context, Loc, Loc,
property->getGetterName(),
property->getType(), nullptr, CD,
resultTy, nullptr, CD,
/*isInstance=*/true, /*isVariadic=*/false,
/*isPropertyAccessor=*/true,
/*isImplicitlyDeclared=*/true, /*isDefined=*/false,
@ -2058,12 +2103,25 @@ void Sema::ProcessPropertyDecl(ObjCPropertyDecl *property,
ObjCMethodDecl::Optional :
ObjCMethodDecl::Required);
// If the property is null_resettable, the setter accepts a
// nullable value.
QualType paramTy = property->getType().getUnqualifiedType();
if (property->getPropertyAttributes() &
ObjCPropertyDecl::OBJC_PR_null_resettable) {
QualType modifiedTy = paramTy;
if (auto nullability = AttributedType::stripOuterNullability(modifiedTy)){
if (*nullability == NullabilityKind::Unspecified)
paramTy = Context.getAttributedType(AttributedType::attr_nullable,
modifiedTy, modifiedTy);
}
}
// Invent the arguments for the setter. We don't bother making a
// nice name for the argument.
ParmVarDecl *Argument = ParmVarDecl::Create(Context, SetterMethod,
Loc, Loc,
property->getIdentifier(),
property->getType().getUnqualifiedType(),
paramTy,
/*TInfo=*/nullptr,
SC_None,
nullptr);

View File

@ -164,6 +164,45 @@ void test_instancetype(InitializableClass * __nonnull ic, id __nonnull object) {
ip = [InitializableClass returnInstanceOfMe]; // expected-warning{{incompatible pointer types assigning to 'int *' from 'InitializableClass * __nullable'}}
ip = [object returnMe]; // expected-warning{{incompatible pointer types assigning to 'int *' from 'id __nullable'}}
}
// Check null_resettable getters/setters.
__attribute__((objc_root_class))
@interface NSResettable
@property(null_resettable,retain) NSResettable *resettable1; // expected-note{{passing argument to parameter 'resettable1' here}}
@property(null_resettable,retain,nonatomic) NSResettable *resettable2;
@property(null_resettable,retain,nonatomic) NSResettable *resettable3;
@property(null_resettable,retain,nonatomic) NSResettable *resettable4;
@property(null_resettable,retain,nonatomic) NSResettable *resettable5;
@property(null_resettable,retain,nonatomic) NSResettable *resettable6;
@end
void test_null_resettable(NSResettable *r, int *ip) {
[r setResettable1:ip]; // expected-warning{{incompatible pointer types sending 'int *' to parameter of type 'NSResettable * __nullable'}}
r.resettable1 = ip; // expected-warning{{incompatible pointer types assigning to 'NSResettable * __nullable' from 'int *'}}
}
@implementation NSResettable // expected-warning{{synthesized setter 'setResettable4:' for null_resettable property 'resettable4' does not handle nil}}
- (NSResettable *)resettable1 {
int *ip = 0;
return ip; // expected-warning{{result type 'NSResettable * __nonnull'}}
}
- (void)setResettable1:(NSResettable *)param {
}
@synthesize resettable2; // no warning; not synthesized
@synthesize resettable3; // expected-warning{{synthesized setter 'setResettable3:' for null_resettable property 'resettable3' does not handle nil}}
- (void)setResettable2:(NSResettable *)param {
}
@dynamic resettable5;
- (NSResettable *)resettable6 {
return 0; // no warning
}
@end
// rdar://problem/19814852
@interface MultiProp
@property (nullable, copy) id a, b, c;