diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/kt26478.kt b/kotlin-native/backend.native/tests/interop/objc/tests/kt26478.kt new file mode 100644 index 00000000000..a5b55b28683 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/objc/tests/kt26478.kt @@ -0,0 +1,21 @@ +import kotlin.test.* +import objcTests.* + +class KT26478 + +@Test +fun testKT26478compiletime() { + val exception = assertFailsWith { + NSBundle() as KT26478 + } + assertEquals("class NSBundle cannot be cast to class KT26478", exception.message) +} + +val bundle26478:Any = NSBundle() +@Test +fun testKT26478runtime() { + val exception = assertFailsWith { + bundle26478 as KT26478 + } + assertEquals("class NSBundle cannot be cast to class KT26478", exception.message) +} diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/kt35544.kt b/kotlin-native/backend.native/tests/interop/objc/tests/kt35544.kt index 1c3b06f0e44..df789abcbb9 100644 --- a/kotlin-native/backend.native/tests/interop/objc/tests/kt35544.kt +++ b/kotlin-native/backend.native/tests/interop/objc/tests/kt35544.kt @@ -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 { + bundle35544 as NSString + } + assertEquals("class NSBundle cannot be cast to class objcTests.NSString", exception.message) +} diff --git a/kotlin-native/runtime/src/main/cpp/KString.cpp b/kotlin-native/runtime/src/main/cpp/KString.cpp index bad794702c1..5019aeb9692 100644 --- a/kotlin-native/runtime/src/main/cpp/KString.cpp +++ b/kotlin-native/runtime/src/main/cpp/KString.cpp @@ -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_; diff --git a/kotlin-native/runtime/src/main/cpp/KString.h b/kotlin-native/runtime/src/main/cpp/KString.h index e970d449af1..17b057369ad 100644 --- a/kotlin-native/runtime/src/main/cpp/KString.h +++ b/kotlin-native/runtime/src/main/cpp/KString.h @@ -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); diff --git a/kotlin-native/runtime/src/main/cpp/KStringTest.cpp b/kotlin-native/runtime/src/main/cpp/KStringTest.cpp new file mode 100644 index 00000000000..c3fa17130ce --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/KStringTest.cpp @@ -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::length(expected)); + for (size_t i=0; icount_; 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::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::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::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::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::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::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 +} diff --git a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm index ff745780b03..70713e62c4d 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm @@ -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& superInterfaces, const std_support::vector& 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. diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/KClassImpl.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/KClassImpl.kt index 4bbd014dba5..df75b3821a1 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/KClassImpl.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/KClassImpl.kt @@ -38,8 +38,8 @@ internal class KClassImpl(private val typeInfo: NativePtr) : KClass 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? =