diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 46f615a3365..fd4bb835134 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -849,6 +849,13 @@ standaloneTest("cleaner_basic") { flags = ['-tr', '-opt-in=kotlin.native.internal.InternalForKotlinNative'] } +standaloneTest("collect_reference_field_values") { + enabled = (project.testTarget != 'wasm32') && // Cleaners need workers + !isNoopGC + source = "runtime/basic/collectReferenceFieldValues.kt" + flags = ['-tr', '-opt-in=kotlin.native.internal.InternalForKotlinNative'] +} + standaloneTest("cleaner_workers") { enabled = (project.testTarget != 'wasm32') && // Cleaners need workers !isNoopGC diff --git a/kotlin-native/backend.native/tests/runtime/basic/collectReferenceFieldValues.kt b/kotlin-native/backend.native/tests/runtime/basic/collectReferenceFieldValues.kt new file mode 100644 index 00000000000..b38ee9ec3e4 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/basic/collectReferenceFieldValues.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2022 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. + */ + +import kotlin.native.internal.* +import kotlin.test.* + +value class BoxInt(val x: Int) +value class BoxBoxInt(val x: BoxInt) +data class A(val x: Int) +value class BoxA(val x: A) +value class BoxBoxA(val x: BoxA) + + +class X( + val a: Int, + val b: List, + val c: IntArray, + val d: Array, + val e: Array, + val f: BoxInt, + val g: BoxBoxInt, + val h: A, + val i: BoxA, + val j: BoxBoxA, + val k: Any?, + val l: Any?, + val m: Any?, + val n: Any?, + val o: IntArray? +) { + val p by lazy { 1 } + lateinit var q: IntArray + lateinit var r: IntArray +} + +@Test +fun `big class with mixed values`() { + val lst = listOf(1, 2, 3) + val ia = intArrayOf(1, 2, 3) + val ia2 = intArrayOf(3, 4, 5) + val ai = arrayOf(1, 2, 3) + val astr: Array = arrayOf("123", "456", 1, 2, 3) + val bi = BoxInt(2) + val bbi = BoxBoxInt(BoxInt(3)) + val a1 = A(1) + val a2 = A(2) + val a3 = A(3) + val a6 = A(3) + val x = X( + 1, lst, ia, ai, astr, bi, bbi, + a1, BoxA(a2), BoxBoxA(BoxA(a3)), + 4, BoxInt(5), BoxA(a6), null, null + ) + x.r = ia2 + val fields = x.collectReferenceFieldValues() + assertEquals(12, fields.size) + // not using assertContains because of === + assertTrue(fields.any { it === lst }, "Should contain list $lst") + assertTrue(fields.any { it === ia }, "Should contain int array $ia") + assertTrue(fields.any { it === ia2 }, "Should contain int array $ia2") + assertTrue(fields.any { it === ai }, "Should contain array of int $ai") + assertTrue(fields.any { it === astr }, "Should contain array of string $astr") + assertTrue(fields.any { it === a1 }, "Should contain A(1)") + assertTrue(fields.any { it === a2 }, "Should contain A(2)") + assertTrue(fields.any { it === a3 }, "Should contain A(3)") + assertTrue(fields.any { it.toString().startsWith("Lazy value not initialized yet") }, "Should contain lazy delegate") + assertContains(fields, 4) + assertContains(fields, BoxInt(5)) + assertContains(fields, BoxA(a6))} + +@Test +fun `call on primitive`() { + assertEquals(1.collectReferenceFieldValues(), emptyList()) + assertEquals(123456.collectReferenceFieldValues(), emptyList()) +} + +@Test +fun `call on value over primitive class`() { + assertEquals(BoxInt(1).collectReferenceFieldValues(), emptyList()) + assertEquals(BoxBoxInt(BoxInt(1)).collectReferenceFieldValues(), emptyList()) +} + +@Test +fun `call on value class`() { + val a1 = A(1) + assertEquals(BoxA(a1).collectReferenceFieldValues(), listOf(a1)) + assertEquals(BoxBoxA(BoxA(a1)).collectReferenceFieldValues(), listOf(a1)) +} + +@Test +fun `call on String`() { + assertEquals("1234".collectReferenceFieldValues(), emptyList()) + assertEquals("".collectReferenceFieldValues(), emptyList()) +} + +@Test +fun `call on primitive array`() { + assertEquals(intArrayOf(1, 2, 3).collectReferenceFieldValues(), emptyList()) + assertEquals(intArrayOf().collectReferenceFieldValues(), emptyList()) +} + +@Test +fun `call on array`() { + assertEquals(arrayOf(1, 2, 3).collectReferenceFieldValues(), listOf(1, 2, 3)) + assertEquals(arrayOf(null, "10", null, 3).collectReferenceFieldValues(), listOf("10", 3)) + assertEquals(arrayOf().collectReferenceFieldValues(), emptyList()) + assertEquals(emptyArray().collectReferenceFieldValues(), emptyList()) + assertEquals(emptyArray().collectReferenceFieldValues(), emptyList()) +} \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp b/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp index a46f3dc5c53..8f741e9a595 100644 --- a/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp +++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.cpp @@ -16,6 +16,9 @@ #include "KAssert.h" #include "TypeInfo.h" +#include "Memory.h" +#include "Types.h" +#include "KString.h" extern "C" { @@ -39,4 +42,14 @@ InterfaceTableRecord const* LookupInterfaceTableRecord(InterfaceTableRecord cons return interfaceTable + l; } +RUNTIME_NOTHROW int Kotlin_internal_reflect_getObjectReferenceFieldsCount(ObjHeader* object) { + auto *info = object->type_info(); + if (info->IsArray()) return 0; + return info->objOffsetsCount_; +} + +RUNTIME_NOTHROW OBJ_GETTER(Kotlin_internal_reflect_getObjectReferenceFieldByIndex, ObjHeader* object, int index) { + RETURN_OBJ(*reinterpret_cast(reinterpret_cast(object) + object->type_info()->objOffsets_[index])); +} + } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Utils.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Utils.kt index d1d4435bc53..a977c41d1b6 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Utils.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Utils.kt @@ -19,3 +19,36 @@ internal fun debugDescription(kClass: KClass<*>, identity: Int): String { val identityStr = unsignedIdentity.toString(16) return "$className@$identityStr" } + +@GCUnsafeCall("Kotlin_internal_reflect_getObjectReferenceFieldsCount") +private external fun getObjectReferenceFieldsCount(o: Any): Int +@GCUnsafeCall("Kotlin_internal_reflect_getObjectReferenceFieldByIndex") +private external fun getObjectReferenceFieldByIndex(o: Any, index: Int): Any? + +/** + * Returns [List] of non-null reference fields of the object. + * + * This function is intended to be used for testing and debugging purposes. + * - It heavily relies on internal ABI details. No compatibility guarantees on exact list contents are provided. + * - Order and representation of object's fields are subject to change. + * - Performance characteristics of the implementation + * + * Limitations: + * - Primitives (unboxed [Int], [Double], [Float], etc.) are not included in the result. + * - Non-boxed value classes over primitives are not included in the result. + * - Non-boxed value classes over references are included in the result as the underlying reference type. + * - Synthetic fields (e.g. special fields for delegation) are included in the result. + * - There is no way to find which reference in the result corresponds to which field. + * + * For `Array` list of all its non-null elements is returned. + * For primitive arrays ([IntArray], [DoubleArray], [FloatArray], etc.) empty list is returned. + */ +@InternalForKotlinNative +public fun Any.collectReferenceFieldValues() : List { + return when { + this is Array<*> -> this.filterNotNull() + else -> (0 until getObjectReferenceFieldsCount(this@collectReferenceFieldValues)).mapNotNull { + getObjectReferenceFieldByIndex(this@collectReferenceFieldValues, it) + } + } +} \ No newline at end of file