From 776154881bd5ec2f30eb160e0d0c9bd374bac885 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 13 Jan 2022 13:57:15 +0300 Subject: [PATCH] ObjCExport: prohibit Kotlin exceptions leaking through native code back If a Kotlin exceptions is thrown out of a native code in ObjCExport (when calling Swift/Obj-C method overriding Kotlin method, or calling Swift/Obj-C block as Kotlin function type), treat this exception as fatal and terminate the process. This could happen if the Kotlin exception leaked from Kotlin to native code before. In other words, when calling Kotlin -> Swift/Obj-C -> Kotlin, if Kotlin exception passes from the last to the first part, terminate. ^KT-50830 --- .../konan/llvm/objcexport/BlockPointerSupport.kt | 6 +++++- .../konan/llvm/objcexport/ObjCExportCodeGenerator.kt | 6 +++++- .../backend.native/tests/objcexport/expectedLazy.h | 3 +-- .../tests/objcexport/expectedLazyLegacySuspendUnit.h | 3 +-- .../tests/objcexport/expectedLazyNoGenerics.h | 3 +-- .../backend.native/tests/objcexport/values.kt | 12 +----------- .../backend.native/tests/objcexport/values.swift | 4 ++-- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt index 2872b9d8d75..c0a8f01b850 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt @@ -51,7 +51,11 @@ internal fun ObjCExportCodeGeneratorBase.generateBlockToKotlinFunctionConverter( ) val invoke = loadBlockInvoke(blockPtr, bridge) - val result = callFromBridge(invoke, listOf(blockPtr) + args, toNative = true) + switchThreadStateIfExperimentalMM(ThreadState.Native) + // Using terminatingExceptionHandler, so any exception thrown by `invoke` will lead to the termination, + // and switching the thread state back to `Runnable` on exceptional path is not required. + val result = call(invoke, listOf(blockPtr) + args, exceptionHandler = terminatingExceptionHandler) + switchThreadStateIfExperimentalMM(ThreadState.Runnable) val kotlinResult = if (bridge.returnsVoid) { theUnitInstanceRef.llvm diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 9a22c428c66..c149c86fd08 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -1259,7 +1259,11 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( } } - val targetResult = callFromBridge(objcMsgSend, objCArgs, toNative = true) + switchThreadStateIfExperimentalMM(ThreadState.Native) + // Using terminatingExceptionHandler, so any exception thrown by the method will lead to the termination, + // and switching the thread state back to `Runnable` on exceptional path is not required. + val targetResult = call(objcMsgSend, objCArgs, exceptionHandler = terminatingExceptionHandler) + switchThreadStateIfExperimentalMM(ThreadState.Runnable) assert(baseMethod.symbol !is IrConstructorSymbol) diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index c0d5af93f2c..2826ec0ebae 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -2675,8 +2675,7 @@ __attribute__((swift_name("ValuesKt"))) + (id)createTransformDecimalStringToInt __attribute__((swift_name("createTransformDecimalStringToInt()"))); + (BOOL)runUnitBlockBlock:(void (^)(void))block __attribute__((swift_name("runUnitBlock(block:)"))); + (void (^)(void))asUnitBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asUnitBlock(block:)"))); -+ (BOOL)runNothingBlockBlock:(void (^)(void))block __attribute__((swift_name("runNothingBlock(block:)"))); -+ (void (^)(void))asNothingBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asNothingBlock(block:)"))); ++ (void)runNothingBlockBlock:(void (^)(void))block __attribute__((swift_name("runNothingBlock(block:)"))); + (void (^ _Nullable)(void))getNullBlock __attribute__((swift_name("getNullBlock()"))); + (BOOL)isBlockNullBlock:(void (^ _Nullable)(void))block __attribute__((swift_name("isBlockNull(block:)"))); + (BOOL)isFunctionObj:(id _Nullable)obj __attribute__((swift_name("isFunction(obj:)"))); diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h index dc754402b25..380ad6b770f 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h @@ -2617,8 +2617,7 @@ __attribute__((swift_name("ValuesKt"))) + (id)createTransformDecimalStringToInt __attribute__((swift_name("createTransformDecimalStringToInt()"))); + (BOOL)runUnitBlockBlock:(void (^)(void))block __attribute__((swift_name("runUnitBlock(block:)"))); + (void (^)(void))asUnitBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asUnitBlock(block:)"))); -+ (BOOL)runNothingBlockBlock:(void (^)(void))block __attribute__((swift_name("runNothingBlock(block:)"))); -+ (void (^)(void))asNothingBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asNothingBlock(block:)"))); ++ (void)runNothingBlockBlock:(void (^)(void))block __attribute__((swift_name("runNothingBlock(block:)"))); + (void (^ _Nullable)(void))getNullBlock __attribute__((swift_name("getNullBlock()"))); + (BOOL)isBlockNullBlock:(void (^ _Nullable)(void))block __attribute__((swift_name("isBlockNull(block:)"))); + (BOOL)isFunctionObj:(id _Nullable)obj __attribute__((swift_name("isFunction(obj:)"))); diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index fd8308ef6d5..b67af84fb78 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -2617,8 +2617,7 @@ __attribute__((swift_name("ValuesKt"))) + (id)createTransformDecimalStringToInt __attribute__((swift_name("createTransformDecimalStringToInt()"))); + (BOOL)runUnitBlockBlock:(void (^)(void))block __attribute__((swift_name("runUnitBlock(block:)"))); + (void (^)(void))asUnitBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asUnitBlock(block:)"))); -+ (BOOL)runNothingBlockBlock:(void (^)(void))block __attribute__((swift_name("runNothingBlock(block:)"))); -+ (void (^)(void))asNothingBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asNothingBlock(block:)"))); ++ (void)runNothingBlockBlock:(void (^)(void))block __attribute__((swift_name("runNothingBlock(block:)"))); + (void (^ _Nullable)(void))getNullBlock __attribute__((swift_name("getNullBlock()"))); + (BOOL)isBlockNullBlock:(void (^ _Nullable)(void))block __attribute__((swift_name("isBlockNull(block:)"))); + (BOOL)isFunctionObj:(id _Nullable)obj __attribute__((swift_name("isFunction(obj:)"))); diff --git a/kotlin-native/backend.native/tests/objcexport/values.kt b/kotlin-native/backend.native/tests/objcexport/values.kt index 70bfc30e9a4..922fce309fe 100644 --- a/kotlin-native/backend.native/tests/objcexport/values.kt +++ b/kotlin-native/backend.native/tests/objcexport/values.kt @@ -504,17 +504,7 @@ fun runUnitBlock(block: () -> Unit): Boolean { fun asUnitBlock(block: () -> Any?): () -> Unit = { block() } -fun runNothingBlock(block: () -> Nothing) = try { - block() - false -} catch (e: Throwable) { - true -} - -fun asNothingBlock(block: () -> Any?): () -> Nothing = { - block() - TODO() -} +fun runNothingBlock(block: () -> Nothing) { (block as () -> Any?)() } fun getNullBlock(): (() -> Unit)? = null fun isBlockNull(block: (() -> Unit)?): Boolean = block == null diff --git a/kotlin-native/backend.native/tests/objcexport/values.swift b/kotlin-native/backend.native/tests/objcexport/values.swift index 660def5d890..e03fa5834bc 100644 --- a/kotlin-native/backend.native/tests/objcexport/values.swift +++ b/kotlin-native/backend.native/tests/objcexport/values.swift @@ -435,8 +435,8 @@ func testLambda() throws { try assertTrue(unitBlock() == Void()) try assertEquals(actual: blockRuns, expected: 2) - let nothingBlock: () -> Void = ValuesKt.asNothingBlock { blockRuns += 1 } - try assertTrue(ValuesKt.runNothingBlock(block: nothingBlock)) + let nothingBlock: () -> Void = { blockRuns += 1 } + ValuesKt.runNothingBlock(block: nothingBlock) try assertEquals(actual: blockRuns, expected: 3) try assertTrue(ValuesKt.getNullBlock() == nil)