96 changes: 90 additions & 6 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ static unsigned getFunctionOrMethodNumParams(const Decl *D) {
return cast<ObjCMethodDecl>(D)->param_size();
}

static const ParmVarDecl *getFunctionOrMethodParam(const Decl *D,
unsigned Idx) {
if (const auto *FD = dyn_cast<FunctionDecl>(D))
return FD->getParamDecl(Idx);
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D))
return MD->getParamDecl(Idx);
if (const auto *BD = dyn_cast<BlockDecl>(D))
return BD->getParamDecl(Idx);
return nullptr;
}

static QualType getFunctionOrMethodParamType(const Decl *D, unsigned Idx) {
if (const FunctionType *FnTy = D->getFunctionType())
return cast<FunctionProtoType>(FnTy)->getParamType(Idx);
Expand All @@ -103,12 +114,8 @@ static QualType getFunctionOrMethodParamType(const Decl *D, unsigned Idx) {
}

static SourceRange getFunctionOrMethodParamRange(const Decl *D, unsigned Idx) {
if (const auto *FD = dyn_cast<FunctionDecl>(D))
return FD->getParamDecl(Idx)->getSourceRange();
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D))
return MD->parameters()[Idx]->getSourceRange();
if (const auto *BD = dyn_cast<BlockDecl>(D))
return BD->getParamDecl(Idx)->getSourceRange();
if (auto *PVD = getFunctionOrMethodParam(D, Idx))
return PVD->getSourceRange();
return SourceRange();
}

Expand Down Expand Up @@ -6093,6 +6100,79 @@ static void handleUninitializedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
UninitializedAttr(AL.getLoc(), S.Context, Index));
}

static bool tryMakeVariablePseudoStrong(Sema &S, VarDecl *VD,
bool DiagnoseFailure) {
QualType Ty = VD->getType();
if (!Ty->isObjCRetainableType()) {
if (DiagnoseFailure) {
S.Diag(VD->getBeginLoc(), diag::warn_ignored_objc_externally_retained)
<< 0;
}
return false;
}

Qualifiers::ObjCLifetime LifetimeQual = Ty.getQualifiers().getObjCLifetime();

// Sema::inferObjCARCLifetime must run after processing decl attributes
// (because __block lowers to an attribute), so if the lifetime hasn't been
// explicitly specified, infer it locally now.
if (LifetimeQual == Qualifiers::OCL_None)
LifetimeQual = Ty->getObjCARCImplicitLifetime();

// The attributes only really makes sense for __strong variables; ignore any
// attempts to annotate a parameter with any other lifetime qualifier.
if (LifetimeQual != Qualifiers::OCL_Strong) {
if (DiagnoseFailure) {
S.Diag(VD->getBeginLoc(), diag::warn_ignored_objc_externally_retained)
<< 1;
}
return false;
}

// Tampering with the type of a VarDecl here is a bit of a hack, but we need
// to ensure that the variable is 'const' so that we can error on
// modification, which can otherwise over-release.
VD->setType(Ty.withConst());
VD->setARCPseudoStrong(true);
return true;
}

static void handleObjCExternallyRetainedAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
if (auto *VD = dyn_cast<VarDecl>(D)) {
assert(!isa<ParmVarDecl>(VD) && "should be diagnosed automatically");
if (!VD->hasLocalStorage()) {
S.Diag(D->getBeginLoc(), diag::warn_ignored_objc_externally_retained)
<< 0;
return;
}

if (!tryMakeVariablePseudoStrong(S, VD, /*DiagnoseFailure=*/true))
return;

handleSimpleAttribute<ObjCExternallyRetainedAttr>(S, D, AL);
return;
}

// If D is a function-like declaration (method, block, or function), then we
// make every parameter psuedo-strong.
for (unsigned I = 0, E = getFunctionOrMethodNumParams(D); I != E; ++I) {
auto *PVD = const_cast<ParmVarDecl *>(getFunctionOrMethodParam(D, I));
QualType Ty = PVD->getType();

// If a user wrote a parameter with __strong explicitly, then assume they
// want "real" strong semantics for that parameter. This works because if
// the parameter was written with __strong, then the strong qualifier will
// be non-local.
if (Ty.getLocalUnqualifiedType().getQualifiers().getObjCLifetime() ==
Qualifiers::OCL_Strong)
continue;

tryMakeVariablePseudoStrong(S, PVD, /*DiagnoseFailure=*/false);
}
handleSimpleAttribute<ObjCExternallyRetainedAttr>(S, D, AL);
}

