Support implicit @Throws(CancellationException::class) in ObjCExport

This commit is contained in:
Svyatoslav Scherbina
2020-05-20 16:54:41 +03:00
committed by SvyatoslavScherbina
parent 7978f0ac8d
commit d5cbfd5420
7 changed files with 121 additions and 21 deletions
@@ -22,6 +22,7 @@ object KonanFqNames {
val nonNullNativePtr = internalPackageName.child(Name.identifier(NON_NULL_NATIVE_PTR_NAME)).toUnsafe()
val Vector128 = packageName.child(Name.identifier(VECTOR128))
val throws = FqName("kotlin.Throws")
val cancellationException = FqName("kotlin.coroutines.cancellation.CancellationException")
val threadLocal = FqName("kotlin.native.concurrent.ThreadLocal")
val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable")
val frozen = FqName("kotlin.native.internal.Frozen")
@@ -409,6 +409,8 @@ internal class KonanSymbols(
.filterNot { it.isExpect }.single().getter!!
)
val cancellationException = topLevelClass(KonanFqNames.cancellationException)
val kotlinResult = topLevelClass("kotlin.Result")
val kotlinResultGetOrThrow = symbolTable.referenceSimpleFunction(
@@ -9,10 +9,7 @@ import llvm.*
import org.jetbrains.kotlin.backend.common.ir.simpleFunctions
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.allParameters
import org.jetbrains.kotlin.backend.konan.ir.isOverridable
import org.jetbrains.kotlin.backend.konan.ir.isUnit
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCCodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator
@@ -890,7 +887,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
private fun ObjCExportCodeGenerator.generateExceptionTypeInfoArray(baseMethod: IrFunction): LLVMValueRef =
exceptionTypeInfoArrays.getOrPut(baseMethod) {
val types = computesThrowsClasses(baseMethod)
val types = effectiveThrowsClasses(baseMethod, symbols)
generateTypeInfoArray(types.toSet())
}.llvm
@@ -900,16 +897,22 @@ private fun ObjCExportCodeGenerator.generateTypeInfoArray(types: Set<IrClass>):
codegen.staticData.placeGlobalConstArray("", codegen.kTypeInfoPtr, typeInfos)
}
private fun computesThrowsClasses(method: IrFunction): List<IrClass> {
// Note: frontend ensures that all topmost overridden methods have equal @Throws annotations.
// However due to linking different versions of libraries IR could end up not meeting this condition.
// Handling this gracefully below.
private fun effectiveThrowsClasses(method: IrFunction, symbols: KonanSymbols): List<IrClass> {
if (method is IrSimpleFunction && method.overriddenSymbols.isNotEmpty()) {
return computesThrowsClasses(method.overriddenSymbols.first().owner)
return effectiveThrowsClasses(method.overriddenSymbols.first().owner, symbols)
}
val throwsVararg = method.annotations.findAnnotation(KonanFqNames.throws)?.getValueArgument(0)
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws)
?: return if (method.isSuspend) {
listOf(symbols.cancellationException.owner)
} else {
// Note: frontend ensures that all topmost overridden methods have (equal) @Throws annotations.
// However due to linking different versions of libraries IR could end up not meeting this condition.
// Handling missing annotation gracefully:
emptyList()
}
val throwsVararg = throwsAnnotation.getValueArgument(0)
?: return emptyList()
if (throwsVararg !is IrVararg) error(method.getContainingFile(), throwsVararg, "unexpected vararg")
@@ -652,16 +652,37 @@ internal class ObjCExportTranslatorImpl(
}
private fun exportThrown(method: FunctionDescriptor) {
if (!method.kind.isReal) return
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws) ?: return
val arguments = (throwsAnnotation.allValueArguments.values.single() as ArrayValue).value
for (argument in arguments) {
val classDescriptor = TypeUtils.getClassDescriptor((argument as KClassValue).getArgumentType(method.module)) ?: continue
generator?.requireClassOrInterface(classDescriptor)
}
getDefinedThrows(method)
?.mapNotNull { method.module.findClassAcrossModuleDependencies(it) }
?.forEach { generator?.requireClassOrInterface(it) }
}
private fun getDefinedThrows(method: FunctionDescriptor): Sequence<ClassId>? {
if (!method.kind.isReal) return null
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws)
if (throwsAnnotation != null) {
val argumentsArrayValue = throwsAnnotation.firstArgument() as? ArrayValue
return argumentsArrayValue?.value?.asSequence().orEmpty()
.filterIsInstance<KClassValue>()
.mapNotNull {
when (val value = it.value) {
is KClassValue.Value.NormalClass -> value.classId
is KClassValue.Value.LocalClass -> null
}
}
}
if (method.isSuspend && method.overriddenDescriptors.isEmpty()) {
return implicitSuspendThrows
}
return null
}
private val implicitSuspendThrows = sequenceOf(ClassId.topLevel(KonanFqNames.cancellationException))
private fun mapReturnType(returnBridge: MethodBridge.ReturnValue, method: FunctionDescriptor, objCExportScope: ObjCExportScope): ObjCType = when (returnBridge) {
MethodBridge.ReturnValue.Suspend,
MethodBridge.ReturnValue.Void -> ObjCVoidType
+25 -1
View File
@@ -139,4 +139,28 @@ fun callSuspendBridge(bridge: AbstractSuspendBridge, resultHolder: ResultHolder<
callSuspendBridgeImpl(bridge)
callAbstractSuspendBridgeImpl(bridge)
}.startCoroutine(ResultHolderCompletion(resultHolder))
}
}
suspend fun throwCancellationException(): Unit {
val exception = CancellationException("coroutine is cancelled")
// Note: frontend checker hardcodes fq names of CancellationException super classes (see NativeThrowsChecker).
// This is our best effort to keep that list in sync with actual stdlib code:
assertTrue(exception is kotlin.Throwable)
assertTrue(exception is kotlin.Exception)
assertTrue(exception is kotlin.RuntimeException)
assertTrue(exception is kotlin.IllegalStateException)
assertTrue(exception is kotlin.coroutines.cancellation.CancellationException)
throw exception
}
abstract class ThrowCancellationException {
internal abstract suspend fun throwCancellationException()
}
class ThrowCancellationExceptionImpl : ThrowCancellationException() {
public override suspend fun throwCancellationException() {
throw CancellationException()
}
}
@@ -221,6 +221,38 @@ private func testBridges() throws {
try assertSame(actual: resultHolder.result, expected: KotlinUnit())
}
private func testImplicitThrows1() throws {
var result: KotlinUnit? = nil
var error: Error? = nil
var completionCalled = 0
CoroutinesKt.throwCancellationException { _result, _error in
completionCalled += 1
result = _result
error = _error
}
try assertEquals(actual: completionCalled, expected: 1)
try assertNil(result)
try assertTrue(error?.kotlinException is KotlinCancellationException)
}
private func testImplicitThrows2() throws {
var result: KotlinUnit? = nil
var error: Error? = nil
var completionCalled = 0
ThrowCancellationExceptionImpl().throwCancellationException { _result, _error in
completionCalled += 1
result = _result
error = _error
}
try assertEquals(actual: completionCalled, expected: 1)
try assertNil(result)
try assertTrue(error?.kotlinException is KotlinCancellationException)
}
class CoroutinesTests : SimpleTestProvider {
override init() {
super.init()
@@ -229,5 +261,7 @@ class CoroutinesTests : SimpleTestProvider {
test("TestCall", testCall)
test("TestOverride", testOverride)
test("TestBridges", testBridges)
test("TestImplicitThrows1", testImplicitThrows1)
test("TestImplicitThrows2", testImplicitThrows2)
}
}
@@ -179,6 +179,20 @@ __attribute__((swift_name("AbstractSuspendBridge")))
- (void)nothingAsUnitValue:(KtInt *)value completionHandler:(void (^)(KtKotlinNothing * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsUnit(value:completionHandler:)")));
@end;
__attribute__((swift_name("ThrowCancellationException")))
@interface KtThrowCancellationException : KtBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("ThrowCancellationExceptionImpl")))
@interface KtThrowCancellationExceptionImpl : KtThrowCancellationException
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (void)throwCancellationExceptionWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwCancellationException(completionHandler:)")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("CoroutinesKt")))
@interface KtCoroutinesKt : KtBase
@@ -189,6 +203,7 @@ __attribute__((swift_name("CoroutinesKt")))
+ (void)callSuspendFunSuspendFun:(id<KtSuspendFun>)suspendFun doYield:(BOOL)doYield doThrow:(BOOL)doThrow resultHolder:(KtResultHolder<KtInt *> *)resultHolder __attribute__((swift_name("callSuspendFun(suspendFun:doYield:doThrow:resultHolder:)")));
+ (void)callSuspendFun2SuspendFun:(id<KtSuspendFun>)suspendFun doYield:(BOOL)doYield doThrow:(BOOL)doThrow completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("callSuspendFun2(suspendFun:doYield:doThrow:completionHandler:)")));
+ (BOOL)callSuspendBridgeBridge:(KtAbstractSuspendBridge *)bridge resultHolder:(KtResultHolder<KtKotlinUnit *> *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("callSuspendBridge(bridge:resultHolder:)")));
+ (void)throwCancellationExceptionWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwCancellationException(completionHandler:)")));
@end;
__attribute__((swift_name("GH4002ArgumentBase")))