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:
Svyatoslav Scherbina
2017-08-29 16:48:02 +03:00
committed by SvyatoslavScherbina
parent 324f54ba1e
commit 12df524138
5 changed files with 72 additions and 28 deletions
@@ -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<FunctionDescriptor>()
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
}
}
fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
descriptor.name.asString()
} else {
@@ -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"
@@ -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<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? {
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<ConstValue> =
private fun ClassDescriptor.generateOverridingMethodDescs(): List<ObjCMethodDesc> =
this.unsubstitutedMemberScope.contributedMethods.filter {
it.kind.isReal && it !is ConstructorDescriptor
}.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
private fun IrClass.generateImpMethodDescs(): List<ConstValue> = this.declarations
private fun IrClass.generateImpMethodDescs(): List<ObjCMethodDesc> = this.declarations
.filterIsInstance<IrSimpleFunction>()
.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
)
}
}
@@ -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<FunctionDescriptor>()
.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)!! }
+24
View File
@@ -19,6 +19,7 @@
#if KONAN_OBJC_INTEROP
#import <objc/runtime.h>
#import <Foundation/NSException.h>
#import <Foundation/NSString.h>
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