From: Alex Lorenz Date: Wed, 9 Jan 2019 22:31:37 +0000 (+0000) Subject: [ObjC] Allow the use of implemented unavailable methods from within X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b01820327c555b8e32787e728892d4b89288e700;p=clang [ObjC] Allow the use of implemented unavailable methods from within the @implementation context In Objective-C, it's common for some frameworks to mark some methods like init as unavailable in the @interface to prohibit their usage. However, these frameworks then often implemented said method and refer to it in another method that acts as a factory for that object. The recent change to how messages to self are type checked in clang (r349841) introduced a regression which started to prohibit this pattern with an X is unavailable error. This commit addresses the aforementioned regression. rdar://47134898 Differential Revision: https://reviews.llvm.org/D56469 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@350768 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp index d1db71838f..864c930136 100644 --- a/lib/Sema/SemaDeclAttr.cpp +++ b/lib/Sema/SemaDeclAttr.cpp @@ -7269,9 +7269,10 @@ ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D, /// whether we should emit a diagnostic for \c K and \c DeclVersion in /// the context of \c Ctx. For example, we should emit an unavailable diagnostic /// in a deprecated context, but not the other way around. -static bool ShouldDiagnoseAvailabilityInContext(Sema &S, AvailabilityResult K, - VersionTuple DeclVersion, - Decl *Ctx) { +static bool +ShouldDiagnoseAvailabilityInContext(Sema &S, AvailabilityResult K, + VersionTuple DeclVersion, Decl *Ctx, + const NamedDecl *OffendingDecl) { assert(K != AR_Available && "Expected an unavailable declaration here!"); // Checks if we should emit the availability diagnostic in the context of C. @@ -7280,9 +7281,22 @@ static bool ShouldDiagnoseAvailabilityInContext(Sema &S, AvailabilityResult K, if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C)) if (AA->getIntroduced() >= DeclVersion) return true; - } else if (K == AR_Deprecated) + } else if (K == AR_Deprecated) { if (C->isDeprecated()) return true; + } else if (K == AR_Unavailable) { + // It is perfectly fine to refer to an 'unavailable' Objective-C method + // when it's actually defined and is referenced from within the + // @implementation itself. In this context, we interpret unavailable as a + // form of access control. + if (const auto *MD = dyn_cast(OffendingDecl)) { + if (const auto *Impl = dyn_cast(C)) { + if (MD->getClassInterface() == Impl->getClassInterface() && + MD->isDefined()) + return true; + } + } + } if (C->isUnavailable()) return true; @@ -7471,7 +7485,8 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K, if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl)) DeclVersion = AA->getIntroduced(); - if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx)) + if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx, + OffendingDecl)) return; SourceLocation Loc = Locs.front(); @@ -7955,7 +7970,8 @@ void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability( // If the context of this function is less available than D, we should not // emit a diagnostic. - if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced, Ctx)) + if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced, Ctx, + OffendingDecl)) return; // We would like to emit the diagnostic even if -Wunguarded-availability is diff --git a/test/SemaObjC/call-unavailable-init-in-self.m b/test/SemaObjC/call-unavailable-init-in-self.m new file mode 100644 index 0000000000..fa6f670cc9 --- /dev/null +++ b/test/SemaObjC/call-unavailable-init-in-self.m @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -x objective-c -verify -fobjc-arc %s + +@interface NSObject + ++ (instancetype)new; ++ (instancetype)alloc; + +@end + +@interface Sub: NSObject + +- (instancetype)init __attribute__((unavailable)); // expected-note 4 {{'init' has been explicitly marked unavailable here}} + +- (void)notImplemented __attribute__((unavailable)); // expected-note {{'notImplemented' has been explicitly marked unavailable here}} + +@end + +@implementation Sub + ++ (Sub *)create { + return [[self alloc] init]; +} + ++ (Sub *)create2 { + return [self new]; +} + ++ (Sub *)create3 { + return [Sub new]; +} + +- (instancetype) init { + return self; +} + +- (void)reportUseOfUnimplemented { + [self notImplemented]; // expected-error {{'notImplemented' is unavailable}} +} + +@end + +@interface SubClassContext: Sub +@end + +@implementation SubClassContext + +- (void)subClassContext { + (void)[[Sub alloc] init]; // expected-error {{'init' is unavailable}} + (void)[Sub new]; // expected-error {{'new' is unavailable}} +} + +@end + +void unrelatedContext() { + (void)[[Sub alloc] init]; // expected-error {{'init' is unavailable}} + (void)[Sub new]; // expected-error {{'new' is unavailable}} +} + +@interface X @end + +@interface X (Foo) +-(void)meth __attribute__((unavailable)); +@end + +@implementation X (Foo) +-(void)meth {} +-(void)call_it { [self meth]; } +@end