Skip to content

[objc_direct] also go through implementations when looking for clashes #960

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 64 additions & 31 deletions clang/lib/Sema/SemaDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4636,6 +4636,62 @@ static void checkObjCMethodX86VectorTypes(Sema &SemaRef,
<< (Triple.isMacOSX() ? "macOS 10.11" : "iOS 9");
}

static void mergeObjCDirectMembers(Sema &S, Decl *CD, ObjCMethodDecl *Method) {
if (!Method->isDirectMethod() && !Method->hasAttr<UnavailableAttr>() &&
CD->hasAttr<ObjCDirectMembersAttr>()) {
Method->addAttr(
ObjCDirectAttr::CreateImplicit(S.Context, Method->getLocation()));
}
}

static void checkObjCDirectMethodClashes(Sema &S, ObjCInterfaceDecl *IDecl,
ObjCMethodDecl *Method,
ObjCImplDecl *ImpDecl = nullptr) {
auto Sel = Method->getSelector();
bool isInstance = Method->isInstanceMethod();
bool diagnosed = false;

auto diagClash = [&](const ObjCMethodDecl *IMD) {
if (diagnosed || IMD->isImplicit())
return;
if (Method->isDirectMethod() || IMD->isDirectMethod()) {
S.Diag(Method->getLocation(), diag::err_objc_direct_duplicate_decl)
<< Method->isDirectMethod() << /* method */ 0 << IMD->isDirectMethod()
<< Method->getDeclName();
S.Diag(IMD->getLocation(), diag::note_previous_declaration);
diagnosed = true;
}
};

// Look for any other declaration of this method anywhere we can see in this
// compilation unit.
//
// We do not use IDecl->lookupMethod() because we have specific needs:
//
// - we absolutely do not need to walk protocols, because
// diag::err_objc_direct_on_protocol has already been emitted
// during parsing if there's a conflict,
//
// - when we do not find a match in a given @interface container,
// we need to attempt looking it up in the @implementation block if the
// translation unit sees it to find more clashes.

if (auto *IMD = IDecl->getMethod(Sel, isInstance))
diagClash(IMD);
else if (auto *Impl = IDecl->getImplementation())
if (Impl != ImpDecl)
if (auto *IMD = IDecl->getImplementation()->getMethod(Sel, isInstance))
diagClash(IMD);

for (const auto *Cat : IDecl->visible_categories())
if (auto *IMD = Cat->getMethod(Sel, isInstance))
diagClash(IMD);
else if (auto CatImpl = Cat->getImplementation())
if (CatImpl != ImpDecl)
if (auto *IMD = Cat->getMethod(Sel, isInstance))
diagClash(IMD);
}

Decl *Sema::ActOnMethodDeclaration(
Scope *S, SourceLocation MethodLoc, SourceLocation EndLoc,
tok::TokenKind MethodType, ObjCDeclSpec &ReturnQT, ParsedType ReturnType,
Expand Down Expand Up @@ -4866,9 +4922,9 @@ Decl *Sema::ActOnMethodDeclaration(
Diag(ObjCMethod->getLocation(), diag::warn_dealloc_in_category)
<< ObjCMethod->getDeclName();
}
} else if (ImpDecl->hasAttr<ObjCDirectMembersAttr>()) {
ObjCMethod->addAttr(
ObjCDirectAttr::CreateImplicit(Context, ObjCMethod->getLocation()));
} else {
mergeObjCDirectMembers(*this, ClassDecl, ObjCMethod);
checkObjCDirectMethodClashes(*this, IDecl, ObjCMethod, ImpDecl);
}

