]> granicus.if.org Git - clang/commitdiff
[ObjC] Allow the use of implemented unavailable methods from within
authorAlex Lorenz <arphaman@gmail.com>
Wed, 9 Jan 2019 22:31:37 +0000 (22:31 +0000)
committerAlex Lorenz <arphaman@gmail.com>
Wed, 9 Jan 2019 22:31:37 +0000 (22:31 +0000)
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

lib/Sema/SemaDeclAttr.cpp
test/SemaObjC/call-unavailable-init-in-self.m [new file with mode: 0644]

index d1db71838fed6b7c96162e4cf9b87235d20efb0b..864c930136e4f3ba8a270d24774211f1c58ba376 100644 (file)
@@ -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<ObjCMethodDecl>(OffendingDecl)) {
+        if (const auto *Impl = dyn_cast<ObjCImplDecl>(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 (file)
index 0000000..fa6f670
--- /dev/null
@@ -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