Skip to content

Commit

Permalink
[Sema][ObjC] Infer availability of +new from availability of -init.
Browse files Browse the repository at this point in the history
When defined in NSObject, +new will call -init. If -init has been marked
unavailable, diagnose uses of +new.

rdar://18335828

Differential revision: https://reviews.llvm.org/D51189

llvm-svn: 341874
  • Loading branch information
epilk committed Sep 10, 2018
1 parent afb8b5d commit 4257857
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 18 deletions.
4 changes: 2 additions & 2 deletions clang/include/clang/AST/ASTContext.h
Expand Up @@ -336,7 +336,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
mutable IdentifierInfo *BoolName = nullptr;

/// The identifier 'NSObject'.
IdentifierInfo *NSObjectName = nullptr;
mutable IdentifierInfo *NSObjectName = nullptr;

/// The identifier 'NSCopying'.
IdentifierInfo *NSCopyingName = nullptr;
Expand Down Expand Up @@ -1676,7 +1676,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
}

/// Retrieve the identifier 'NSObject'.
IdentifierInfo *getNSObjectName() {
IdentifierInfo *getNSObjectName() const {
if (!NSObjectName) {
NSObjectName = &Idents.get("NSObject");
}
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/DeclObjC.h
Expand Up @@ -506,6 +506,9 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
/// Returns whether this specific method is a definition.
bool isThisDeclarationADefinition() const { return hasBody(); }

/// Is this method defined in the NSObject base class?
bool definedInNSObject(const ASTContext &) const;

// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == ObjCMethod; }
Expand Down
11 changes: 10 additions & 1 deletion clang/include/clang/AST/NSAPI.h
Expand Up @@ -166,6 +166,14 @@ class NSAPI {
return getOrInitSelector(StringRef("isEqual"), isEqualSel);
}

Selector getNewSelector() const {
return getOrInitNullarySelector("new", NewSel);
}

Selector getInitSelector() const {
return getOrInitNullarySelector("init", InitSel);
}

/// Enumerates the NSNumber methods used to generate literals.
enum NSNumberLiteralMethodKind {
NSNumberWithChar,
Expand Down Expand Up @@ -229,6 +237,7 @@ class NSAPI {
bool isObjCEnumerator(const Expr *E,
StringRef name, IdentifierInfo *&II) const;
Selector getOrInitSelector(ArrayRef<StringRef> Ids, Selector &Sel) const;
Selector getOrInitNullarySelector(StringRef Id, Selector &Sel) const;

ASTContext &Ctx;

Expand All @@ -251,7 +260,7 @@ class NSAPI {

mutable Selector objectForKeyedSubscriptSel, objectAtIndexedSubscriptSel,
setObjectForKeyedSubscriptSel,setObjectAtIndexedSubscriptSel,
isEqualSel;
isEqualSel, InitSel, NewSel;

mutable IdentifierInfo *BOOLId, *NSIntegerId, *NSUIntegerId;
mutable IdentifierInfo *NSASCIIStringEncodingId, *NSUTF8StringEncodingId;
Expand Down
6 changes: 4 additions & 2 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -3967,7 +3967,8 @@ class Sema {
void DiagnoseAvailabilityOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass,
bool ObjCPropertyAccess,
bool AvoidPartialAvailabilityChecks = false);
bool AvoidPartialAvailabilityChecks = false,
ObjCInterfaceDecl *ClassReceiver = nullptr);

bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
Expand All @@ -3982,7 +3983,8 @@ class Sema {
bool DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass = nullptr,
bool ObjCPropertyAccess = false,
bool AvoidPartialAvailabilityChecks = false);
bool AvoidPartialAvailabilityChecks = false,
ObjCInterfaceDecl *ClassReciever = nullptr);
void NoteDeletedFunction(FunctionDecl *FD);
void NoteDeletedInheritingConstructor(CXXConstructorDecl *CD);
std::string getDeletedOrUnavailableSuffix(const FunctionDecl *FD);
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/AST/DeclObjC.cpp
Expand Up @@ -829,6 +829,14 @@ bool ObjCMethodDecl::isThisDeclarationADesignatedInitializer() const {
hasAttr<ObjCDesignatedInitializerAttr>();
}

bool ObjCMethodDecl::definedInNSObject(const ASTContext &Ctx) const {
if (const auto *PD = dyn_cast<const ObjCProtocolDecl>(getDeclContext()))
return PD->getIdentifier() == Ctx.getNSObjectName();
if (const auto *ID = dyn_cast<const ObjCInterfaceDecl>(getDeclContext()))
return ID->getIdentifier() == Ctx.getNSObjectName();
return false;
}

bool ObjCMethodDecl::isDesignatedInitializerForTheInterface(
const ObjCMethodDecl **InitMethod) const {
if (getMethodFamily() != OMF_init)
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/AST/NSAPI.cpp
Expand Up @@ -607,3 +607,11 @@ Selector NSAPI::getOrInitSelector(ArrayRef<StringRef> Ids,
}
return Sel;
}

Selector NSAPI::getOrInitNullarySelector(StringRef Id, Selector &Sel) const {
if (Sel.isNull()) {
IdentifierInfo *Ident = &Ctx.Idents.get(Id);
Sel = Ctx.Selectors.getSelector(0, &Ident);
}
return Sel;
}
43 changes: 35 additions & 8 deletions clang/lib/Sema/SemaDeclAttr.cpp
Expand Up @@ -6967,8 +6967,12 @@ static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
/// \param D The declaration to check.
/// \param Message If non-null, this will be populated with the message from
/// the availability attribute that is selected.
/// \param ClassReceiver If we're checking the the method of a class message
/// send, the class. Otherwise nullptr.
static std::pair<AvailabilityResult, const NamedDecl *>
ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message) {
ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D,
std::string *Message,
ObjCInterfaceDecl *ClassReceiver) {
AvailabilityResult Result = D->getAvailability(Message);

// For typedefs, if the typedef declaration appears available look
Expand Down Expand Up @@ -7001,6 +7005,20 @@ ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message) {
}
}

// For +new, infer availability from -init.
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
if (S.NSAPIObj && ClassReceiver) {
ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod(
S.NSAPIObj->getInitSelector());
if (Init && Result == AR_Available && MD->isClassMethod() &&
MD->getSelector() == S.NSAPIObj->getNewSelector() &&
MD->definedInNSObject(S.getASTContext())) {
Result = Init->getAvailability(Message);
D = Init;
}
}
}