// Warn if a method declared in a protocol to which a category or
Expand All @@ -4889,39 +4945,16 @@ Decl *Sema::ActOnMethodDeclaration(
}
} else {
if (!isa<ObjCProtocolDecl>(ClassDecl)) {
if (!ObjCMethod->isDirectMethod() &&
ClassDecl->hasAttr<ObjCDirectMembersAttr>()) {
ObjCMethod->addAttr(
ObjCDirectAttr::CreateImplicit(Context, ObjCMethod->getLocation()));
}
mergeObjCDirectMembers(*this, ClassDecl, ObjCMethod);

// There can be a single declaration in any @interface container
// for a given direct method, look for clashes as we add them.
//
// For valid code, we should always know the primary interface
// declaration by now, however for invalid code we'll keep parsing
// but we won't find the primary interface and IDecl will be nil.
ObjCInterfaceDecl *IDecl = dyn_cast<ObjCInterfaceDecl>(ClassDecl);
if (!IDecl)
IDecl = cast<ObjCCategoryDecl>(ClassDecl)->getClassInterface();

// For valid code, we should always know the primary interface
// declaration by now, however for invalid code we'll keep parsing
// but we won't find the primary interface and IDecl will be nil.
if (IDecl)
if (auto *IMD = IDecl->lookupMethod(ObjCMethod->getSelector(),
ObjCMethod->isInstanceMethod(),
/*shallowCategoryLookup=*/false,
/*followSuper=*/false)) {
if (isa<ObjCProtocolDecl>(IMD->getDeclContext())) {
// Do not emit a diagnostic for the Protocol case:
// diag::err_objc_direct_on_protocol has already been emitted
// during parsing for these with a nicer diagnostic.
} else if (ObjCMethod->isDirectMethod() || IMD->isDirectMethod()) {
Diag(ObjCMethod->getLocation(),
diag::err_objc_direct_duplicate_decl)
<< ObjCMethod->isDirectMethod() << /* method */ 0
<< IMD->isDirectMethod() << ObjCMethod->getDeclName();
Diag(IMD->getLocation(), diag::note_previous_declaration);
}
}
checkObjCDirectMethodClashes(*this, IDecl, ObjCMethod);
}

cast<DeclContext>(ClassDecl)->addDecl(ObjCMethod);
Expand Down
11 changes: 11 additions & 0 deletions clang/test/SemaObjC/method-direct-one-definition.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ @interface B (OtherCat)
- (void)B_OtherCat __attribute__((objc_direct)); // expected-note {{previous declaration is here}}
@end

@implementation B
- (void)B_primary {
}
- (void)B_extension {
}
- (void)B_implOnly __attribute__((objc_direct)) { // expected-note {{previous declaration is here}}
}
@end

@implementation B (Cat)
- (void)B_primary { // expected-error {{direct method was declared in the primary interface but is implemented in a category}}
}
Expand All @@ -39,6 +48,8 @@ - (void)B_Cat {
}
- (void)B_OtherCat { // expected-error {{direct method was declared in a category but is implemented in a different category}}
}
- (void)B_implOnly __attribute__((objc_direct)) { // expected-error {{direct method declaration conflicts with previous direct declaration of method 'B_implOnly'}}
}
@end

__attribute__((objc_root_class))
Expand Down
4 changes: 4 additions & 0 deletions clang/test/SemaObjC/method-direct.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ + (void)classProtoMethod __attribute__((objc_direct)); // expected-error {{'objc

__attribute__((objc_root_class))
@interface Root
- (void)unavailableInChild;
- (void)rootRegular; // expected-note {{previous declaration is here}}
+ (void)classRootRegular; // expected-note {{previous declaration is here}}
- (void)rootDirect __attribute__((objc_direct)); // expected-note {{previous declaration is here}};
Expand Down Expand Up @@ -52,6 +53,7 @@ + (void)classRootCategoryDirect2 __attribute__((objc_direct)); // expected-note
__attribute__((objc_direct_members))
@interface SubDirectMembers : Root
@property int foo; // expected-note {{previous declaration is here}}
- (void)unavailableInChild __attribute__((unavailable)); // should not warn
- (instancetype)init;
@end

Expand Down Expand Up @@ -81,6 +83,8 @@ + (void)classRootCategoryDirect2; // expected-error {{cannot override a method

__attribute__((objc_direct_members))
@implementation Root
- (void)unavailableInChild {
}
- (void)rootRegular {
}
+ (void)classRootRegular {
Expand Down