Index: clang/include/clang/AST/Decl.h =================================================================== --- clang/include/clang/AST/Decl.h +++ clang/include/clang/AST/Decl.h @@ -866,8 +866,12 @@ unsigned SClass : 3; unsigned TSCSpec : 2; unsigned InitStyle : 2; + + /// Whether this variable is an ARC pseudo-__strong variable; see + /// isARCPseudoStrong() for details. + unsigned ARCPseudoStrong : 1; }; - enum { NumVarDeclBits = 7 }; + enum { NumVarDeclBits = 8 }; protected: enum { NumParameterIndexBits = 8 }; @@ -940,10 +944,6 @@ /// Whether this variable is the for-in loop declaration in Objective-C. unsigned ObjCForDecl : 1; - /// Whether this variable is an ARC pseudo-__strong - /// variable; see isARCPseudoStrong() for details. - unsigned ARCPseudoStrong : 1; - /// Whether this variable is (C++1z) inline. unsigned IsInline : 1; @@ -1349,17 +1349,12 @@ NonParmVarDeclBits.ObjCForDecl = FRD; } - /// Determine whether this variable is an ARC pseudo-__strong - /// variable. A pseudo-__strong variable has a __strong-qualified - /// type but does not actually retain the object written into it. - /// Generally such variables are also 'const' for safety. - bool isARCPseudoStrong() const { - return isa(this) ? false : NonParmVarDeclBits.ARCPseudoStrong; - } - void setARCPseudoStrong(bool ps) { - assert(!isa(this)); - NonParmVarDeclBits.ARCPseudoStrong = ps; - } + /// Determine whether this variable is an ARC pseudo-__strong variable. A + /// pseudo-__strong variable has a __strong-qualified type but does not + /// actually retain the object written into it. Generally such variables are + /// also 'const' for safety. + bool isARCPseudoStrong() const { return VarDeclBits.ARCPseudoStrong; } + void setARCPseudoStrong(bool ps) { VarDeclBits.ARCPseudoStrong = ps; } /// Whether this variable is (C++1z) inline. bool isInline() const { Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -3131,3 +3131,9 @@ let Subjects = SubjectList<[Function, ObjCMethod], ErrorDiag>; let Documentation = [SpeculativeLoadHardeningDocs]; } + +def ObjCExternallyRetained : InheritableParamAttr { + let Spellings = [Clang<"objc_externally_retained">]; + let Subjects = SubjectList<[Var]>; + let Documentation = [ObjCExternallyRetainedDocs]; +} Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -3762,3 +3762,21 @@ (even after inlining) end up hardened. }]; } + +def ObjCExternallyRetainedDocs : Documentation { + let Category = DocCatVariable; + let Content = [{ +The ``objc_externally_retained`` attribute can be applied to strong function +parameters or local variables in ARC mode. When applied to parameters, it causes +clang to omit the implicit calls to objc_retain and objc_release on function +entry and exit respectivly. When applied to local variables, it causes clang to +omit the call to retain on variable initialization, and release when the +variable goes out of scope. Parameters and variables annotated with this +attribute are implicitly ``const``. + +This attribute is meant to be used to opt-out of the additional safety that ARC +provides when the programmer knows that the safety isn't necessary. You can read +more about ARC `here +`_. + }]; +} Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3488,6 +3488,10 @@ def err_objc_attr_protocol_requires_definition : Error< "attribute %0 can only be applied to @protocol definitions, not forward declarations">; +def warn_ignored_objc_externally_retained : Warning< + "'objc_externally_retained' can only be applied to strong retainable " + "object pointer types with automatic storage">, InGroup; + // Function Parameter Semantic Analysis. def err_param_with_void_type : Error<"argument may not have 'void' type">; def err_void_only_param : Error< @@ -5261,6 +5265,9 @@ def err_typecheck_arr_assign_enumeration : Error< "fast enumeration variables cannot be modified in ARC by default; " "declare the variable __strong to allow this">; +def err_typecheck_arc_assign_externally_retained : Error< + "variable declared with 'objc_externally_retained' " + "cannot be modified in ARC">; def warn_arc_retained_assign : Warning< "assigning retained object to %select{weak|unsafe_unretained}0 " "%select{property|variable}1" Index: clang/lib/CodeGen/CGDecl.cpp =================================================================== --- clang/lib/CodeGen/CGDecl.cpp +++ clang/lib/CodeGen/CGDecl.cpp @@ -797,15 +797,19 @@ case Qualifiers::OCL_None: llvm_unreachable("present but none"); + case Qualifiers::OCL_Strong: { + if (!D || !isa(D) || !cast(D)->isARCPseudoStrong()) { + value = EmitARCRetainScalarExpr(init); + break; + } + // If D is pseudo-strong, omit the retain. + LLVM_FALLTHROUGH; + } + case Qualifiers::OCL_ExplicitNone: value = EmitARCUnsafeUnretainedScalarExpr(init); break; - case Qualifiers::OCL_Strong: { - value = EmitARCRetainScalarExpr(init); - break; - } - case Qualifiers::OCL_Weak: { // If it's not accessed by the initializer, try to emit the // initialization with a copy or move. @@ -2095,16 +2099,20 @@ // 'self' is always formally __strong, but if this is not an // init method then we don't want to retain it. - if (D.isARCPseudoStrong()) { - const ObjCMethodDecl *method = cast(CurCodeDecl); - assert(&D == method->getSelfDecl()); - assert(lt == Qualifiers::OCL_Strong); - assert(qs.hasConst()); - assert(method->getMethodFamily() != OMF_init); - (void) method; - lt = Qualifiers::OCL_ExplicitNone; + if (auto *method = dyn_cast(CurCodeDecl)) { + if (D.isARCPseudoStrong() && method->getSelfDecl() == &D) { + assert(lt == Qualifiers::OCL_Strong); + assert(qs.hasConst()); + assert(method->getMethodFamily() != OMF_init); + lt = Qualifiers::OCL_ExplicitNone; + } } + // If a parameter is pseudo-strong (due to 'objc_externally_retained'), + // then we omit the implicit retain. + if (D.isARCPseudoStrong()) + lt = Qualifiers::OCL_ExplicitNone; + // Load objects passed indirectly. if (Arg.isIndirect() && !ArgVal) ArgVal = Builder.CreateLoad(DeclPtr); Index: clang/lib/CodeGen/CGExpr.cpp =================================================================== --- clang/lib/CodeGen/CGExpr.cpp +++ clang/lib/CodeGen/CGExpr.cpp @@ -419,8 +419,12 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) { const Expr *E = M->GetTemporaryExpr(); - // FIXME: ideally this would use EmitAnyExprToMem, however, we cannot do so - // as that will cause the lifetime adjustment to be lost for ARC + assert((!M->getExtendingDecl() || !isa(M->getExtendingDecl()) || + !cast(M->getExtendingDecl())->isARCPseudoStrong()) && + "Reference should never be pseudo-strong!"); + + // FIXME: ideally this would use EmitAnyExprToMem, however, we cannot do so + // as that will cause the lifetime adjustment to be lost for ARC auto ownership = M->getType().getObjCLifetime(); if (ownership != Qualifiers::OCL_None && ownership != Qualifiers::OCL_ExplicitNone) { @@ -1904,11 +1908,14 @@ } -/// EmitStoreThroughLValue - Store the specified rvalue into the specified -/// lvalue, where both are guaranteed to the have the same type, and that type -/// is 'Ty'. +/// Store the specified rvalue into the specified lvalue, where both are +/// guaranteed to the have the same type. +/// +/// \param isPsuedoStrong Currently this is always false, but it should be set +/// to true if a caller is initializing a pseudo-strong variable. void CodeGenFunction::EmitStoreThroughLValue(RValue Src, LValue Dst, - bool isInit) { + bool isInit, + bool isPseudoStrong) { if (!Dst.isSimple()) { if (Dst.isVectorElt()) { // Read/modify/write the vector, inserting the new element. @@ -1935,6 +1942,9 @@ // There's special magic for assigning into an ARC-qualified l-value. if (Qualifiers::ObjCLifetime Lifetime = Dst.getQuals().getObjCLifetime()) { + if (isInit && isPseudoStrong) + Lifetime = Qualifiers::OCL_ExplicitNone; + switch (Lifetime) { case Qualifiers::OCL_None: llvm_unreachable("present but none"); Index: clang/lib/CodeGen/CGObjC.cpp =================================================================== --- clang/lib/CodeGen/CGObjC.cpp +++ clang/lib/CodeGen/CGObjC.cpp @@ -155,7 +155,7 @@ (getLangOpts().ObjCAutoRefCount && CGM.getCodeGenOpts().OptimizationLevel != 0); - // Perform the actual initialialization of the array(s). + // Perform the actual initialization of the array(s). for (uint64_t i = 0; i < NumElements; i++) { if (ALE) { // Emit the element and store it to the appropriate array slot. Index: clang/lib/CodeGen/CodeGenFunction.h =================================================================== --- clang/lib/CodeGen/CodeGenFunction.h +++ clang/lib/CodeGen/CodeGenFunction.h @@ -3438,10 +3438,13 @@ RValue EmitLoadOfBitfieldLValue(LValue LV, SourceLocation Loc); RValue EmitLoadOfGlobalRegLValue(LValue LV); - /// EmitStoreThroughLValue - Store the specified rvalue into the specified - /// lvalue, where both are guaranteed to the have the same type, and that type - /// is 'Ty'. - void EmitStoreThroughLValue(RValue Src, LValue Dst, bool isInit = false); + /// Store the specified rvalue into the specified lvalue, where both are + /// guaranteed to the have the same type. + /// + /// \param isPsuedoStrong Currently this is always false, but it should be set + /// to true if a caller is initializing a pseudo-strong variable. + void EmitStoreThroughLValue(RValue Src, LValue Dst, bool isInit = false, + bool isPseudoStrong = false); void EmitStoreThroughExtVectorComponentLValue(RValue Src, LValue Dst); void EmitStoreThroughGlobalRegLValue(RValue Src, LValue Dst); Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -6080,6 +6080,47 @@ handleSimpleAttributeWithExclusions(S, D, A); } +static void handleObjCExternallyRetainedAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + // This attribute is a no-op without -fobjc-arc. + if (!S.getLangOpts().ObjCAutoRefCount) + return; + + auto *VD = cast(D); + QualType Ty = VD->getType(); + + if (!Ty->isObjCRetainableType() || !VD->isLocalVarDeclOrParm()) { + S.Diag(D->getBeginLoc(), diag::warn_ignored_objc_externally_retained); + return; + } + + 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) { + S.Diag(D->getBeginLoc(), diag::warn_ignored_objc_externally_retained); + return; + } + + // FIXME: If D is explicitly __strong, and this attribute is being applied via + // #pragma clang attribute, then ignore this attribute. + + // 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 overrelease. + VD->setType(Ty.withConst()); + VD->setARCPseudoStrong(true); + + handleSimpleAttribute(S, D, AL); +} + //===----------------------------------------------------------------------===// // Top Level Sema Entry Points //===----------------------------------------------------------------------===// @@ -6771,6 +6812,10 @@ case ParsedAttr::AT_NoDestroy: handleDestroyAttr(S, D, AL); break; + + case ParsedAttr::AT_ObjCExternallyRetained: + handleObjCExternallyRetainedAttr(S, D, AL); + break; } } Index: clang/lib/Sema/SemaExpr.cpp =================================================================== --- clang/lib/Sema/SemaExpr.cpp +++ clang/lib/Sema/SemaExpr.cpp @@ -11215,17 +11215,23 @@ 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 (declRef->getDecl() + ->hasAttr()) { + 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) Index: clang/lib/Serialization/ASTReaderDecl.cpp =================================================================== --- clang/lib/Serialization/ASTReaderDecl.cpp +++ clang/lib/Serialization/ASTReaderDecl.cpp @@ -1350,6 +1350,7 @@ VD->VarDeclBits.SClass = (StorageClass)Record.readInt(); VD->VarDeclBits.TSCSpec = Record.readInt(); VD->VarDeclBits.InitStyle = Record.readInt(); + VD->VarDeclBits.ARCPseudoStrong = Record.readInt(); if (!isa(VD)) { VD->NonParmVarDeclBits.IsThisDeclarationADemotedDefinition = Record.readInt(); @@ -1357,7 +1358,6 @@ 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(); Index: clang/lib/Serialization/ASTWriterDecl.cpp =================================================================== --- clang/lib/Serialization/ASTWriterDecl.cpp +++ clang/lib/Serialization/ASTWriterDecl.cpp @@ -922,13 +922,13 @@ Record.push_back(D->getStorageClass()); Record.push_back(D->getTSCSpec()); Record.push_back(D->getInitStyle()); + Record.push_back(D->isARCPseudoStrong()); if (!isa(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()); @@ -1986,6 +1986,7 @@ 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 @@ -2062,12 +2063,12 @@ 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 Index: clang/test/CodeGenObjC/externally-retained.m =================================================================== --- /dev/null +++ clang/test/CodeGenObjC/externally-retained.m @@ -0,0 +1,121 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-arc -fblocks -O0 %s -S -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-arc -fblocks -O0 -xobjective-c++ -std=c++11 %s -S -emit-llvm -o - | FileCheck %s --check-prefix CHECKXX + +#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(); +extern "C" void esc(void (^)()); +extern "C" void escp(void (^)(ObjTy *)); +extern "C" void block_param(); +#endif + +void param(EXT_RET ObjTy *p) { + // CHECK-LABEL: define void @param + // CHECK-NOT: objc_ + // CHECK ret +} + +void local() { + EXT_RET ObjTy *local = global; + // CHECK-LABEL: define void @local + // CHECK-NOT: 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 @objc_release(i8* [[CASTED]]) + + // CHECK-NOT: objc_ + // CHECK: ret +} + +void esc(void (^)()); + +void block_capture(EXT_RET ObjTy *obj) { + esc(^{ (void)obj; }); + + // CHECK-LABEL: define void @block_capture + // CHECK-NOT: objc_ + // CHECK: call i8* @objc_retain + // CHECK-NOT: objc_ + // CHECK: call void @esc + // CHECK-NOT: objc_ + // CHECK: call void @objc_storeStrong({{.*}} null) + // CHECK-NOT: objc_ + // CHECK: ret + + // CHECK-LABEL: define {{.*}} void @__copy_helper_block_ + // CHECK-NOT: objc_ + // CHECK: objc_storeStrong + // CHECK-NOT: objc_ + // CHECK: ret + + // CHECK-LABEL: define {{.*}} void @__destroy_helper_block_ + // CHECK-NOT: objc_ + // CHECK: objc_storeStrong({{.*}} null) + // CHECK-NOT: objc_ + // CHECK: ret +} + +void escp(void (^)(ObjTy *)); + +void block_param() { + escp(^(EXT_RET ObjTy *p) {}); + + // CHECK-LABEL: define internal void @__block_param_block_invoke + // CHECK-NOT: objc_ + // CHECK: ret +} + +@interface Inter +-(void)m1: (ObjTy *)w; +@end + +@implementation Inter +-(void)m1: (ObjTy *) EXT_RET w { + // CHECK-LABEL: define internal void @"\01-[Inter m1:]" + // CHECK-NOT: objc_ + // CHECK: ret +} +-(void)m2: (__strong ObjTy *) EXT_RET w { + // CHECK-LABEL: define internal void @"\01-[Inter m2:]" + // CHECK-NOT: objc_ + // CHECK: ret +} +@end + +#if __cplusplus +// Verify that the implicit "const" doesn't make it into the name mangling in +// C++ mode. +int takes_p(ObjTy *p); +int call_it = takes_p(0); +int takes_p(EXT_RET ObjTy *p) { + return 0; +} + +// CHECKXX-NOT: _Z7takes_pPK5ObjTy +// CHECKXX: _Z7takes_pP5ObjTy +#endif Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -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) // CHECK-NEXT: ObjCMethodFamily (SubjectMatchRule_objc_method) // CHECK-NEXT: ObjCPreciseLifetime (SubjectMatchRule_variable) // CHECK-NEXT: ObjCRequiresPropertyDefs (SubjectMatchRule_objc_interface) Index: clang/test/SemaObjC/externally-retained.m =================================================================== --- /dev/null +++ clang/test/SemaObjC/externally-retained.m @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.13.0 -fobjc-runtime=macosx-10.13.0 -fblocks -fobjc-arc %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)(); + EXT_RET ObjCTy *e; + EXT_RET __strong ObjCTy *f; + + 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(EXT_RET ObjCTy *a) { + 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 *) EXT_RET p { + p = 0; // expected-error{{variable declared with 'objc_externally_retained' cannot be modified in ARC}} +} +@end