Fix KT-26478: Objective-C-objects-class-name-is-null-in-ClassCastExceptions-message

#KT-26478 Fixed

Merge-request: KT-MR-6405
Merged-by: Vladimir Sukharev <Vladimir.Sukharev@jetbrains.com>
This commit is contained in:
Vladimir Sukharev
2022-06-09 12:57:11 +00:00
committed by Space
parent a31d383b4d
commit 83d7524232
7 changed files with 144 additions and 5 deletions
@@ -0,0 +1,21 @@
import kotlin.test.*
import objcTests.*
class KT26478
@Test
fun testKT26478compiletime() {
val exception = assertFailsWith<ClassCastException> {
NSBundle() as KT26478
}
assertEquals("class NSBundle cannot be cast to class KT26478", exception.message)
}
val bundle26478:Any = NSBundle()
@Test
fun testKT26478runtime() {
val exception = assertFailsWith<ClassCastException> {
bundle26478 as KT26478
}
assertEquals("class NSBundle cannot be cast to class KT26478", exception.message)
}
@@ -7,3 +7,12 @@ fun testKT35544() {
}
assertEquals("class kotlin.Int cannot be cast to class objcTests.NSString", exception.message)
}
val bundle35544: Any = NSBundle()
@Test
fun testKT35544runtime() {
val exception = assertFailsWith<TypeCastException> {
bundle35544 as NSString
}
assertEquals("class NSBundle cannot be cast to class objcTests.NSString", exception.message)
}
@@ -134,6 +134,24 @@ void DisposeCString(char* cstring) {
if (cstring) std_support::free(cstring);
}
ObjHeader* CreatePermanentStringFromCString(const char* nullTerminatedUTF8) {
const char* end = nullTerminatedUTF8 + strlen(nullTerminatedUTF8);
size_t count = utf8::with_replacement::utf16_length(nullTerminatedUTF8, end);
size_t headerSize = alignUp(sizeof(ArrayHeader), alignof(char16_t));
size_t arraySize = headerSize + count * sizeof(char16_t);
ArrayHeader* header = (ArrayHeader*)std_support::calloc(arraySize, 1);
header->obj()->typeInfoOrMeta_ = setPointerBits((TypeInfo *)theStringTypeInfo, OBJECT_TAG_PERMANENT_CONTAINER);
header->count_ = count;
utf8::with_replacement::utf8to16(nullTerminatedUTF8, end, CharArrayAddressOfElementAt(header, 0));
return header->obj();
}
void FreePermanentStringForTests(ArrayHeader* header) {
std_support::free(header);
}
// String.kt
OBJ_GETTER(Kotlin_String_replace, KString thiz, KChar oldChar, KChar newChar) {
auto count = thiz->count_;
@@ -30,6 +30,8 @@ OBJ_GETTER(CreateStringFromCString, const char* cstring);
OBJ_GETTER(CreateStringFromUtf8, const char* utf8, uint32_t lengthBytes);
char* CreateCStringFromString(KConstRef kstring);
void DisposeCString(char* cstring);
ObjHeader* CreatePermanentStringFromCString(const char* nullTerminatedUTF8);
void FreePermanentStringForTests(ArrayHeader* header); // to make ASAN happy, in hostRuntimeTests call FreePermanentStringForTests() after CreatePermanentStringFromCString()
OBJ_GETTER(StringFromUtf8Buffer, const char* start, size_t size);
@@ -0,0 +1,87 @@
/*
* Copyright 2022 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.
*/
#include "KString.h"
#include "Natives.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "std_support/CStdlib.hpp"
using namespace kotlin;
void checkContentsEquality(ArrayHeader* actual, const char16_t* expected) {
EXPECT_THAT(actual->count_, std::char_traits<char16_t>::length(expected));
for (size_t i=0; i<actual->count_; i++) {
EXPECT_THAT(*CharArrayAddressOfElementAt(actual, i), expected[i]);
}
EXPECT_THAT(actual->obj()->permanent(), true);
}
TEST(KStringTest, CreatePermanentStringFromCString_ascii) {
const char* ascii = "Ascii";
EXPECT_THAT(strlen(ascii), 5);
const char16_t* expected = u"Ascii";
EXPECT_THAT(std::char_traits<char16_t>::length(expected), 5);
auto actual = CreatePermanentStringFromCString(ascii)->array();
checkContentsEquality(actual, expected);
FreePermanentStringForTests(actual); // to prevent Address Sanitizer test failures, permanently allocated strings must be deallocated before test end
}
TEST(KStringTest, CreatePermanentStringFromCString_misc) {
const char* non_ascii = "-£öü²ソニーΣℜ∫♣‰€";
EXPECT_THAT(strlen(non_ascii), 35);
const char16_t* expected = u"-£öü²ソニーΣℜ∫♣‰€";
EXPECT_THAT(std::char_traits<char16_t>::length(expected), 14);
auto actual = CreatePermanentStringFromCString(non_ascii)->array();
checkContentsEquality(actual, expected);
FreePermanentStringForTests(actual); // to prevent Address Sanitizer test failures, permanently allocated strings must be deallocated before test end
}
TEST(KStringTest, CreatePermanentStringFromCString_surrogates) {
const char* surrogates = "😃𓄀🌀🐀𝜀";
EXPECT_THAT(strlen(surrogates), 20);
const char16_t* expected = u"😃𓄀🌀🐀𝜀";
EXPECT_THAT(std::char_traits<char16_t>::length(expected), 10);
auto actual = CreatePermanentStringFromCString(surrogates)->array();
checkContentsEquality(actual, expected);
FreePermanentStringForTests(actual); // to prevent Address Sanitizer test failures, permanently allocated strings must be deallocated before test end
}
TEST(KStringTest, CreatePermanentStringFromCString_empty) {
const char* empty = "";
EXPECT_THAT(strlen(empty), 0);
const char16_t* expected = u"";
EXPECT_THAT(std::char_traits<char16_t>::length(expected), 0);
auto actual = CreatePermanentStringFromCString(empty)->array();
checkContentsEquality(actual, expected);
FreePermanentStringForTests(actual); // to prevent Address Sanitizer test failures, permanently allocated strings must be deallocated before test end
}
TEST(KStringTest, CreatePermanentStringFromCString_impossible) {
const char* impossible = "\xff";
EXPECT_THAT(strlen(impossible), 1);
const char16_t* expected = u"\xfffd";
EXPECT_THAT(std::char_traits<char16_t>::length(expected), 1);
auto actual = CreatePermanentStringFromCString(impossible)->array();
checkContentsEquality(actual, expected);
FreePermanentStringForTests(actual); // to prevent Address Sanitizer test failures, permanently allocated strings must be deallocated before test end
}
TEST(KStringTest, CreatePermanentStringFromCString_overlong) {
const char* overlong = "\xc0\xaf";
EXPECT_THAT(strlen(overlong), 2);
const char16_t* expected = u"\xfffd\xfffd";
EXPECT_THAT(std::char_traits<char16_t>::length(expected), 2);
auto actual = CreatePermanentStringFromCString(overlong)->array();
checkContentsEquality(actual, expected);
FreePermanentStringForTests(actual); // to prevent Address Sanitizer test failures, permanently allocated strings must be deallocated before test end
}
@@ -16,8 +16,8 @@
#import "Types.h"
#import "Memory.h"
#include "Natives.h"
#include "ObjCInterop.h"
#include "KString.h"
#if KONAN_OBJC_INTEROP
@@ -43,6 +43,7 @@
#import "Runtime.h"
#import "Mutex.hpp"
#import "Exceptions.h"
#import "Natives.h"
#include "std_support/CStdlib.hpp"
#include "std_support/Map.hpp"
#include "std_support/String.hpp"
@@ -727,6 +728,7 @@ static void buildITable(TypeInfo* result, const std_support::map<ClassId, std_su
}
static const TypeInfo* createTypeInfo(
const char* className,
const TypeInfo* superType,
const std_support::vector<const TypeInfo*>& superInterfaces,
const std_support::vector<VTableElement>& vtable,
@@ -785,7 +787,7 @@ static const TypeInfo* createTypeInfo(
}
result->packageName_ = nullptr;
result->relativeName_ = nullptr; // TODO: add some info.
result->relativeName_ = CreatePermanentStringFromCString(className);
result->writableInfo_ = (WritableTypeInfo*)std_support::calloc(1, sizeof(WritableTypeInfo));
for (size_t i = 0; i < vtable.size(); ++i) result->vtable()[i] = vtable[i];
@@ -994,7 +996,7 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co
// TODO: consider forbidding the class being abstract.
const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, interfaceVTables,
const TypeInfo* result = createTypeInfo(class_getName(clazz), superType, addedInterfaces, vtable, interfaceVTables,
superITable, superITableSize, itableEqualsSuper, fieldsInfo);
// TODO: it will probably never be requested, since such a class can't be instantiated in Kotlin.
@@ -38,8 +38,8 @@ internal class KClassImpl<T : Any>(private val typeInfo: NativePtr) : KClass<T>
internal val fullName: String?
get() {
val relativeName = getRelativeName(typeInfo, false) ?: return null
val packageName = getPackageName(typeInfo, false)!!
return if (packageName.isEmpty()) relativeName else "$packageName.$relativeName"
val packageName: String? = getPackageName(typeInfo, false)
return if (packageName?.isEmpty() ?: true) relativeName else "$packageName.$relativeName"
}
internal fun findAssociatedObjectImpl(key: KClassImpl<*>): Any? =