Disable inheriting Objective-C initializers by Kotlin classes
Unoverridden inherited Objective-C initializer can lead to creating uninitialized Kotlin instances. Throw exception when such an initializer gets called.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
324f54ba1e
commit
12df524138
+10
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.backend.konan
|
|||||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
|
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
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<FunctionDescriptor>()
|
||||||
|
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
|
fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
|
||||||
descriptor.name.asString()
|
descriptor.name.asString()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
@@ -328,6 +328,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
|||||||
|
|
||||||
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
||||||
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
|
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
|
||||||
|
val missingInitImp by lazy { importRtFunction("MissingInitImp") }
|
||||||
|
|
||||||
private val personalityFunctionName = when (context.config.targetManager.target) {
|
private val personalityFunctionName = when (context.config.targetManager.target) {
|
||||||
KonanTarget.MINGW -> "__gxx_personality_seh0"
|
KonanTarget.MINGW -> "__gxx_personality_seh0"
|
||||||
|
|||||||
+35
-17
@@ -17,12 +17,11 @@
|
|||||||
package org.jetbrains.kotlin.backend.konan.llvm
|
package org.jetbrains.kotlin.backend.konan.llvm
|
||||||
|
|
||||||
import llvm.LLVMStoreSizeOfType
|
import llvm.LLVMStoreSizeOfType
|
||||||
import org.jetbrains.kotlin.backend.konan.Context
|
import llvm.LLVMValueRef
|
||||||
import org.jetbrains.kotlin.backend.konan.ObjCMethodInfo
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods
|
import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValueOrNull
|
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.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.isFinalClass
|
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 objCLLvmDeclarations = context.llvmDeclarations.forClass(descriptor).objCDeclarations!!
|
||||||
|
|
||||||
val instanceMethods = descriptor.generateOverridingMethodDescs() + irClass.generateImpMethodDescs()
|
val instanceMethods = generateInstanceMethodDescs(irClass)
|
||||||
|
|
||||||
val companionObjectDescriptor = descriptor.companionObjectDescriptor
|
val companionObjectDescriptor = descriptor.companionObjectDescriptor
|
||||||
val classMethods = companionObjectDescriptor?.generateOverridingMethodDescs() ?: emptyList()
|
val classMethods = companionObjectDescriptor?.generateOverridingMethodDescs() ?: emptyList()
|
||||||
@@ -80,6 +79,25 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
|||||||
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
|
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateInstanceMethodDescs(
|
||||||
|
irClass: IrClass
|
||||||
|
): List<ObjCMethodDesc> = mutableListOf<ObjCMethodDesc>().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? {
|
private fun selectClassName(descriptor: ClassDescriptor): String? {
|
||||||
val exportObjCClassAnnotation =
|
val exportObjCClassAnnotation =
|
||||||
descriptor.annotations.findAnnotation(context.interopBuiltIns.exportObjCClass.fqNameSafe)
|
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,
|
runtime.objCMethodDescription,
|
||||||
staticData.cStringLiteral(selector),
|
staticData.cStringLiteral(selector),
|
||||||
staticData.cStringLiteral(encoding),
|
staticData.cStringLiteral(encoding),
|
||||||
imp
|
constPointer(impFunction).bitcast(int8TypePtr)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun generateMethodDesc(info: ObjCMethodInfo): ConstValue {
|
private fun generateMethodDesc(info: ObjCMethodInfo) = ObjCMethodDesc(
|
||||||
return createMethodDesc(
|
info.selector,
|
||||||
info.selector,
|
info.encoding,
|
||||||
info.encoding,
|
context.llvm.externalFunction(info.imp, functionType(voidType))
|
||||||
constPointer(context.llvm.externalFunction(info.imp, functionType(voidType))).bitcast(int8TypePtr)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ClassDescriptor.generateOverridingMethodDescs(): List<ConstValue> =
|
private fun ClassDescriptor.generateOverridingMethodDescs(): List<ObjCMethodDesc> =
|
||||||
this.unsubstitutedMemberScope.contributedMethods.filter {
|
this.unsubstitutedMemberScope.contributedMethods.filter {
|
||||||
it.kind.isReal && it !is ConstructorDescriptor
|
it.kind.isReal && it !is ConstructorDescriptor
|
||||||
}.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
|
}.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
|
||||||
|
|
||||||
private fun IrClass.generateImpMethodDescs(): List<ConstValue> = this.declarations
|
private fun IrClass.generateImpMethodDescs(): List<ObjCMethodDesc> = this.declarations
|
||||||
.filterIsInstance<IrSimpleFunction>()
|
.filterIsInstance<IrSimpleFunction>()
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
val annotation =
|
val annotation =
|
||||||
it.descriptor.annotations.findAnnotation(context.interopBuiltIns.objCMethodImp.fqNameSafe) ?:
|
it.descriptor.annotations.findAnnotation(context.interopBuiltIns.objCMethodImp.fqNameSafe) ?:
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
|
||||||
createMethodDesc(
|
ObjCMethodDesc(
|
||||||
annotation.getStringValue("selector"),
|
annotation.getStringValue("selector"),
|
||||||
annotation.getStringValue("encoding"),
|
annotation.getStringValue("encoding"),
|
||||||
it.descriptor.entryPointAddress
|
it.descriptor.llvmFunction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-11
@@ -389,7 +389,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
|||||||
|
|
||||||
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
|
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
|
||||||
|
|
||||||
val initMethod = getObjCInitMethod(expression.descriptor)!!
|
val initMethod = expression.descriptor.getObjCInitMethod()!!
|
||||||
val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
|
val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
|
||||||
|
|
||||||
assert(expression.dispatchReceiver == null)
|
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<FunctionDescriptor>()
|
|
||||||
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitCall(expression: IrCall): IrExpression {
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
expression.transformChildrenVoid()
|
expression.transformChildrenVoid()
|
||||||
|
|
||||||
val descriptor = expression.descriptor.original
|
val descriptor = expression.descriptor.original
|
||||||
|
|
||||||
if (descriptor is ConstructorDescriptor) {
|
if (descriptor is ConstructorDescriptor) {
|
||||||
val initMethod = getObjCInitMethod(descriptor)
|
val initMethod = descriptor.getObjCInitMethod()
|
||||||
|
|
||||||
if (initMethod != null) {
|
if (initMethod != null) {
|
||||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#if KONAN_OBJC_INTEROP
|
#if KONAN_OBJC_INTEROP
|
||||||
|
|
||||||
#import <objc/runtime.h>
|
#import <objc/runtime.h>
|
||||||
|
#import <Foundation/NSException.h>
|
||||||
#import <Foundation/NSString.h>
|
#import <Foundation/NSString.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -34,6 +35,11 @@ namespace {
|
|||||||
}
|
}
|
||||||
return result;
|
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" {
|
extern "C" {
|
||||||
@@ -67,6 +73,24 @@ OBJ_GETTER(CreateKStringFromNSString, NSString* str) {
|
|||||||
RETURN_OBJ(result->obj());
|
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"
|
} // extern "C"
|
||||||
|
|
||||||
#else // KONAN_OBJC_INTEROP
|
#else // KONAN_OBJC_INTEROP
|
||||||
|
|||||||
Reference in New Issue
Block a user