diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt index 8ab5a800dfc..ef1ab57a292 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition import org.jetbrains.kotlin.resolve.descriptorUtil.* @@ -163,6 +164,15 @@ class ObjCOverridabilityCondition : ExternalOverridabilityCondition { } +fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? { + return this.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let { + val initSelector = it.getStringValue("initSelector") + this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence() + .filterIsInstance() + .single { it.getExternalObjCMethodInfo()?.selector == initSelector } + } +} + fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) { descriptor.name.asString() } else { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 6e5408066ef..a3158c5acbc 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -328,6 +328,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") } val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") } + val missingInitImp by lazy { importRtFunction("MissingInitImp") } private val personalityFunctionName = when (context.config.targetManager.target) { KonanTarget.MINGW -> "__gxx_personality_seh0" diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt index 02b82aa97e8..692a01fe161 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt @@ -17,12 +17,11 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.LLVMStoreSizeOfType -import org.jetbrains.kotlin.backend.konan.Context -import org.jetbrains.kotlin.backend.konan.ObjCMethodInfo +import llvm.LLVMValueRef +import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue import org.jetbrains.kotlin.backend.konan.descriptors.getStringValueOrNull -import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.isFinalClass @@ -39,7 +38,7 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con val objCLLvmDeclarations = context.llvmDeclarations.forClass(descriptor).objCDeclarations!! - val instanceMethods = descriptor.generateOverridingMethodDescs() + irClass.generateImpMethodDescs() + val instanceMethods = generateInstanceMethodDescs(irClass) val companionObjectDescriptor = descriptor.companionObjectDescriptor val classMethods = companionObjectDescriptor?.generateOverridingMethodDescs() ?: emptyList() @@ -80,6 +79,25 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0)) } + private fun generateInstanceMethodDescs( + irClass: IrClass + ): List = mutableListOf().apply { + val descriptor = irClass.descriptor + addAll(descriptor.generateOverridingMethodDescs()) + addAll(irClass.generateImpMethodDescs()) + val allImplementedSelectors = this.map { it.selector }.toSet() + + assert(descriptor.getSuperClassNotAny()!!.isExternalObjCClass()) + val allInitMethodsInfo = descriptor.getSuperClassNotAny()!!.constructors + .mapNotNull { it.getObjCInitMethod()?.getExternalObjCMethodInfo() } + .filter { it.selector !in allImplementedSelectors } + .distinctBy { it.selector } + + allInitMethodsInfo.mapTo(this) { + ObjCMethodDesc(it.selector, it.encoding, context.llvm.missingInitImp) + } + } + private fun selectClassName(descriptor: ClassDescriptor): String? { val exportObjCClassAnnotation = descriptor.annotations.findAnnotation(context.interopBuiltIns.exportObjCClass.fqNameSafe) @@ -93,37 +111,37 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con } } - private fun createMethodDesc(selector: String, encoding: String, imp: ConstPointer) = Struct( + private inner class ObjCMethodDesc( + val selector: String, val encoding: String, val impFunction: LLVMValueRef + ) : Struct( runtime.objCMethodDescription, staticData.cStringLiteral(selector), staticData.cStringLiteral(encoding), - imp + constPointer(impFunction).bitcast(int8TypePtr) ) - private fun generateMethodDesc(info: ObjCMethodInfo): ConstValue { - return createMethodDesc( - info.selector, - info.encoding, - constPointer(context.llvm.externalFunction(info.imp, functionType(voidType))).bitcast(int8TypePtr) - ) - } + private fun generateMethodDesc(info: ObjCMethodInfo) = ObjCMethodDesc( + info.selector, + info.encoding, + context.llvm.externalFunction(info.imp, functionType(voidType)) + ) - private fun ClassDescriptor.generateOverridingMethodDescs(): List = + private fun ClassDescriptor.generateOverridingMethodDescs(): List = this.unsubstitutedMemberScope.contributedMethods.filter { it.kind.isReal && it !is ConstructorDescriptor }.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) } - private fun IrClass.generateImpMethodDescs(): List = this.declarations + private fun IrClass.generateImpMethodDescs(): List = this.declarations .filterIsInstance() .mapNotNull { val annotation = it.descriptor.annotations.findAnnotation(context.interopBuiltIns.objCMethodImp.fqNameSafe) ?: return@mapNotNull null - createMethodDesc( + ObjCMethodDesc( annotation.getStringValue("selector"), annotation.getStringValue("encoding"), - it.descriptor.entryPointAddress + it.descriptor.llvmFunction ) } } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 5b3a688aeb8..29d38dbd1da 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -389,7 +389,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass) - val initMethod = getObjCInitMethod(expression.descriptor)!! + val initMethod = expression.descriptor.getObjCInitMethod()!! val initMethodInfo = initMethod.getExternalObjCMethodInfo()!! assert(expression.dispatchReceiver == null) @@ -438,22 +438,13 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme } } - private fun getObjCInitMethod(descriptor: ConstructorDescriptor): FunctionDescriptor? { - return descriptor.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let { - val initSelector = it.getStringValue("initSelector") - descriptor.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence() - .filterIsInstance() - .single { it.getExternalObjCMethodInfo()?.selector == initSelector } - } - } - override fun visitCall(expression: IrCall): IrExpression { expression.transformChildrenVoid() val descriptor = expression.descriptor.original if (descriptor is ConstructorDescriptor) { - val initMethod = getObjCInitMethod(descriptor) + val initMethod = descriptor.getObjCInitMethod() if (initMethod != null) { val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! } diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index c3c23d0cac9..02b5d0832db 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -19,6 +19,7 @@ #if KONAN_OBJC_INTEROP #import +#import #import namespace { @@ -34,6 +35,11 @@ namespace { } return result; } + + // Note: using @"foo" string literals leads to linkage dependency on frameworks. + NSString* cStringToNS(const char* str) { + return [getNSStringClass() stringWithCString:str encoding:NSUTF8StringEncoding]; + } } extern "C" { @@ -67,6 +73,24 @@ OBJ_GETTER(CreateKStringFromNSString, NSString* str) { RETURN_OBJ(result->obj()); } +// Note: this body is used for init methods with signatures differing from this; +// it is correct on arm64 and x86_64, because the body uses only the first two arguments which are fixed, +// and returns pointers. +id MissingInitImp(id self, SEL _cmd) { + const char* className = object_getClassName(self); + [self release]; // Since init methods receive ownership on the receiver. + + // Lookup dynamically to avoid direct reference to Foundation: + Class nsExceptionClass = objc_getClass("NSException"); + RuntimeAssert(nsExceptionClass != nullptr, "NSException class not found"); + + [nsExceptionClass raise:cStringToNS("Initializer is not implemented") + format:cStringToNS("%s is not implemented in %s"), + sel_getName(_cmd), className]; + + return nullptr; +} + } // extern "C" #else // KONAN_OBJC_INTEROP