Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[K/N][Tests] Fix crashes when casting to an Obj-C class companion #5270

Closed
wants to merge 7 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,16 @@ fun IrClass.getExternalObjCClassBinaryName(): String =
this.getExplicitExternalObjCClassBinaryName()
?: this.name.asString()

fun IrClass.getExternalObjCMetaClassBinaryName(): String =
this.getExplicitExternalObjCClassBinaryName()
?: this.name.asString().removeSuffix("Meta")
fun IrClass.getExternalObjCMetaClassBinaryName(): String {
this.getExplicitExternalObjCClassBinaryName()?.let { return it }
// External ObjC metaclass is named as `Foo.Componion`, Foo is subclass of NSObject or itself.
if (this.isCompanion) {
val parent = this.parent as? IrClass
parent?.getExplicitExternalObjCClassBinaryName()?.let { return it }
parent?.let { return it.name.asString() }
edisongz marked this conversation as resolved.
Show resolved Hide resolved
}
return this.name.asString().removeSuffix("Meta")
}

private fun IrClass.getExplicitExternalObjCClassBinaryName() =
this.annotations.findAnnotation(externalObjCClassFqName)!!.getAnnotationValueOrNull<String>("binaryName")
this.annotations.findAnnotation(externalObjCClassFqName)?.getAnnotationValueOrNull<String>("binaryName")
edisongz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ internal class CodegenLlvmHelpers(private val generationState: NativeGenerationS

val Kotlin_Interop_DoesObjectConformToProtocol by lazyRtFunction
val Kotlin_Interop_IsObjectKindOfClass by lazyRtFunction
val Kotlin_Interop_IsObjectSameClass by lazyRtFunction

val Kotlin_ObjCExport_refToLocalObjC by lazyRtFunction
val Kotlin_ObjCExport_refToRetainedObjC by lazyRtFunction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,12 @@ internal class CodeGeneratorVisitor(
null
)

return if (dstClass.isObjCClass()) {
return if (dstClass.isCompanion) {
call(
llvm.Kotlin_Interop_IsObjectSameClass,
edisongz marked this conversation as resolved.
Show resolved Hide resolved
listOf(objCObject, genGetObjCClass(dstClass))
)
edisongz marked this conversation as resolved.
Show resolved Hide resolved
} else if (dstClass.isObjCClass()) {
if (dstClass.isInterface) {
val isMeta = if (dstClass.isObjCMetaClass()) kTrue else kFalse
call(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

touchFunction(Kotlin_Interop_DoesObjectConformToProtocol)
touchFunction(Kotlin_Interop_IsObjectKindOfClass)
touchFunction(Kotlin_Interop_IsObjectSameClass)

touchFunction(Kotlin_ObjCExport_refToLocalObjC)
touchFunction(Kotlin_ObjCExport_refToRetainedObjC)
Expand Down
1 change: 1 addition & 0 deletions kotlin-native/runtime/src/main/cpp/ObjCInteropUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ extern "C" {
id MissingInitImp(id self, SEL _cmd);
KBoolean Kotlin_Interop_DoesObjectConformToProtocol(id obj, void* prot, KBoolean isMeta);
KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* cls);
KBoolean Kotlin_Interop_IsObjectSameClass(id obj, void* cls);
}

#endif // KONAN_OBJC_INTEROP
Expand Down
4 changes: 4 additions & 0 deletions kotlin-native/runtime/src/main/cpp/ObjCInteropUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* cls) {
return [((id<NSObject>)obj) isKindOfClass:(Class)cls];
}

KBoolean Kotlin_Interop_IsObjectSameClass(id obj, void* cls) {
return obj == cls;
}

OBJ_GETTER((*Konan_ObjCInterop_getWeakReference_ptr), KRef ref) = nullptr;
void (*Konan_ObjCInterop_initWeakReference_ptr)(KRef ref, id objcPtr) = nullptr;

Expand Down
150 changes: 150 additions & 0 deletions native/native.tests/testData/codegen/cinterop/objc/kt65260.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
// TARGET_BACKEND: NATIVE
// DISABLE_NATIVE: isAppleTarget=false

// MODULE: cinterop
// FILE: conversion.def
language = Objective-C
headers = conversion.h
headerFilter = conversion.h

// FILE: conversion.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

__attribute__((objc_runtime_name("Foo")))
@interface A : NSObject
@end

__attribute__((objc_runtime_name("Bar")))
@interface B : A
@end

NS_ASSUME_NONNULL_END

// FILE: conversion.m
#import "conversion.h"

@implementation A
@end

@implementation B
@end

// MODULE: lib(cinterop)
// FILE: lib.kt
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
import conversion.*
import platform.darwin.*
import kotlinx.cinterop.*
import kotlin.test.*

class ANativeHeir : A() {
companion object
}

class BNativeHeir : B() {
companion object
}

fun testExternalObjCMetaClassCast() {
try {
BNativeHeir as A.Companion
} catch (e: Exception) {
assertTrue(e is ClassCastException)
}
edisongz marked this conversation as resolved.
Show resolved Hide resolved

try {
ANativeHeir as A.Companion
} catch (e: Exception) {
assertTrue(e is ClassCastException)
}

assertTrue(A is A.Companion)

val fooObjectClass: Any = A
val barObjectClass: Any = B
assertFalse(fooObjectClass is BMeta)
assertTrue(barObjectClass is AMeta)
}


// MODULE: main(cinterop, lib)
// FILE: main.kt
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
import kotlinx.cinterop.*
import platform.Foundation.*
import platform.darwin.*
import platform.objc.*
import kotlin.test.*

open class FooK {
companion object
}

class BarK: FooK() {
companion object
}

fun testAnyCast() {
try {
Any() as NSObject.Companion
} catch (e: Exception) {
edisongz marked this conversation as resolved.
Show resolved Hide resolved
assertTrue(e is ClassCastException)
}

try {
Any() as NSNumber.Companion
} catch (e: Exception) {
assertTrue(e is ClassCastException)
}
}

fun testMetaClassCast() {
assertFalse(Any() is NSObject.Companion)
assertFalse(Any() is NSNumber.Companion)

val nsObjectClass: Any = NSObject
assertFalse(nsObjectClass is NSNumberMeta)

try {
NSNumber as NSObject.Companion
} catch (e: Exception) {
assertTrue(e is ClassCastException)
}
assertTrue(NSData is NSData.Companion)
}

fun testNonObjCObjectTypeCast() {
val foo: Any = FooK
assertTrue(foo is FooK.Companion)

try {
BarK as FooK.Companion
edisongz marked this conversation as resolved.
Show resolved Hide resolved
} catch (e: Exception) {
assertTrue(e is ClassCastException)
}
}

@OptIn(kotlinx.cinterop.BetaInteropApi::class)
fun testObjCMetaClassTypeChecking() {
val nsObjectClass: ObjCClass = NSObject
val nsObjectMetaClass: ObjCClass = object_getClass(nsObjectClass)!!
assertFalse(nsObjectClass is NSObject.Companion)
assertTrue(nsObjectMetaClass is NSObject.Companion)
assertFalse(nsObjectClass == nsObjectMetaClass)
}

fun box(): String {
testAnyCast()
testMetaClassCast()
testExternalObjCMetaClassCast()
testNonObjCObjectTypeCast()
testObjCMetaClassTypeChecking()

return "OK"
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.