//===----------------------------------------------------------------------===//
// Top Level Sema Entry Points
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -6788,6 +6868,10 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_Uninitialized:
handleUninitializedAttr(S, D, AL);
break;

case ParsedAttr::AT_ObjCExternallyRetained:
handleObjCExternallyRetainedAttr(S, D, AL);
break;
}
}

Expand Down
12 changes: 9 additions & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11214,17 +11214,23 @@ static bool CheckForModifiableLvalue(Expr *E, SourceLocation Loc, Sema &S) {
if (var->isARCPseudoStrong() &&
(!var->getTypeSourceInfo() ||
!var->getTypeSourceInfo()->getType().isConstQualified())) {
// There are two pseudo-strong cases:
// There are three pseudo-strong cases:
// - self
ObjCMethodDecl *method = S.getCurMethodDecl();
if (method && var == method->getSelfDecl())
if (method && var == method->getSelfDecl()) {
DiagID = method->isClassMethod()
? diag::err_typecheck_arc_assign_self_class_method
: diag::err_typecheck_arc_assign_self;

// - Objective-C externally_retained attribute.
} else if (var->hasAttr<ObjCExternallyRetainedAttr>() ||
isa<ParmVarDecl>(var)) {
DiagID = diag::err_typecheck_arc_assign_externally_retained;

// - fast enumeration variables
else
} else {
DiagID = diag::err_typecheck_arr_assign_enumeration;
}

SourceRange Assign;
if (Loc != OrigLoc)
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1350,14 +1350,14 @@ ASTDeclReader::RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
VD->VarDeclBits.SClass = (StorageClass)Record.readInt();
VD->VarDeclBits.TSCSpec = Record.readInt();
VD->VarDeclBits.InitStyle = Record.readInt();
VD->VarDeclBits.ARCPseudoStrong = Record.readInt();
if (!isa<ParmVarDecl>(VD)) {
VD->NonParmVarDeclBits.IsThisDeclarationADemotedDefinition =
Record.readInt();
VD->NonParmVarDeclBits.ExceptionVar = Record.readInt();
VD->NonParmVarDeclBits.NRVOVariable = Record.readInt();
VD->NonParmVarDeclBits.CXXForRangeDecl = Record.readInt();
VD->NonParmVarDeclBits.ObjCForDecl = Record.readInt();
VD->NonParmVarDeclBits.ARCPseudoStrong = Record.readInt();
VD->NonParmVarDeclBits.IsInline = Record.readInt();
VD->NonParmVarDeclBits.IsInlineSpecified = Record.readInt();
VD->NonParmVarDeclBits.IsConstexpr = Record.readInt();
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -922,13 +922,13 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
Record.push_back(D->getStorageClass());
Record.push_back(D->getTSCSpec());
Record.push_back(D->getInitStyle());
Record.push_back(D->isARCPseudoStrong());
if (!isa<ParmVarDecl>(D)) {
Record.push_back(D->isThisDeclarationADemotedDefinition());
Record.push_back(D->isExceptionVariable());
Record.push_back(D->isNRVOVariable());
Record.push_back(D->isCXXForRangeDecl());
Record.push_back(D->isObjCForDecl());
Record.push_back(D->isARCPseudoStrong());
Record.push_back(D->isInline());
Record.push_back(D->isInlineSpecified());
Record.push_back(D->isConstexpr());
Expand Down Expand Up @@ -1986,6 +1986,7 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(0)); // SClass
Abv->Add(BitCodeAbbrevOp(0)); // TSCSpec
Abv->Add(BitCodeAbbrevOp(0)); // InitStyle
Abv->Add(BitCodeAbbrevOp(0)); // ARCPseudoStrong
Abv->Add(BitCodeAbbrevOp(0)); // Linkage
Abv->Add(BitCodeAbbrevOp(0)); // HasInit
Abv->Add(BitCodeAbbrevOp(0)); // HasMemberSpecializationInfo
Expand Down Expand Up @@ -2062,12 +2063,12 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // SClass
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); // TSCSpec
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); // InitStyle
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isARCPseudoStrong
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // IsThisDeclarationADemotedDefinition
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isExceptionVariable
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isNRVOVariable
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isCXXForRangeDecl
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isObjCForDecl
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isARCPseudoStrong
Abv->Add(BitCodeAbbrevOp(0)); // isInline
Abv->Add(BitCodeAbbrevOp(0)); // isInlineSpecified
Abv->Add(BitCodeAbbrevOp(0)); // isConstexpr
Expand Down
115 changes: 115 additions & 0 deletions clang/test/CodeGenObjC/externally-retained.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-arc -fblocks -Wno-objc-root-class -O0 %s -S -emit-llvm -o - | FileCheck %s --dump-input-on-failure
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-arc -fblocks -Wno-objc-root-class -O0 -xobjective-c++ -std=c++11 %s -S -emit-llvm -o - | FileCheck %s --check-prefix CHECKXX --dump-input-on-failure

#define EXT_RET __attribute__((objc_externally_retained))

@interface ObjTy @end

ObjTy *global;

#if __cplusplus
// Suppress name mangling in C++ mode for the sake of check lines.
extern "C" void param(ObjTy *p);
extern "C" void local();
extern "C" void in_init();
extern "C" void anchor();
extern "C" void block_capture(ObjTy *);
extern "C" void esc(void (^)());
extern "C" void escp(void (^)(ObjTy *));
extern "C" void block_param();
#endif

void param(ObjTy *p) EXT_RET {
// CHECK-LABEL: define void @param
// CHECK-NOT: llvm.objc.
// CHECK ret
}

void local() {
EXT_RET ObjTy *local = global;
// CHECK-LABEL: define void @local
// CHECK-NOT: llvm.objc.
// CHECK: ret
}

void in_init() {
// Test that we do the right thing when a variable appears in it's own
// initializer. Here, we release the value stored in 'wat' after overwriting
// it, in case it was somehow set to point to a non-null object while it's
// initializer is being evaluated.
EXT_RET ObjTy *wat = 0 ? wat : global;

// CHECK-LABEL: define void @in_init
// CHECK: [[WAT:%.*]] = alloca
// CHECK-NEXT: store {{.*}} null, {{.*}} [[WAT]]
// CHECK-NEXT: [[GLOBAL:%.*]] = load {{.*}} @global
// CHECK-NEXT: [[WAT_LOAD:%.*]] = load {{.*}} [[WAT]]
// CHECK-NEXT: store {{.*}} [[GLOBAL]], {{.*}} [[WAT]]
// CHECK-NEXT: [[CASTED:%.*]] = bitcast {{.*}} [[WAT_LOAD]] to
// CHECK-NEXT: call void @llvm.objc.release(i8* [[CASTED]])

// CHECK-NOT: llvm.objc.
// CHECK: ret
}

void esc(void (^)());

void block_capture(ObjTy *obj) EXT_RET {
esc(^{ (void)obj; });

// CHECK-LABEL: define void @block_capture
// CHECK-NOT: llvm.objc.
// CHECK: call i8* @llvm.objc.retain
// CHECK-NOT: llvm.objc.
// CHECK: call void @esc
// CHECK-NOT: llvm.objc.
// CHECK: call void @llvm.objc.storeStrong({{.*}} null)
// CHECK-NOT: llvm.objc.
// CHECK: ret

// CHECK-LABEL: define {{.*}} void @__copy_helper_block_
// CHECK-NOT: llvm.objc.
// CHECK: llvm.objc.storeStrong
// CHECK-NOT: llvm.objc.
// CHECK: ret

// CHECK-LABEL: define {{.*}} void @__destroy_helper_block_
// CHECK-NOT: llvm.objc.
// CHECK: llvm.objc.storeStrong({{.*}} null)
// CHECK-NOT: llvm.objc.
// CHECK: ret
}

void escp(void (^)(ObjTy *));

void block_param() {
escp(^(ObjTy *p) EXT_RET {});

// CHECK-LABEL: define internal void @__block_param_block_invoke
// CHECK-NOT: llvm.objc.
// CHECK: ret
}

@interface Inter
-(void)m1: (ObjTy *)w;
@end

@implementation Inter
-(void)m1: (ObjTy *) w EXT_RET {
// CHECK-LABEL: define internal void @"\01-[Inter m1:]"
// CHECK-NOT: llvm.objc.
// CHECK: ret
}
-(void)m2: (ObjTy *) w EXT_RET {
// CHECK-LABEL: define internal void @"\01-[Inter m2:]"
// CHECK-NOT: llvm.objc.
// CHECK: ret
}
@end

#if __cplusplus
// Verify that the decltype(p) is resolved before 'p' is made implicitly const.
__attribute__((objc_externally_retained))
void foo(ObjTy *p, decltype(p) *) {}
// CHECKXX: _Z3fooP5ObjTyPU8__strongS0_
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
// CHECK-NEXT: ObjCBridgeRelated (SubjectMatchRule_record)
// CHECK-NEXT: ObjCException (SubjectMatchRule_objc_interface)
// CHECK-NEXT: ObjCExplicitProtocolImpl (SubjectMatchRule_objc_protocol)
// CHECK-NEXT: ObjCExternallyRetained (SubjectMatchRule_variable_not_is_parameter, SubjectMatchRule_function, SubjectMatchRule_block, SubjectMatchRule_objc_method)
// CHECK-NEXT: ObjCMethodFamily (SubjectMatchRule_objc_method)
// CHECK-NEXT: ObjCPreciseLifetime (SubjectMatchRule_variable)
// CHECK-NEXT: ObjCRequiresPropertyDefs (SubjectMatchRule_objc_interface)
Expand Down
7 changes: 7 additions & 0 deletions clang/test/SemaObjC/externally-retained-no-arc.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// RUN: %clang_cc1 %s -verify

@interface NSWidget @end

__attribute__((objc_externally_retained)) void f(NSWidget *p) { // expected-warning{{'objc_externally_retained' attribute ignored}}
__attribute__((objc_externally_retained)) NSWidget *w; // expected-warning{{'objc_externally_retained' attribute ignored}}
}
114 changes: 114 additions & 0 deletions clang/test/SemaObjC/externally-retained.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-runtime=macosx-10.13.0 -fblocks -fobjc-arc %s -verify
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-runtime=macosx-10.13.0 -fblocks -fobjc-arc -xobjective-c++ %s -verify

#define EXT_RET __attribute__((objc_externally_retained))

@interface ObjCTy
@end

void test1() {
EXT_RET int a; // expected-warning{{'objc_externally_retained' can only be applied to}}
EXT_RET __weak ObjCTy *b; // expected-warning{{'objc_externally_retained' can only be applied to}}
EXT_RET __weak int (^c)(); // expected-warning{{'objc_externally_retained' can only be applied to}}

EXT_RET int (^d)() = ^{return 0;};
EXT_RET ObjCTy *e = 0;
EXT_RET __strong ObjCTy *f = 0;

e = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
f = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
d = ^{ return 0; }; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
}

void test2(ObjCTy *a);

void test2(ObjCTy *a) EXT_RET {
a = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
}

EXT_RET ObjCTy *test3; // expected-warning{{'objc_externally_retained' can only be applied to}}

@interface X // expected-warning{{defined without specifying a base class}} expected-note{{add a super class}}
-(void)m: (ObjCTy *) p;
@end
@implementation X
-(void)m: (ObjCTy *) p EXT_RET {
p = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
}
@end

void test4() {
__attribute__((objc_externally_retained(0))) ObjCTy *a; // expected-error{{'objc_externally_retained' attribute takes no arguments}}
}

void test5(ObjCTy *first, __strong ObjCTy *second) EXT_RET {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0; // fine
}

void test6(ObjCTy *first,
__strong ObjCTy *second) EXT_RET {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
}

__attribute__((objc_root_class)) @interface Y @end

@implementation Y
- (void)test7:(__strong ObjCTy *)first
withThird:(ObjCTy *)second EXT_RET {
first = 0;
second = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
}
@end

void (^blk)(ObjCTy *, ObjCTy *) =
^(__strong ObjCTy *first, ObjCTy *second) EXT_RET {
first = 0;
second = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
};

void test8(EXT_RET ObjCTy *x) {} // expected-warning{{'objc_externally_retained' attribute only applies to variables}}

#pragma clang attribute ext_ret.push(__attribute__((objc_externally_retained)), apply_to=any(function, block, objc_method))
void test9(ObjCTy *first, __strong ObjCTy *second) {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
}
void (^test10)(ObjCTy *first, ObjCTy *second) = ^(ObjCTy *first, __strong ObjCTy *second) {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
};
__attribute__((objc_root_class)) @interface Test11 @end
@implementation Test11
-(void)meth: (ObjCTy *)first withSecond:(__strong ObjCTy *)second {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
}
+(void)othermeth: (ObjCTy *)first withSecond:(__strong ObjCTy *)second {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
}
@end

#if __cplusplus
class Test12 {
void inline_member(ObjCTy *first, __strong ObjCTy *second) {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
}
static void static_inline_member(ObjCTy *first, __strong ObjCTy *second) {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
}
};
#endif

void test13(ObjCTy *first, __weak ObjCTy *second, __unsafe_unretained ObjCTy *third, __strong ObjCTy *fourth) {
first = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}}
second = 0;
third = 0;
fourth = 0;
}

#pragma clang attribute ext_ret.pop