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 @@ -1648,7 +1648,12 @@ internal class CodeGeneratorVisitor(
val resultNullBB = currentBlock.takeIf { !isAfterTerminator() }

positionAtEnd(bbInstanceOf)
val resultInstanceOf = onCheck(srcArg, if (isSuperClassCast) kTrue else genInstanceOfImpl(srcArg, dstClass))
val checkResult = if (isSuperClassCast) {
kTrue
} else {
if (!isCompanionParentClass(value, dstClass)) kFalse else genInstanceOfImpl(srcArg, dstClass)
}
val resultInstanceOf = onCheck(srcArg, checkResult)
val resultInstanceOfBB = currentBlock.also { require(!isAfterTerminator()) }


Expand All @@ -1667,6 +1672,16 @@ internal class CodeGeneratorVisitor(
}
}

private fun isCompanionParentClass(value: IrTypeOperatorCall, dstClass: IrClass): Boolean {
var result = true
if (dstClass.isCompanion) {
(dstClass.parent as? IrClass)?.symbol?.let {
result = value.argument.type == it
}
}
return result
}
edisongz marked this conversation as resolved.
Show resolved Hide resolved

private fun genInstanceOfImpl(obj: LLVMValueRef, dstClass: IrClass) = with(functionGenerationContext) {
if (dstClass.defaultType.isObjCObjectType()) {
genInstanceOfObjC(obj, dstClass)
Expand Down
120 changes: 120 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,120 @@
import java.lang.Exception

/*
* 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 platform.Foundation.*
import platform.darwin.*
import kotlin.test.*

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 box(): String {
testAnyCast()
testMetaClassCast()
testExternalObjCMetaClassCast()

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.