Skip to content

[IRGen] Call objc_direct methods correctly #33349

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
Oct 30, 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
13 changes: 12 additions & 1 deletion lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//
//===----------------------------------------------------------------------===//

#include "clang/AST/DeclObjC.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTVisitor.h"
Expand Down Expand Up @@ -1837,6 +1838,17 @@ AnyObjectLookupRequest::evaluate(Evaluator &evaluator, const DeclContext *dc,
if (!decl->isObjC())
continue;

// If the declaration is objc_direct, it cannot be called dynamically.
if (auto clangDecl = decl->getClangDecl()) {
if (auto objCMethod = dyn_cast<clang::ObjCMethodDecl>(clangDecl)) {
if (objCMethod->isDirectMethod())
continue;
} else if (auto objCProperty = dyn_cast<clang::ObjCPropertyDecl>(clangDecl)) {
if (objCProperty->isDirectProperty())
continue;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an interesting question whether suppressing this in lookup is the right thing to do, as opposed to diagnosing the attempt during type-checking. I've asked @DougGregor and @brentdax to weigh in here. Naively I'd think we should diagnose during type-checking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is effectively a choice between emitting a generic and slightly incorrect "no method found"-type diagnostic, and emitting a tailored "direct methods cannot be called through AnyObject dispatch"-type diagnostic.

I think it'd be better to emit a tailored diagnostic, but it could be left out if that would be very difficult, or it could be emitted based on a separate lookup performed while diagnosing the error. Please make sure you include a test case where one class has a direct method and another has a normal method by the same name—we'll want to make sure that the direct method doesn't stop you from using the normal one through AnyObject dispatch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree that this is a very unhelpful error message for developers who might be surprised to see a "no method found"-type message after adding an objc_direct attribute. I'm not very familiar with this part of the codebase, where would we put the lookup to diagnose the error?

And thanks for the test case, I'll add that.


// If the declaration has an override, name lookup will also have
// found the overridden method. Skip this declaration, because we
// prefer the overridden method.
Expand All @@ -1848,7 +1860,6 @@ AnyObjectLookupRequest::evaluate(Evaluator &evaluator, const DeclContext *dc,

// If we didn't see this declaration before, and it's an acceptable
// result, add it to the list.
// declaration to the list.
if (knownDecls.insert(decl).second &&
isAcceptableLookupResult(dc, options, decl,
/*onlyCompleteObjectInits=*/false))
Expand Down
27 changes: 19 additions & 8 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4537,7 +4537,8 @@ namespace {
bodyParams, resultTy,
async, throws, dc, decl);

result->setAccess(getOverridableAccessLevel(dc));
result->setAccess(decl->isDirectMethod() ? AccessLevel::Public
: getOverridableAccessLevel(dc));

// Optional methods in protocols.
if (decl->getImplementationControl() == clang::ObjCMethodDecl::Optional &&
Expand Down Expand Up @@ -5289,8 +5290,9 @@ namespace {
}

auto type = importedType.getType();
auto result = Impl.createDeclWithClangNode<VarDecl>(decl,
getOverridableAccessLevel(dc),
const auto access = decl->isDirectProperty() ? AccessLevel::Public
: getOverridableAccessLevel(dc);
auto result = Impl.createDeclWithClangNode<VarDecl>(decl, access,
/*IsStatic*/decl->isClassProperty(), VarDecl::Introducer::Var,
Impl.importSourceLoc(decl->getLocation()), name, dc);
result->setInterfaceType(type);
Expand Down Expand Up @@ -6981,7 +6983,13 @@ SwiftDeclConverter::importSubscript(Decl *decl,
bodyParams, decl->getLoc(),
elementTy, dc,
getter->getClangNode());
const auto access = getOverridableAccessLevel(dc);

bool IsObjCDirect = false;
if (auto objCDecl = dyn_cast<clang::ObjCMethodDecl>(getter->getClangDecl())) {
IsObjCDirect = objCDecl->isDirectMethod();
}
const auto access = IsObjCDirect ? AccessLevel::Public
: getOverridableAccessLevel(dc);
subscript->setAccess(access);
subscript->setSetterAccess(access);

Expand Down Expand Up @@ -7835,10 +7843,13 @@ void ClangImporter::Implementation::importAttributes(

if (auto method = dyn_cast<clang::ObjCMethodDecl>(ClangDecl)) {
if (method->isDirectMethod() && !AnyUnavailable) {
auto attr = AvailableAttr::createPlatformAgnostic(
C, "", "", PlatformAgnosticAvailabilityKind::UnavailableInSwift);
MappedDecl->getAttrs().add(attr);
AnyUnavailable = true;
assert(isa<AbstractFunctionDecl>(MappedDecl) &&
"objc_direct declarations are expected to be an AbstractFunctionDecl");
MappedDecl->getAttrs().add(new (C) FinalAttr(/*IsImplicit=*/true));
if (auto accessorDecl = dyn_cast<AccessorDecl>(MappedDecl)) {
auto attr = new (C) FinalAttr(/*isImplicit=*/true);
accessorDecl->getStorage()->getAttrs().add(attr);
}
}
}

Expand Down
16 changes: 11 additions & 5 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2860,12 +2860,18 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(
// the insert-before point.
llvm::Constant *clangAddr = nullptr;
if (auto clangDecl = f->getClangDecl()) {
auto globalDecl = getClangGlobalDeclForFunction(clangDecl);
clangAddr = getAddrOfClangGlobalDecl(globalDecl, forDefinition);
// If we have an Objective-C Clang declaration, it must be a direct
// method and we want to generate the IR declaration ourselves.
if (auto objcDecl = dyn_cast<clang::ObjCMethodDecl>(clangDecl)) {
assert(objcDecl->isDirectMethod());
} else {
auto globalDecl = getClangGlobalDeclForFunction(clangDecl);
clangAddr = getAddrOfClangGlobalDecl(globalDecl, forDefinition);

if (auto ctor = dyn_cast<clang::CXXConstructorDecl>(clangDecl)) {
clangAddr =
emitCXXConstructorThunkIfNeeded(*this, f, ctor, entity, clangAddr);
if (auto ctor = dyn_cast<clang::CXXConstructorDecl>(clangDecl)) {
clangAddr =
emitCXXConstructorThunkIfNeeded(*this, f, ctor, entity, clangAddr);
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions lib/IRGen/GenObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,14 @@ Callee irgen::getObjCMethodCallee(IRGenFunction &IGF,
return Callee(std::move(info), fn, receiverValue, selectorValue);
}

Callee irgen::getObjCDirectMethodCallee(CalleeInfo &&info, const FunctionPointer &fn,
llvm::Value *selfValue) {
// Direct calls to Objective-C methods have a selector value of `undef`.
auto selectorType = fn.getFunctionType()->getParamType(1);
auto selectorValue = llvm::UndefValue::get(selectorType);
return Callee(std::move(info), fn, selfValue, selectorValue);
}

/// Call [self allocWithZone: nil].
llvm::Value *irgen::emitObjCAllocObjectCall(IRGenFunction &IGF,
llvm::Value *self,
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/GenObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ namespace irgen {
Callee getObjCMethodCallee(IRGenFunction &IGF, const ObjCMethod &method,
llvm::Value *selfValue, CalleeInfo &&info);

/// Prepare a callee for an Objective-C method with the `objc_direct` attribute.
Callee getObjCDirectMethodCallee(CalleeInfo &&info, const FunctionPointer &fn,
llvm::Value *selfValue);

/// Emit a partial application of an Objective-C method to its 'self'
/// argument.
void emitObjCPartialApplication(IRGenFunction &IGF,
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2524,6 +2524,10 @@ Callee LoweredValue::getCallee(IRGenFunction &IGF,
switch (kind) {
case Kind::FunctionPointer: {
auto &fn = getFunctionPointer();
if (calleeInfo.OrigFnType->getRepresentation() ==
SILFunctionTypeRepresentation::ObjCMethod) {
return getObjCDirectMethodCallee(std::move(calleeInfo), fn, selfValue);
}
return Callee(std::move(calleeInfo), fn, selfValue);
}

Expand Down
11 changes: 11 additions & 0 deletions lib/SIL/IR/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/SIL/SILLinkage.h"
#include "swift/SIL/SILLocation.h"
#include "swift/SILOptimizer/Utils/SpecializationMangler.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
Expand Down Expand Up @@ -712,6 +713,16 @@ std::string SILDeclRef::mangle(ManglingKind MKind) const {
return SS.str();
}
return namedClangDecl->getName().str();
} else if (auto objcDecl = dyn_cast<clang::ObjCMethodDecl>(clangDecl)) {
if (objcDecl->isDirectMethod()) {
std::string storage;
llvm::raw_string_ostream SS(storage);
clang::ASTContext &ctx = clangDecl->getASTContext();
std::unique_ptr<clang::MangleContext> mangler(ctx.createMangleContext());
mangler->mangleObjCMethodName(objcDecl, SS, /*includePrefixByte=*/true,
/*includeCategoryNamespace=*/false);
return SS.str();
}
}
}
}
Expand Down
22 changes: 20 additions & 2 deletions lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "swift/SIL/SILArgument.h"
#include "clang/AST/DeclCXX.h"
#include "llvm/Support/Compiler.h"
#include "clang/AST/DeclObjC.h"

using namespace swift;
using namespace Lowering;
Expand Down Expand Up @@ -1059,7 +1060,18 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
auto constant = SILDeclRef(afd).asForeign(requiresForeignEntryPoint(afd));

auto subs = e->getDeclRef().getSubstitutions();
setCallee(Callee::forClassMethod(SGF, constant, subs, e));

bool isObjCDirect = false;
if (auto objcDecl = dyn_cast_or_null<clang::ObjCMethodDecl>(
afd->getClangDecl())) {
isObjCDirect = objcDecl->isDirectMethod();
}

if (isObjCDirect) {
setCallee(Callee::forDirect(SGF, constant, subs, e));
} else {
setCallee(Callee::forClassMethod(SGF, constant, subs, e));
}
}

//
Expand Down Expand Up @@ -5112,8 +5124,14 @@ static Callee getBaseAccessorFunctionRef(SILGenFunction &SGF,
}
}

bool isObjCDirect = false;
if (auto objcDecl = dyn_cast_or_null<clang::ObjCMethodDecl>(
decl->getClangDecl())) {
isObjCDirect = objcDecl->isDirectMethod();
}

// Dispatch in a struct/enum or to a final method is always direct.
if (!isClassDispatch)
if (!isClassDispatch || isObjCDirect)
return Callee::forDirect(SGF, constant, subs, loc);

// Otherwise, if we have a non-final class dispatch to a normal method,
Expand Down
12 changes: 12 additions & 0 deletions lib/Sema/LookupVisibleDecls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//===----------------------------------------------------------------------===//

#include "TypeChecker.h"
#include "clang/AST/DeclObjC.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/GenericSignature.h"
#include "swift/AST/GenericSignatureBuilder.h"
Expand Down Expand Up @@ -314,6 +315,17 @@ static void doDynamicLookup(VisibleDeclConsumer &Consumer,
if (!D->isObjC())
return;

// If the declaration is objc_direct, it cannot be called dynamically.
if (auto clangDecl = D->getClangDecl()) {
if (auto objCMethod = dyn_cast<clang::ObjCMethodDecl>(clangDecl)) {
if (objCMethod->isDirectMethod())
return;
} else if (auto objCProperty = dyn_cast<clang::ObjCPropertyDecl>(clangDecl)) {
if (objCProperty->isDirectProperty())
return;
}
}

if (D->isRecursiveValidation())
return;

Expand Down
12 changes: 0 additions & 12 deletions test/ClangImporter/Inputs/objc_direct.h

This file was deleted.

29 changes: 21 additions & 8 deletions test/ClangImporter/objc_direct.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
// RUN: %target-swift-frontend-verify -typecheck %s -enable-objc-interop -import-objc-header %S/Inputs/objc_direct.h -verify-ignore-unknown
// RUN: %target-swift-frontend-verify -typecheck %s -enable-objc-interop -import-objc-header %S/../Inputs/objc_direct.h

// REQUIRES: objc_interop

func callThingsOnBar(_ x: Bar) {
let _ = x.directProperty // expected-error {{getter for 'directProperty' is unavailable in Swift}}
let _ = x.directProperty2 // expected-error {{getter for 'directProperty2' is unavailable in Swift}}
var something = Bar() as AnyObject

x.directMethod() // expected-error {{'directMethod()' is unavailable in Swift}}
x.directMethod2() // expected-error {{'directMethod2()' is unavailable in Swift}}
something.directProperty = 123 // expected-error {{value of type 'AnyObject' has no member 'directProperty'}}
let _ = something.directProperty // expected-error {{value of type 'AnyObject' has no member 'directProperty'}}

Bar.directClassMethod() // expected-error {{'directClassMethod()' is unavailable in Swift}}
Bar.directClassMethod2() // expected-error {{'directClassMethod2()' is unavailable in Swift}}
something.directProperty2 = 456 // expected-error {{value of type 'AnyObject' has no member 'directProperty2'}}
let _ = something.directProperty2 // expected-error {{value of type 'AnyObject' has no member 'directProperty2'}}

let _ = something.directMethod() // expected-error {{value of type 'AnyObject' has no member 'directMethod'}}

let _ = something.directMethod2() // expected-error {{value of type 'AnyObject' has no member 'directMethod2'}}

class Foo {
// Test that we can use a method with the same name as some `objc_direct` method.
@objc func directProtocolMethod() -> String {
"This is not a direct method."
}
}

var otherthing = Foo() as AnyObject

// We expect no error.
let _ = otherthing.directProtocolMethod()
66 changes: 66 additions & 0 deletions test/IRGen/objc_direct.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// RUN: %target-swift-emit-ir -import-objc-header %S/../Inputs/objc_direct.h -o - %s | %FileCheck %s

// REQUIRES: objc_interop

func markUsed<T>(_ t: T) {}

protocol BarProtocol {
func directProtocolMethod() -> String!
}

extension Bar: BarProtocol {}

let bar = Bar()

bar.directProperty = 123
// CHECK: call void @"\01-[Bar setDirectProperty:]"({{.*}}, i8* undef, i32 {{.*}})

markUsed(bar.directProperty)
// CHECK: call i32 @"\01-[Bar directProperty]"({{.*}}, i8* undef)

bar.directProperty2 = 456
// CHECK: call void @"\01-[Bar setDirectProperty2:]"({{.*}}, i8* undef, i32 {{.*}})

markUsed(bar.directProperty2)
// CHECK: call i32 @"\01-[Bar directProperty2]"({{.*}}, i8* undef)

bar[0] = 789
// CHECK: call void @"\01-[Bar setObject:atIndexedSubscript:]"({{.*}}, i8* undef, i32 789, i32 0)

markUsed(bar[0])
// CHECK: call i32 @"\01-[Bar objectAtIndexedSubscript:]"({{.*}}, i8* undef, i32 0)

markUsed(bar.directMethod())
// CHECK: call {{.*}} @"\01-[Bar directMethod]"({{.*}}, i8* undef)

markUsed(bar.directMethod2())
// CHECK: call {{.*}} @"\01-[Bar directMethod2]"({{.*}}, i8* undef)

markUsed(Bar.directClassMethod())
// NOTE: The class must be realized before calling objc_direct class methods, even if
// Swift avoids explicit class realization before calling regular class methods.
// CHECK: [[R0:%.*]] = load %objc_class*, %objc_class** @"OBJC_CLASS_REF_$_Bar"
// CHECK: [[R1:%.*]] = call %objc_class* @swift_getInitializedObjCClass(%objc_class* [[R0]])
// CHECK: [[R2:%.*]] = bitcast %objc_class* [[R1]] to i8*
// CHECK: call {{.*}} @"\01+[Bar directClassMethod]"(i8* [[R2]], i8* undef)

markUsed(Bar.directClassMethod2())
// CHECK: [[R3:%.*]] = load %objc_class*, %objc_class** @"OBJC_CLASS_REF_$_Bar"
// CHECK: [[R4:%.*]] = call %objc_class* @swift_getInitializedObjCClass(%objc_class* [[R3]])
// CHECK: [[R5:%.*]] = bitcast %objc_class* [[R4]] to i8*
// CHECK: call {{.*}} @"\01+[Bar directClassMethod2]"(i8* [[R5]], i8* undef)

markUsed(bar.directProtocolMethod())
// CHECK: call {{.*}} @"\01-[Bar directProtocolMethod]"({{.*}}, i8* undef)

// CHECK-DAG: declare i32 @"\01-[Bar directProperty]"
// CHECK-DAG: declare void @"\01-[Bar setDirectProperty:]"
// CHECK-DAG: declare i32 @"\01-[Bar directProperty2]"
// CHECK-DAG: declare void @"\01-[Bar setDirectProperty2:]"
// CHECK-DAG: declare void @"\01-[Bar setObject:atIndexedSubscript:]"
// CHECK-DAG: declare i32 @"\01-[Bar objectAtIndexedSubscript:]"
// CHECK-DAG: declare {{.*}} @"\01-[Bar directMethod]"
// CHECK-DAG: declare {{.*}} @"\01-[Bar directMethod2]"
// CHECK-DAG: declare {{.*}} @"\01+[Bar directClassMethod]"
// CHECK-DAG: declare {{.*}} @"\01+[Bar directClassMethod2]"
// CHECK-DAG: declare {{.*}} @"\01-[Bar directProtocolMethod]"
2 changes: 1 addition & 1 deletion test/Index/Inputs/cross_language.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ @interface MyCls1(ext_in_objc)
// CHECK: [[@LINE-1]]:12 | class/Swift | MyCls1 | [[MyCls1_USR]] |
// CHECK: [[@LINE-2]]:19 | extension/ObjC | ext_in_objc | c:@M@cross_language@objc(cy)MyCls1@ext_in_objc |
-(void)someMethFromObjC;
// CHECK: [[@LINE-1]]:8 | instance-method/ObjC | someMethFromObjC | [[someMethFromObjC_USR:.*]] | -[ext_in_objc someMethFromObjC]
// CHECK: [[@LINE-1]]:8 | instance-method/ObjC | someMethFromObjC | [[someMethFromObjC_USR:.*]] | -[MyCls1(ext_in_objc) someMethFromObjC]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjmccall I want to draw your attention to this test I fixed that breaks when using swiftlang/llvm-project#2028.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@end

void test1() {
Expand Down
17 changes: 17 additions & 0 deletions test/Inputs/objc_direct.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>

@interface Bar : NSObject
@property(direct) int directProperty;
- (int)objectAtIndexedSubscript:(int)i __attribute__((objc_direct));
- (void)setObject:(int)obj atIndexedSubscript:(int)i __attribute__((objc_direct));
- (NSString *)directMethod __attribute__((objc_direct));
+ (NSString *)directClassMethod __attribute__((objc_direct));
- (NSString *)directProtocolMethod __attribute__((objc_direct));
@end

__attribute__((objc_direct_members))
@interface Bar(CategoryName)
@property int directProperty2;
- (NSString *)directMethod2;
+ (NSString *)directClassMethod2;
@end
Loading