Use ForeignException wrapper to handle native exception [KT-35056] (#4307)
* Use ForeignException wrapper to handle native exception [KT-35056] * Add test * Use Any? instead of NativePtr for automagic memory management * Use clauseNSException specific clause, similar to `catch (NSException* e) * cleanup + test fix * Terminate on anything else but NSException * Use ForeignException message for NSException details * Code generated for AppleFamily only + move stuff to kotlinx.cinterop package * Cleanup * Fix platform-dependent code generation * Fix test allocException * wip: ForeignExceptionMode option: cinterop part * ForeignExceptionMode support in compiler * rework test for foreignExceptionMode option * More tests on foreignExceptionMode option * PR feedback: resolveFakeOverride and more * Test reworked and extended Co-authored-by: Vladimir Ivanov <vladimir.d.ivanov@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package kotlinx.cinterop
|
||||
|
||||
import kotlin.native.internal.ExportForCppRuntime
|
||||
|
||||
public class ForeignException internal constructor(val nativeException: Any?): Exception() {
|
||||
override val message: String = nativeException?.let {
|
||||
kotlin_ObjCExport_ExceptionDetails(nativeException)
|
||||
}?: ""
|
||||
|
||||
// Current implementation expects NSException type only, which is ensured by CodeGenerator.
|
||||
@SymbolName("Kotlin_ObjCExport_ExceptionDetails")
|
||||
private external fun kotlin_ObjCExport_ExceptionDetails(nativeException: Any): String?
|
||||
}
|
||||
|
||||
@ExportForCppRuntime
|
||||
internal fun CreateForeignException(payload: NativePtr): Throwable
|
||||
= ForeignException(interpretObjCPointerOrNull<Any?>(payload))
|
||||
+4
@@ -29,6 +29,7 @@ const val TEMP_DIR = "Xtemporary-files-dir"
|
||||
const val NOPACK = "nopack"
|
||||
const val COMPILE_SOURCES = "Xcompile-source"
|
||||
const val SHORT_MODULE_NAME = "Xshort-module-name"
|
||||
const val FOREIGN_EXCEPTION_MODE = "Xforeign-exception-mode"
|
||||
|
||||
// TODO: unify camel and snake cases.
|
||||
// Possible solution is to accept both cases
|
||||
@@ -119,6 +120,9 @@ open class CInteropArguments(argParser: ArgParser =
|
||||
fullName = "Xmodule-name",
|
||||
description = "A full name of the library used for dependency resolution"
|
||||
)
|
||||
|
||||
val foreignExceptionMode by argParser.option(ArgType.String, FOREIGN_EXCEPTION_MODE,
|
||||
description = "Handle native exception in Kotlin: <terminate|objc-wrap>")
|
||||
}
|
||||
|
||||
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop",
|
||||
|
||||
+9
-1
@@ -27,6 +27,7 @@ import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlinx.cli.default
|
||||
import kotlinx.cli.required
|
||||
import org.jetbrains.kotlin.konan.ForeignExceptionMode
|
||||
import org.jetbrains.kotlin.konan.library.*
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.Distribution
|
||||
@@ -200,7 +201,7 @@ private fun processCLib(flavorName: String, cinteropArguments: CInteropArguments
|
||||
val manifestAddend = additionalArgs.manifest?.let { File(it) }
|
||||
|
||||
if (defFile == null && cinteropArguments.pkg == null) {
|
||||
cinteropArguments.argParser.printError("-def or -pkg should provided!")
|
||||
cinteropArguments.argParser.printError("-def or -pkg should be provided!")
|
||||
}
|
||||
|
||||
val tool = prepareTool(cinteropArguments.target, flavor)
|
||||
@@ -327,6 +328,13 @@ private fun processCLib(flavorName: String, cinteropArguments: CInteropArguments
|
||||
}
|
||||
stubIrContext.addManifestProperties(def.manifestAddendProperties)
|
||||
|
||||
// cinterop command line option overrides def file property
|
||||
val foreignExceptionMode = cinteropArguments.foreignExceptionMode?: def.config.foreignExceptionMode
|
||||
foreignExceptionMode?.let {
|
||||
def.manifestAddendProperties[ForeignExceptionMode.manifestKey] =
|
||||
ForeignExceptionMode.byValue(it).value // may throw IllegalArgumentException
|
||||
}
|
||||
|
||||
manifestAddend?.parentFile?.mkdirs()
|
||||
manifestAddend?.let { def.manifestAddendProperties.storeProperties(it) }
|
||||
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ private fun isExportedClass(descriptor: ClassDescriptor): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun AnnotationDescriptor.properValue(key: String) =
|
||||
internal fun AnnotationDescriptor.properValue(key: String) =
|
||||
this.argumentValue(key)?.toString()?.removeSurrounding("\"")
|
||||
|
||||
private fun functionImplName(descriptor: DeclarationDescriptor, default: String, shortName: Boolean): String {
|
||||
|
||||
+16
-6
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.lower.irNot
|
||||
import org.jetbrains.kotlin.backend.konan.KonanFqNames
|
||||
import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType
|
||||
import org.jetbrains.kotlin.backend.konan.RuntimeNames
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.konanLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.isObjCMetaClass
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
@@ -45,6 +46,7 @@ import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
|
||||
import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.ir.descriptors.*
|
||||
import org.jetbrains.kotlin.konan.ForeignExceptionMode
|
||||
|
||||
internal interface KotlinStubs {
|
||||
val irBuiltIns: IrBuiltIns
|
||||
@@ -62,7 +64,8 @@ internal interface KotlinStubs {
|
||||
private class KotlinToCCallBuilder(
|
||||
val irBuilder: IrBuilderWithScope,
|
||||
val stubs: KotlinStubs,
|
||||
val isObjCMethod: Boolean
|
||||
val isObjCMethod: Boolean,
|
||||
foreignExceptionMode: ForeignExceptionMode.Mode
|
||||
) {
|
||||
|
||||
val cBridgeName = stubs.getUniqueCName("knbridge")
|
||||
@@ -70,7 +73,7 @@ private class KotlinToCCallBuilder(
|
||||
val symbols: KonanSymbols get() = stubs.symbols
|
||||
|
||||
val bridgeCallBuilder = KotlinCallBuilder(irBuilder, symbols)
|
||||
val bridgeBuilder = KotlinCBridgeBuilder(irBuilder.startOffset, irBuilder.endOffset, cBridgeName, stubs, isKotlinToC = true)
|
||||
val bridgeBuilder = KotlinCBridgeBuilder(irBuilder.startOffset, irBuilder.endOffset, cBridgeName, stubs, isKotlinToC = true, foreignExceptionMode)
|
||||
val cBridgeBodyLines = mutableListOf<String>()
|
||||
val cCallBuilder = CCallBuilder()
|
||||
val cFunctionBuilder = CFunctionBuilder()
|
||||
@@ -110,10 +113,11 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrMemberA
|
||||
transformCall
|
||||
)
|
||||
|
||||
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean): IrExpression {
|
||||
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean,
|
||||
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default): IrExpression {
|
||||
require(expression.dispatchReceiver == null)
|
||||
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this, isObjCMethod = false)
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this, isObjCMethod = false, foreignExceptionMode)
|
||||
|
||||
val callee = expression.symbol.owner
|
||||
|
||||
@@ -307,7 +311,13 @@ internal fun KotlinStubs.generateObjCCall(
|
||||
receiver: ObjCCallReceiver,
|
||||
arguments: List<IrExpression?>
|
||||
) = builder.irBlock {
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this@generateObjCCall, isObjCMethod = true)
|
||||
val resolved = method.resolveFakeOverride(allowAbstract = true)?: method
|
||||
val exceptionMode = ForeignExceptionMode.byValue(
|
||||
resolved.module.konanLibrary?.manifestProperties
|
||||
?.getProperty(ForeignExceptionMode.manifestKey)
|
||||
)
|
||||
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this@generateObjCCall, isObjCMethod = true, exceptionMode)
|
||||
|
||||
val superClass = irTemporaryVar(
|
||||
superQualifier?.let { getObjCClass(symbols, it) } ?: irNullNativePtr(symbols)
|
||||
@@ -1327,7 +1337,7 @@ private class ObjCBlockPointerValuePassing(
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callBlock(blockPtr: IrExpression, arguments: List<IrExpression>): IrExpression {
|
||||
val callBuilder = KotlinToCCallBuilder(this, stubs, isObjCMethod = false)
|
||||
val callBuilder = KotlinToCCallBuilder(this, stubs, isObjCMethod = false, ForeignExceptionMode.default)
|
||||
|
||||
val rawBlockPointerParameter = callBuilder.passThroughBridge(blockPtr, blockPtr.type, CTypes.id)
|
||||
val blockVariableName = "block"
|
||||
|
||||
+11
-6
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType
|
||||
import org.jetbrains.kotlin.ir.util.constructors
|
||||
import org.jetbrains.kotlin.ir.util.irBuilder
|
||||
import org.jetbrains.kotlin.ir.util.irCatch
|
||||
import org.jetbrains.kotlin.konan.ForeignExceptionMode
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
internal class CFunctionBuilder {
|
||||
@@ -71,10 +72,11 @@ internal class KotlinBridgeBuilder(
|
||||
endOffset: Int,
|
||||
cName: String,
|
||||
stubs: KotlinStubs,
|
||||
isExternal: Boolean
|
||||
isExternal: Boolean,
|
||||
foreignExceptionMode: ForeignExceptionMode.Mode
|
||||
) {
|
||||
private var counter = 0
|
||||
private val bridge: IrFunction = createKotlinBridge(startOffset, endOffset, cName, stubs, isExternal)
|
||||
private val bridge: IrFunction = createKotlinBridge(startOffset, endOffset, cName, stubs, isExternal, foreignExceptionMode)
|
||||
val irBuilder: IrBuilderWithScope = irBuilder(stubs.irBuiltIns, bridge.symbol).at(startOffset, endOffset)
|
||||
|
||||
fun addParameter(type: IrType): IrValueParameter {
|
||||
@@ -105,7 +107,8 @@ private fun createKotlinBridge(
|
||||
endOffset: Int,
|
||||
cBridgeName: String,
|
||||
stubs: KotlinStubs,
|
||||
isExternal: Boolean
|
||||
isExternal: Boolean,
|
||||
foreignExceptionMode: ForeignExceptionMode.Mode
|
||||
): IrFunction {
|
||||
val bridgeDescriptor = WrappedSimpleFunctionDescriptor()
|
||||
val bridge = IrFunctionImpl(
|
||||
@@ -131,7 +134,8 @@ private fun createKotlinBridge(
|
||||
bridge.annotations += buildSimpleAnnotation(stubs.irBuiltIns, startOffset, endOffset,
|
||||
stubs.symbols.symbolName.owner, cBridgeName)
|
||||
bridge.annotations += buildSimpleAnnotation(stubs.irBuiltIns, startOffset, endOffset,
|
||||
stubs.symbols.filterExceptions.owner)
|
||||
stubs.symbols.filterExceptions.owner,
|
||||
foreignExceptionMode.value)
|
||||
} else {
|
||||
bridge.annotations += buildSimpleAnnotation(stubs.irBuiltIns, startOffset, endOffset,
|
||||
stubs.symbols.exportForCppRuntime.owner, cBridgeName)
|
||||
@@ -144,9 +148,10 @@ internal class KotlinCBridgeBuilder(
|
||||
endOffset: Int,
|
||||
cName: String,
|
||||
stubs: KotlinStubs,
|
||||
isKotlinToC: Boolean
|
||||
isKotlinToC: Boolean,
|
||||
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default
|
||||
) {
|
||||
private val kotlinBridgeBuilder = KotlinBridgeBuilder(startOffset, endOffset, cName, stubs, isExternal = isKotlinToC)
|
||||
private val kotlinBridgeBuilder = KotlinBridgeBuilder(startOffset, endOffset, cName, stubs, isExternal = isKotlinToC, foreignExceptionMode)
|
||||
private val cBridgeBuilder = CFunctionBuilder()
|
||||
|
||||
val kotlinIrBuilder: IrBuilderWithScope get() = kotlinBridgeBuilder.irBuilder
|
||||
|
||||
+2
@@ -195,6 +195,8 @@ internal class KonanSymbols(
|
||||
val interopCreateNSStringFromKString =
|
||||
symbolTable.referenceSimpleFunction(context.interopBuiltIns.CreateNSStringFromKString)
|
||||
|
||||
val createForeignException = interopFunction("CreateForeignException")
|
||||
|
||||
val interopObjCGetSelector = interopFunction("objCGetSelector")
|
||||
|
||||
val interopCEnumVar = interopClass("CEnumVar")
|
||||
|
||||
+55
-4
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
|
||||
import org.jetbrains.kotlin.ir.expressions.IrReturn
|
||||
import org.jetbrains.kotlin.konan.ForeignExceptionMode
|
||||
|
||||
private fun IrConstructor.isAnyConstructorDelegation(context: Context): Boolean {
|
||||
val statements = this.body?.statements ?: return false
|
||||
@@ -703,23 +704,49 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
return LLVMBuildExtractElement(builder, vector, index, name)!!
|
||||
}
|
||||
|
||||
fun filteringExceptionHandler(codeContext: CodeContext): ExceptionHandler {
|
||||
fun filteringExceptionHandler(codeContext: CodeContext, foreignExceptionMode: ForeignExceptionMode.Mode): ExceptionHandler {
|
||||
val lpBlock = basicBlockInFunction("filteringExceptionHandler", position()?.start)
|
||||
|
||||
val wrapExceptionMode = context.config.target.family.isAppleFamily &&
|
||||
foreignExceptionMode == ForeignExceptionMode.Mode.OBJC_WRAP
|
||||
|
||||
appendingTo(lpBlock) {
|
||||
val landingpad = gxxLandingpad(2)
|
||||
LLVMAddClause(landingpad, kotlinExceptionRtti.llvm)
|
||||
if (wrapExceptionMode) {
|
||||
LLVMAddClause(landingpad, objcNSExceptionRtti.llvm)
|
||||
}
|
||||
LLVMAddClause(landingpad, LLVMConstNull(kInt8Ptr))
|
||||
|
||||
val fatalForeignExceptionBlock = basicBlock("fatalForeignException", position()?.start)
|
||||
val forwardKotlinExceptionBlock = basicBlock("forwardKotlinException", position()?.start)
|
||||
|
||||
val typeId = extractValue(landingpad, 1)
|
||||
val isKotlinException = icmpEq(
|
||||
extractValue(landingpad, 1),
|
||||
typeId,
|
||||
call(context.llvm.llvmEhTypeidFor, listOf(kotlinExceptionRtti.llvm))
|
||||
)
|
||||
|
||||
condBr(isKotlinException, forwardKotlinExceptionBlock, fatalForeignExceptionBlock)
|
||||
if (wrapExceptionMode) {
|
||||
val foreignExceptionBlock = basicBlock("foreignException", position()?.start)
|
||||
val forwardNativeExceptionBlock = basicBlock("forwardNativeException", position()?.start)
|
||||
|
||||
condBr(isKotlinException, forwardKotlinExceptionBlock, foreignExceptionBlock)
|
||||
appendingTo(foreignExceptionBlock) {
|
||||
val isObjCException = icmpEq(
|
||||
typeId,
|
||||
call(context.llvm.llvmEhTypeidFor, listOf(objcNSExceptionRtti.llvm))
|
||||
)
|
||||
condBr(isObjCException, forwardNativeExceptionBlock, fatalForeignExceptionBlock)
|
||||
|
||||
appendingTo(forwardNativeExceptionBlock) {
|
||||
val exception = createForeignException(landingpad, codeContext.exceptionHandler)
|
||||
codeContext.genThrow(exception)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
condBr(isKotlinException, forwardKotlinExceptionBlock, fatalForeignExceptionBlock)
|
||||
}
|
||||
|
||||
appendingTo(forwardKotlinExceptionBlock) {
|
||||
// Rethrow Kotlin exception to real handler.
|
||||
@@ -731,6 +758,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
call(context.llvm.cxaBeginCatchFunction, listOf(exceptionRecord))
|
||||
terminate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return object : ExceptionHandler.Local() {
|
||||
@@ -770,7 +798,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
|
||||
LLVMAddClause(landingpadResult, LLVMConstNull(kInt8Ptr))
|
||||
|
||||
// FIXME: properly handle C++ exceptions: currently C++ exception can be thrown out from try-finally
|
||||
// TODO: properly handle C++ exceptions: currently C++ exception can be thrown out from try-finally
|
||||
// bypassing the finally block.
|
||||
|
||||
return extractKotlinException(landingpadResult)
|
||||
@@ -797,6 +825,21 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
return exceptionPtr
|
||||
}
|
||||
|
||||
private fun createForeignException(landingpadResult: LLVMValueRef, exceptionHandler: ExceptionHandler): LLVMValueRef {
|
||||
val exceptionRecord = extractValue(landingpadResult, 0, "er")
|
||||
|
||||
// __cxa_begin_catch returns pointer to C++ exception object.
|
||||
val exceptionRawPtr = call(context.llvm.cxaBeginCatchFunction, listOf(exceptionRecord))
|
||||
|
||||
// This will take care of ARC - need to be done in the catching scope, i.e. before __cxa_end_catch
|
||||
val exception = call(context.ir.symbols.createForeignException.owner.llvmFunction,
|
||||
listOf(exceptionRawPtr),
|
||||
Lifetime.GLOBAL, exceptionHandler)
|
||||
|
||||
call(context.llvm.cxaEndCatchFunction, listOf())
|
||||
return exception
|
||||
}
|
||||
|
||||
inline fun ifThenElse(
|
||||
condition: LLVMValueRef,
|
||||
thenValue: LLVMValueRef,
|
||||
@@ -1273,6 +1316,14 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
origin = context.stdlibModule.llvmSymbolOrigin
|
||||
)).bitcast(int8TypePtr)
|
||||
|
||||
private val objcNSExceptionRtti: ConstPointer by lazy {
|
||||
constPointer(importGlobal(
|
||||
"OBJC_EHTYPE_\$_NSException", // typeinfo for NSException*
|
||||
int8TypePtr,
|
||||
origin = context.stdlibModule.llvmSymbolOrigin
|
||||
)).bitcast(int8TypePtr)
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
/**
|
||||
|
||||
+6
-5
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.konan.ForeignExceptionMode
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -2311,11 +2312,11 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
|
||||
private fun call(function: IrFunction, llvmFunction: LLVMValueRef, args: List<LLVMValueRef>,
|
||||
resultLifetime: Lifetime): LLVMValueRef {
|
||||
val exceptionHandler = if (function.hasAnnotation(RuntimeNames.filterExceptions)) {
|
||||
functionGenerationContext.filteringExceptionHandler(currentCodeContext)
|
||||
} else {
|
||||
currentCodeContext.exceptionHandler
|
||||
}
|
||||
|
||||
val exceptionHandler = function.annotations.findAnnotation(RuntimeNames.filterExceptions)?.let {
|
||||
val foreignExceptionMode = ForeignExceptionMode.byValue(it.getAnnotationValueOrNull<String>("mode"))
|
||||
functionGenerationContext.filteringExceptionHandler(currentCodeContext, foreignExceptionMode)
|
||||
} ?: currentCodeContext.exceptionHandler
|
||||
|
||||
val result = call(llvmFunction, args, resultLifetime, exceptionHandler)
|
||||
if (!function.isSuspend && function.returnType.isNothing()) {
|
||||
|
||||
+6
-1
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isFromInteropLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.konanLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.ir.companionObject
|
||||
@@ -50,6 +51,7 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.konan.ForeignExceptionMode
|
||||
|
||||
internal class InteropLowering(context: Context) : FileLoweringPass {
|
||||
// TODO: merge these lowerings.
|
||||
@@ -1006,7 +1008,10 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
|
||||
|
||||
if (function.annotations.hasAnnotation(RuntimeNames.cCall)) {
|
||||
context.llvmImports.add(function.llvmSymbolOrigin)
|
||||
return generateWithStubs { generateCCall(expression, builder, isInvoke = false) }
|
||||
val exceptionMode = ForeignExceptionMode.byValue(
|
||||
function.module.konanLibrary?.manifestProperties?.getProperty(ForeignExceptionMode.manifestKey)
|
||||
)
|
||||
return generateWithStubs { generateCCall(expression, builder, isInvoke = false, exceptionMode) }
|
||||
}
|
||||
|
||||
val failCompilation = { msg: String -> context.reportCompilationError(msg, irFile, expression) }
|
||||
|
||||
@@ -3626,6 +3626,22 @@ if (PlatformInfo.isAppleTarget(project)) {
|
||||
it.headers "$projectDir/interop/objc/msg_send/messaging.h"
|
||||
}
|
||||
|
||||
createInterop("foreignException") {
|
||||
it.defFile 'interop/objc/foreignException/objc_wrap.def'
|
||||
it.headers "$projectDir/interop/objc/foreignException/objc_wrap.h"
|
||||
}
|
||||
|
||||
createInterop("foreignExceptionMode_default") {
|
||||
it.defFile 'interop/objc/foreignException/objcExceptionMode.def'
|
||||
it.headers "$projectDir/interop/objc/foreignException/objc_wrap.h"
|
||||
}
|
||||
|
||||
createInterop("foreignExceptionMode_wrap") {
|
||||
it.defFile 'interop/objc/foreignException/objcExceptionMode.def'
|
||||
it.headers "$projectDir/interop/objc/foreignException/objc_wrap.h"
|
||||
it.extraOpts '-Xforeign-exception-mode', "objc-wrap"
|
||||
}
|
||||
|
||||
createInterop("objcKt34467") {
|
||||
it.defFile 'interop/objc/kt34467/module_library.def'
|
||||
def moduleMap = file("interop/objc/kt34467/module_library.modulemap").absolutePath
|
||||
@@ -3995,6 +4011,59 @@ if (PlatformInfo.isAppleTarget(project)) {
|
||||
}
|
||||
}
|
||||
|
||||
interopTest("interop_objc_foreignException") {
|
||||
source = "interop/objc/foreignException/objc_wrap.kt"
|
||||
interop = 'foreignException'
|
||||
|
||||
doBeforeBuild {
|
||||
mkdir(buildDir)
|
||||
execKonanClang(project.target) {
|
||||
args "$projectDir/interop/objc/foreignException/objc_wrap.m"
|
||||
args "-lobjc", '-fobjc-arc'
|
||||
args '-fPIC', '-shared', '-o', "$buildDir/libobjcexception.dylib"
|
||||
}
|
||||
if (project.target instanceof KonanTarget.IOS_X64) {
|
||||
UtilsKt.codesign(project, "$buildDir/libobjcexception.dylib")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interopTest("interop_objc_foreignExceptionMode_wrap") {
|
||||
source = "interop/objc/foreignException/objcExceptionMode.kt"
|
||||
interop = 'foreignExceptionMode_wrap'
|
||||
goldValue = "OK: ForeignException\n"
|
||||
|
||||
doBeforeBuild {
|
||||
mkdir(buildDir)
|
||||
execKonanClang(project.target) {
|
||||
args "$projectDir/interop/objc/foreignException/objc_wrap.m"
|
||||
args "-lobjc", '-fobjc-arc'
|
||||
args '-fPIC', '-shared', '-o', "$buildDir/libobjcexception.dylib"
|
||||
}
|
||||
if (project.target instanceof KonanTarget.IOS_X64) {
|
||||
UtilsKt.codesign(project, "$buildDir/libobjcexception.dylib")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interopTest("interop_objc_foreignExceptionMode_default") {
|
||||
source = "interop/objc/foreignException/objcExceptionMode.kt"
|
||||
interop = 'foreignExceptionMode_default'
|
||||
goldValue = "OK: Ends with uncaught exception handler\n"
|
||||
|
||||
doBeforeBuild {
|
||||
mkdir(buildDir)
|
||||
execKonanClang(project.target) {
|
||||
args "$projectDir/interop/objc/foreignException/objc_wrap.m"
|
||||
args "-lobjc", '-fobjc-arc'
|
||||
args '-fPIC', '-shared', '-o', "$buildDir/libobjcexception.dylib"
|
||||
}
|
||||
if (project.target instanceof KonanTarget.IOS_X64) {
|
||||
UtilsKt.codesign(project, "$buildDir/libobjcexception.dylib")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interopTest("interop_objc_kt34467") {
|
||||
source = "interop/objc/kt34467/foo.kt"
|
||||
interop = 'objcKt34467'
|
||||
|
||||
@@ -18,7 +18,13 @@ fun exc_handler(x: Any?) : Unit {
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// This does not work anymore, as NSException propagated as ForeignException
|
||||
// so we got `Uncaught Kotlin exception` instead. Use normal try/catch.
|
||||
objc_setUncaughtExceptionHandler(staticCFunction(::exc_handler))
|
||||
|
||||
println(NSJSONSerialization())
|
||||
try {
|
||||
println(NSJSONSerialization())
|
||||
} catch (e: Exception) { // ForeignException expected
|
||||
println(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/objc_wrap.h
|
||||
linkerOpts = -lobjcexception
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Test different behavior depending on foreignExceptionMode option
|
||||
*/
|
||||
|
||||
import kotlin.test.*
|
||||
import objcExceptionMode.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.objc.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
|
||||
@Test fun testKT35056() {
|
||||
val name = "Some native exception"
|
||||
val reason = "Illegal value"
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "FAILED"
|
||||
try {
|
||||
raiseExc(name, reason)
|
||||
assertNotEquals("FAILED", catchBlockTest) // shall not get here anyway
|
||||
} catch (e: ForeignException) {
|
||||
val ret = logExc(e.nativeException) // return NSException name
|
||||
assertEquals(name, ret)
|
||||
assertEquals("$name:: $reason", e.message)
|
||||
println("OK: ForeignException")
|
||||
catchBlockTest = "PASSED"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("PASSED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun abnormal_handler(x: Any?) : Unit {
|
||||
println("OK: Ends with uncaught exception handler")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// Depending on the `foreignxceptionMode` option (def file or cinterop cli) this test should ends
|
||||
// normally with `ForeignException` handled or abnormally with `abnormal_handler`.
|
||||
// Test shall validate output (golden value) from `abnormal_handler`.
|
||||
|
||||
objc_setUncaughtExceptionHandler(staticCFunction(::abnormal_handler))
|
||||
|
||||
testKT35056()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/objc_wrap.h
|
||||
linkerOpts = -lobjcexception
|
||||
foreignExceptionMode = objc-wrap
|
||||
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void raiseExc(id name, id reason);
|
||||
id logExc(id exception);
|
||||
|
||||
@interface Foo : NSObject
|
||||
- (void)instanceMethodThrow:(id)name reason:(id)reason;
|
||||
+ (void)classMethodThrow:(id)name reason:(id)reason;
|
||||
@end
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Test different types of callable with foreignExceptionMode=objc-wrap
|
||||
*/
|
||||
|
||||
import kotlin.test.*
|
||||
//import objcTests.*
|
||||
import objc_wrap.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun testInner(name: String, reason: String) {
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "NOT EXPECTED"
|
||||
try {
|
||||
raiseExc(name, reason)
|
||||
} catch (e: RuntimeException) {
|
||||
catchBlockTest = "This shouldn't happen"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("NOT EXPECTED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
typealias CallMe = (String, String) -> Unit
|
||||
|
||||
@Test fun testExceptionWrap(raise: CallMe) {
|
||||
val name = "Some native exception"
|
||||
val reason = "Illegal value"
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "FAILED"
|
||||
try {
|
||||
raise(name, reason)
|
||||
} catch (e: ForeignException) {
|
||||
val ret = logExc(e.nativeException) // return NSException name
|
||||
assertEquals(name, ret)
|
||||
assertEquals("$name:: $reason", e.message)
|
||||
catchBlockTest = "PASSED"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("PASSED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
class Bar() : Foo()
|
||||
|
||||
fun main() {
|
||||
testExceptionWrap(::raiseExc) // simple
|
||||
testExceptionWrap(::testInner) // nested try block
|
||||
testExceptionWrap(Foo::classMethodThrow) // class method
|
||||
testExceptionWrap(Foo()::instanceMethodThrow) // instance method
|
||||
testExceptionWrap(Bar()::instanceMethodThrow) // fake override
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "objc_wrap.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void raiseExc(id name, id reason) {
|
||||
[NSException raise:name format:@"%@", reason];
|
||||
}
|
||||
|
||||
id logExc(id exc) {
|
||||
assert([exc isKindOfClass:[NSException class]]);
|
||||
return ((NSException*)exc).name;
|
||||
}
|
||||
|
||||
@implementation Foo : NSObject
|
||||
- (void) instanceMethodThrow:(id)name reason:(id)reason {
|
||||
raiseExc(name, reason);
|
||||
}
|
||||
+ (void) classMethodThrow:(id)name reason:(id)reason {
|
||||
raiseExc(name, reason);
|
||||
}
|
||||
@end
|
||||
@@ -69,6 +69,8 @@ add_executable(runtime
|
||||
src/main/cpp/ObjCInteropUtils.mm
|
||||
src/main/cpp/ObjCExportCollectionUtils.mm
|
||||
src/main/cpp/ObjCExportErrors.mm
|
||||
src/main/cpp/ObjCExportExceptionDetails.mm
|
||||
|
||||
src/objc/cpp/ObjCExportNumbers.mm
|
||||
src/objc/cpp/ObjCExportClasses.mm
|
||||
src/objc/cpp/ObjCExportCollections.mm
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "Memory.h"
|
||||
#import "Types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
OBJ_GETTER(Kotlin_ObjCExport_ExceptionDetails, KRef thiz, KRef exceptionHolder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "Memory.h"
|
||||
#import "ObjCExportExceptionDetails.h"
|
||||
#import "ObjCExport.h"
|
||||
|
||||
#if KONAN_OBJC_INTEROP
|
||||
|
||||
#import <Foundation/NSException.h>
|
||||
|
||||
//! TODO: Use not_null signature.
|
||||
OBJ_GETTER(Kotlin_ObjCExport_ExceptionDetails, KRef /*thiz*/, KRef exceptionHolder) {
|
||||
if (NSException* exception = (NSException*)Kotlin_ObjCExport_refToObjC(exceptionHolder)) {
|
||||
RuntimeAssert([exception isKindOfClass:[NSException class]], "Illegal type: NSException expected");
|
||||
NSString* ret = [NSString stringWithFormat: @"%@:: %@", exception.name, exception.reason];
|
||||
RETURN_RESULT_OF(Kotlin_Interop_CreateKStringFromNSString, ret);
|
||||
}
|
||||
|
||||
RETURN_OBJ(nullptr);
|
||||
}
|
||||
|
||||
#else // KONAN_OBJC_INTEROP
|
||||
|
||||
OBJ_GETTER(Kotlin_ObjCExport_ExceptionDetails, KRef /*thiz*/, KRef /*exceptionHolder*/) {
|
||||
RETURN_OBJ(nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -116,7 +116,7 @@ annotation class Independent
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@PublishedApi internal annotation class FilterExceptions
|
||||
@PublishedApi internal annotation class FilterExceptions(val mode: String = "terminate")
|
||||
|
||||
/**
|
||||
* Marks a class whose instances to be added to the list of leak detector candidates.
|
||||
|
||||
@@ -31,3 +31,22 @@ class KonanExternalToolFailure(message: String, val toolName: String, cause: Thr
|
||||
*/
|
||||
class MissingXcodeException(message: String, cause: Throwable? = null) : KonanException(message, cause)
|
||||
|
||||
/**
|
||||
* Native exception handling in Kotlin: terminate, wrap, etc.
|
||||
* Foreign exceptionMode mode is per library option: controlled by cinterop command-line option or def file property
|
||||
* than stored in klib manifest and used by compiler to generate appropriate handler.
|
||||
*/
|
||||
class ForeignExceptionMode {
|
||||
companion object {
|
||||
val manifestKey = "foreignExceptionMode"
|
||||
val default = Mode.TERMINATE
|
||||
fun byValue(value: String?): Mode = value?.let {
|
||||
Mode.values().find { it.value == value }
|
||||
?: throw IllegalArgumentException("Illegal ForeignExceptionMode $value")
|
||||
} ?: default
|
||||
}
|
||||
enum class Mode(val value: String) {
|
||||
TERMINATE("terminate"),
|
||||
OBJC_WRAP("objc-wrap")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,11 @@ class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProper
|
||||
val disableDesignatedInitializerChecks by lazy {
|
||||
properties.getProperty("disableDesignatedInitializerChecks")?.toBoolean() ?: false
|
||||
}
|
||||
|
||||
val foreignExceptionMode by lazy {
|
||||
properties.getProperty("foreignExceptionMode")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user