return {Result, D};
}

Expand Down Expand Up @@ -7589,7 +7607,8 @@ class DiagnoseUnguardedAvailability
SmallVector<VersionTuple, 8> AvailabilityStack;
SmallVector<const Stmt *, 16> StmtStack;

void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range);
void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range,
ObjCInterfaceDecl *ClassReceiver = nullptr);

public:
DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx)
Expand Down Expand Up @@ -7631,9 +7650,15 @@ class DiagnoseUnguardedAvailability
}

bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
if (ObjCMethodDecl *D = Msg->getMethodDecl())
if (ObjCMethodDecl *D = Msg->getMethodDecl()) {
ObjCInterfaceDecl *ID = nullptr;
QualType ReceiverTy = Msg->getClassReceiver();
if (!ReceiverTy.isNull() && ReceiverTy->getAsObjCInterfaceType())
ID = ReceiverTy->getAsObjCInterfaceType()->getInterface();

DiagnoseDeclAvailability(
D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()));
D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()), ID);
}
return true;
}

Expand All @@ -7659,11 +7684,11 @@ class DiagnoseUnguardedAvailability
};

void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
NamedDecl *D, SourceRange Range) {
NamedDecl *D, SourceRange Range, ObjCInterfaceDecl *ReceiverClass) {
AvailabilityResult Result;
const NamedDecl *OffendingDecl;
std::tie(Result, OffendingDecl) =
ShouldDiagnoseAvailabilityOfDecl(D, nullptr);
ShouldDiagnoseAvailabilityOfDecl(SemaRef, D, nullptr, ReceiverClass);
if (Result != AR_Available) {
// All other diagnostic kinds have already been handled in
// DiagnoseAvailabilityOfDecl.
Expand Down Expand Up @@ -7845,12 +7870,14 @@ void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass,
bool ObjCPropertyAccess,
bool AvoidPartialAvailabilityChecks) {
bool AvoidPartialAvailabilityChecks,
ObjCInterfaceDecl *ClassReceiver) {
std::string Message;
AvailabilityResult Result;
const NamedDecl* OffendingDecl;
// See if this declaration is unavailable, deprecated, or partial.
std::tie(Result, OffendingDecl) = ShouldDiagnoseAvailabilityOfDecl(D, &Message);
std::tie(Result, OffendingDecl) =
ShouldDiagnoseAvailabilityOfDecl(*this, D, &Message, ClassReceiver);
if (Result == AR_Available)
return;

Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Sema/SemaExpr.cpp
Expand Up @@ -206,7 +206,8 @@ void Sema::MaybeSuggestAddingStaticToDecl(const FunctionDecl *Cur) {
bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass,
bool ObjCPropertyAccess,
bool AvoidPartialAvailabilityChecks) {
bool AvoidPartialAvailabilityChecks,
ObjCInterfaceDecl *ClassReceiver) {
SourceLocation Loc = Locs.front();
if (getLangOpts().CPlusPlus && isa<FunctionDecl>(D)) {
// If there were any diagnostics suppressed by template argument deduction,
Expand Down Expand Up @@ -292,7 +293,7 @@ bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
}

DiagnoseAvailabilityOfDecl(D, Locs, UnknownObjCClass, ObjCPropertyAccess,
AvoidPartialAvailabilityChecks);
AvoidPartialAvailabilityChecks, ClassReceiver);

DiagnoseUnusedOfDecl(*this, D, Loc);

Expand Down
12 changes: 9 additions & 3 deletions clang/lib/Sema/SemaExprObjC.cpp
Expand Up @@ -2471,7 +2471,8 @@ ExprResult Sema::BuildClassMessage(TypeSourceInfo *ReceiverTypeInfo,
if (!Method)
Method = Class->lookupPrivateClassMethod(Sel);

if (Method && DiagnoseUseOfDecl(Method, SelectorSlotLocs))
if (Method && DiagnoseUseOfDecl(Method, SelectorSlotLocs,
nullptr, false, false, Class))
return ExprError();
}

Expand Down Expand Up @@ -2784,14 +2785,19 @@ ExprResult Sema::BuildInstanceMessage(Expr *Receiver,
} else {
if (ObjCMethodDecl *CurMeth = getCurMethodDecl()) {
if (ObjCInterfaceDecl *ClassDecl = CurMeth->getClassInterface()) {
// FIXME: Is this correct? Why are we assuming that a message to
// Class will call a method in the current interface?

// First check the public methods in the class interface.
Method = ClassDecl->lookupClassMethod(Sel);

if (!Method)
Method = ClassDecl->lookupPrivateClassMethod(Sel);

if (Method && DiagnoseUseOfDecl(Method, SelectorSlotLocs, nullptr,
false, false, ClassDecl))
return ExprError();
}
if (Method && DiagnoseUseOfDecl(Method, SelectorSlotLocs))
return ExprError();
}
if (!Method) {
// If not messaging 'self', look for any factory method named 'Sel'.
Expand Down
58 changes: 58 additions & 0 deletions clang/test/SemaObjC/infer-availability-from-init.m
@@ -0,0 +1,58 @@
// RUN: %clang_cc1 -triple x86_64-apple-macosx-10.9 -Wunguarded-availability -fblocks -fsyntax-only -verify %s

__attribute__((objc_root_class))
@interface NSObject
+(instancetype)new;
-(instancetype)init;
@end

@interface MyObject : NSObject
-(instancetype)init __attribute__((unavailable)); // expected-note{{'init' has been explicitly marked unavailable here}}
@end

void usemyobject() {
[MyObject new]; // expected-error{{'new' is unavailable}}
}

@interface MyOtherObject : NSObject
+(instancetype)init __attribute__((unavailable));
+(instancetype)new;
@end

void usemyotherobject() {
[MyOtherObject new]; // no error; new is overrideen.
}

@interface NotGoodOverride : NSObject
+(instancetype)init __attribute__((unavailable));
-(instancetype)new;
+(instancetype)new: (int)x;
@end

void usenotgoodoverride() {
[NotGoodOverride new]; // no error
}

@interface NotNSObject
+(instancetype)new;
-(instancetype)init;
@end

@interface NotMyObject : NotNSObject
-(instancetype)init __attribute__((unavailable));
@end

void usenotmyobject() {
[NotMyObject new]; // no error; this isn't NSObject
}

@interface FromSelf : NSObject
-(instancetype)init __attribute__((unavailable)); // expected-note {{'init' has been explicitly marked unavailable here}}
+(FromSelf*)another_one;
@end

@implementation FromSelf
+(FromSelf*)another_one {
[self new]; // expected-error{{'new' is unavailable}}
}
@end

0 comments on commit 4257857

Please sign in to comment.