From d42ff069f64d382584dc1f13c8bfcd3f1c67f188 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Thu, 13 May 2021 17:57:59 +0300 Subject: [PATCH] [Native][IR] Move verbose RTTI for local classes and anonymous objects ^KT-45304 --- .../jetbrains/kotlin/backend/konan/Context.kt | 12 + .../backend/konan/KonanLoweringPhases.kt | 10 +- .../kotlin/backend/konan/ToplevelPhases.kt | 1 + .../backend/konan/llvm/RTTIGenerator.kt | 63 +- .../konan/lower/FunctionReferenceLowering.kt | 3 + .../lower/NativeInventNamesForLocalClasses.kt | 21 + .../tests/codegen/kclass/kclass0.kt | 588 +++++++++++++++++- kotlin-native/runtime/src/main/cpp/TypeInfo.h | 9 +- kotlin-native/runtime/src/main/cpp/Types.cpp | 18 +- .../kotlin/native/internal/KClassImpl.kt | 32 +- 10 files changed, 680 insertions(+), 77 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInventNamesForLocalClasses.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index bebd8b10cea..840e0c764a8 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -258,6 +258,18 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { lazyValues[member] = newValue } + private val localClassNames = mutableMapOf() + + fun getLocalClassName(container: IrAttributeContainer): String? = localClassNames[container.attributeOwnerId] + + fun putLocalClassName(container: IrAttributeContainer, name: String) { + localClassNames[container.attributeOwnerId] = name + } + + fun copyLocalClassName(source: IrAttributeContainer, destination: IrAttributeContainer) { + getLocalClassName(source)?.let { name -> putLocalClassName(destination, name) } + } + val reflectionTypes: KonanReflectionTypes by lazy(PUBLICATION) { KonanReflectionTypes(moduleDescriptor, KonanFqNames.internalPackageName) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt index 51dd4bbb567..9afc54b92f6 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt @@ -121,6 +121,12 @@ internal val sharedVariablesPhase = makeKonanModuleLoweringPhase( prerequisite = setOf(lateinitPhase) ) +internal val inventNamesForLocalClasses = makeKonanModuleLoweringPhase( + ::NativeInventNamesForLocalClasses, + name = "InventNamesForLocalClasses", + description = "Invent names for local classes and anonymous objects" +) + internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase( lower = object : SameTypeCompilerPhase { override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: IrModuleFragment): IrModuleFragment { @@ -138,7 +144,7 @@ internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase( }, name = "ExtractLocalClassesFromInlineBodies", description = "Extraction of local classes from inline bodies", - prerequisite = setOf(sharedVariablesPhase), + prerequisite = setOf(sharedVariablesPhase), // TODO: add "soft" dependency on inventNamesForLocalClasses nlevels = 0, actions = modulePhaseActions ) @@ -217,7 +223,7 @@ internal val localFunctionsPhase = makeKonanFileOpPhase( }, name = "LocalFunctions", description = "Local function lowering", - prerequisite = setOf(sharedVariablesPhase) + prerequisite = setOf(sharedVariablesPhase) // TODO: add "soft" dependency on inventNamesForLocalClasses ) internal val tailrecPhase = makeKonanFileLoweringPhase( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 55970dd943e..75b79d4e9a0 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -192,6 +192,7 @@ internal val allLoweringsPhase = NamedCompilerPhase( arrayConstructorPhase then lateinitPhase then sharedVariablesPhase then + inventNamesForLocalClasses then extractLocalClassesFromInlineBodies then inlinePhase then provisionalFunctionExpressionPhase then diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 4d86dac810e..d7c88d65325 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod +import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering.Companion.isLoweredFunctionReference import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.symbols.isPublicApi @@ -253,7 +254,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { interfaceTableSize, interfaceTablePtr, reflectionInfo.packageName, reflectionInfo.relativeName, - flagsFromClass(irClass), + flagsFromClass(irClass) or reflectionInfo.reflectionFlags, context.getLayoutBuilder(irClass).classId, llvmDeclarations.writableTypeInfoGlobal?.pointer, associatedObjects = genAssociatedObjects(irClass) @@ -496,8 +497,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { val objOffsetsPtr = staticData.placeGlobalConstArray("", int32Type, objOffsets) val objOffsetsCount = objOffsets.size - val reflectionInfo = ReflectionInfo(null, null) - val writableTypeInfoType = runtime.writableTypeInfoType val writableTypeInfo = if (writableTypeInfoType == null) { null @@ -541,8 +540,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount, interfaces = interfacesPtr, interfacesCount = interfaces.size, interfaceTableSize = interfaceTableSize, interfaceTable = interfaceTablePtr, - packageName = reflectionInfo.packageName, - relativeName = reflectionInfo.relativeName, + packageName = ReflectionInfo.EMPTY.packageName, + relativeName = ReflectionInfo.EMPTY.relativeName, flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0), classId = typeHierarchyInfo.classIdLo, writableTypeInfo = writableTypeInfo, @@ -557,21 +556,46 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { private val OverriddenFunctionInfo.implementation get() = getImplementation(context) - data class ReflectionInfo(val packageName: String?, val relativeName: String?) - - private fun getReflectionInfo(irClass: IrClass): ReflectionInfo = when { - irClass.isAnonymousObject -> ReflectionInfo(packageName = null, relativeName = null) - - irClass.isLocal -> ReflectionInfo(packageName = null, relativeName = irClass.name.asString()) - - else -> ReflectionInfo( - packageName = irClass.findPackage().fqNameForIrSerialization.asString(), - relativeName = generateSequence(irClass) { it.parent as? IrClass } - .toList().reversed() - .joinToString(".") { it.name.asString() } - ) + data class ReflectionInfo(val packageName: String?, val relativeName: String?, val reflectionFlags: Int) { + companion object { + val EMPTY = ReflectionInfo(null, null, 0) + } } + private fun getReflectionInfo(irClass: IrClass): ReflectionInfo { + val packageName: String = irClass.findPackage().fqName.asString() // Compute and store package name in TypeInfo anyways. + val relativeName: String? + val flags: Int + + when { + irClass.isAnonymousObject -> { + relativeName = context.getLocalClassName(irClass) + flags = 0 // Forbid to use package and relative names in KClass.[simpleName|qualifiedName]. + } + irClass.isLocal -> { + relativeName = context.getLocalClassName(irClass) + flags = TF_REFLECTION_SHOW_REL_NAME // Only allow relative name to be used in KClass.simpleName. + } + isLoweredFunctionReference(irClass) -> { + // TODO: might return null so use fallback here, to be fixed in KT-47194 + relativeName = context.getLocalClassName(irClass) ?: generateDefaultRelativeName(irClass) + flags = 0 // Forbid to use package and relative names in KClass.[simpleName|qualifiedName]. + } + else -> { + relativeName = generateDefaultRelativeName(irClass) + flags = TF_REFLECTION_SHOW_PKG_NAME or TF_REFLECTION_SHOW_REL_NAME // Allow both package and relative names to be used in + // KClass.[simpleName|qualifiedName]. + } + } + + return ReflectionInfo(packageName, relativeName, flags) + } + + private fun generateDefaultRelativeName(irClass: IrClass) = + generateSequence(irClass) { it.parent as? IrClass } + .toList().reversed() + .joinToString(".") { it.name.asString() } + fun dispose() { debugRuntimeOrNull?.let { LLVMDisposeModule(it) } } @@ -586,4 +610,5 @@ private const val TF_LEAK_DETECTOR_CANDIDATE = 16 private const val TF_SUSPEND_FUNCTION = 32 private const val TF_HAS_FINALIZER = 64 private const val TF_HAS_FREEZE_HOOK = 128 - +private const val TF_REFLECTION_SHOW_PKG_NAME = 256 +private const val TF_REFLECTION_SHOW_REL_NAME = 512 diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt index 399e7786521..ed8667c910e 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionReferenceLowering.kt @@ -218,6 +218,9 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass ).apply { parent = this@FunctionReferenceBuilder.parent createParameterDeclarations() + + // copy the generated name for IrClass, partially solves KT-47194 + context.copyLocalClassName(functionReference, this) } private val functionReferenceThis = functionReferenceClass.thisReceiver!! diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInventNamesForLocalClasses.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInventNamesForLocalClasses.kt new file mode 100644 index 00000000000..d5096e2b86a --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/NativeInventNamesForLocalClasses.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2021 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. + */ + +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.lower.InventNamesForLocalClasses +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer +import org.jetbrains.kotlin.ir.declarations.IrClass + +// TODO: consider replacing '$' by another delimeter that can't be used in class name specified with backticks (``) +internal class NativeInventNamesForLocalClasses(val context: Context) : InventNamesForLocalClasses(allowTopLevelCallables = true) { + override fun computeTopLevelClassName(clazz: IrClass): String = clazz.name.asString() + override fun sanitizeNameIfNeeded(name: String) = name + + override fun putLocalClassName(declaration: IrAttributeContainer, localClassName: String) { + context.putLocalClassName(declaration, localClassName) + } +} diff --git a/kotlin-native/backend.native/tests/codegen/kclass/kclass0.kt b/kotlin-native/backend.native/tests/codegen/kclass/kclass0.kt index 88875f29edc..aca1ac6d360 100644 --- a/kotlin-native/backend.native/tests/codegen/kclass/kclass0.kt +++ b/kotlin-native/backend.native/tests/codegen/kclass/kclass0.kt @@ -6,6 +6,7 @@ package codegen.kclass.kclass0 import kotlin.test.* +import kotlin.coroutines.* import kotlin.reflect.KClass @Test fun runTest() { @@ -13,36 +14,556 @@ import kotlin.reflect.KClass } fun main(args: Array) { - checkClass(Any::class, "kotlin.Any", "Any", Any(), null) - checkClass(Int::class, "kotlin.Int", "Int", 42, "17") - checkClass(String::class, "kotlin.String", "String", "17", 42) - checkClass(RootClass::class, "codegen.kclass.kclass0.RootClass", "RootClass", RootClass(), Any()) - checkClass(RootClass.Nested::class, "codegen.kclass.kclass0.RootClass.Nested", "Nested", RootClass.Nested(), Any()) + checkBasics() + checkLocalClasses(args) + checkAnonymousObjects(args) + checkAnonymousObjectsAssignedToProperty(args) + checkFunctionReferences() + checkInterfaces() + checkEtc() +} - class Local { +private fun checkBasics() { + checkClass( + clazz = Any::class, + expectedQualifiedName = "kotlin.Any", + expectedSimpleName = "Any", + expectedToStringName = "class kotlin.Any", + expectedInstance = Any(), + expectedNotInstance = null + ) + checkClass( + clazz = Int::class, + expectedQualifiedName = "kotlin.Int", + expectedSimpleName = "Int", + expectedToStringName = "class kotlin.Int", + expectedInstance = 42, + expectedNotInstance = "17" + ) + checkClass( + clazz = String::class, + expectedQualifiedName = "kotlin.String", + expectedSimpleName = "String", + expectedToStringName = "class kotlin.String", + expectedInstance = "17", + expectedNotInstance = 42 + ) + checkClass( + clazz = TopLevel::class, + expectedQualifiedName = "codegen.kclass.kclass0.TopLevel", + expectedSimpleName = "TopLevel", + expectedToStringName = "class codegen.kclass.kclass0.TopLevel", + expectedInstance = TopLevel(), + expectedNotInstance = Any() + ) + checkClass( + clazz = TopLevel.Nested::class, + expectedQualifiedName = "codegen.kclass.kclass0.TopLevel.Nested", + expectedSimpleName = "Nested", + expectedToStringName = "class codegen.kclass.kclass0.TopLevel.Nested", + expectedInstance = TopLevel.Nested(), + expectedNotInstance = Any() + ) +} + +private fun checkLocalClasses(args: Array) { + // Local class. + class Local0 { val captured = args - inner class Inner } - checkClass(Local::class, null, "Local", Local(), Any()) - checkClass(Local.Inner::class, null, "Inner", Local().Inner(), Any()) + checkClass( + clazz = Local0::class, + expectedQualifiedName = null, + expectedSimpleName = "Local0", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$Local0", + expectedInstance = Local0(), + expectedNotInstance = Any() + ) + checkClass( + clazz = Local0.Inner::class, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$Local0\$Inner", + expectedInstance = Local0().Inner(), + expectedNotInstance = Any() + ) + // Local class inside of lambda. + ;{ + class Local { + val captured = args + inner class Inner + } + checkClass( + clazz = Local::class, + expectedQualifiedName = null, + expectedSimpleName = "Local", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$1\$Local", + expectedInstance = Local(), + expectedNotInstance = Any() + ) + checkClass( + clazz = Local.Inner::class, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$1\$Local\$Inner", + expectedInstance = Local().Inner(), + expectedNotInstance = Any() + ) + }.invoke() + + // Local class inside of non-inlined lambda. + runNonInlining { + class Local { + val captured = args + inner class Inner + } + checkClass( + clazz = Local::class, + expectedQualifiedName = null, + expectedSimpleName = "Local", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$2\$Local", + expectedInstance = Local(), + expectedNotInstance = Any() + ) + checkClass( + clazz = Local.Inner::class, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$2\$Local\$Inner", + expectedInstance = Local().Inner(), + expectedNotInstance = Any() + ) + } + + // Local class inside of inlined lambda. + run { + class Local { + val captured = args + inner class Inner + } + checkClass( + clazz = Local::class, + expectedQualifiedName = null, + expectedSimpleName = "Local", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$3\$Local", + expectedInstance = Local(), + expectedNotInstance = Any() + ) + checkClass( + clazz = Local.Inner::class, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$3\$Local\$Inner", + expectedInstance = Local().Inner(), + expectedNotInstance = Any() + ) + } + + // Local class inside of suspend lambda. + suspend { + class Local { + val captured = args + inner class Inner + } + checkClass( + clazz = Local::class, + expectedQualifiedName = null, + expectedSimpleName = "Local", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$4\$Local", + expectedInstance = Local(), + expectedNotInstance = Any() + ) + checkClass( + clazz = Local.Inner::class, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$4\$Local\$Inner", + expectedInstance = Local().Inner(), + expectedNotInstance = Any() + ) + }.runCoroutine() + + // Local class inside of suspend function. + suspend fun suspendFunWithLocalClass() { + class Local { + val captured = args + inner class Inner + } + checkClass( + clazz = Local::class, + expectedQualifiedName = null, + expectedSimpleName = "Local", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$suspendFunWithLocalClass\$Local", + expectedInstance = Local(), + expectedNotInstance = Any() + ) + checkClass( + clazz = Local.Inner::class, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkLocalClasses\$suspendFunWithLocalClass\$Local\$Inner", + expectedInstance = Local().Inner(), + expectedNotInstance = Any() + ) + } + ::suspendFunWithLocalClass.runCoroutine() +} + +private fun checkAnonymousObjects(args: Array) { + // Anonymous object. + with(object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + }) { + checkClass( + clazz = this::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$1", + expectedInstance = this, + expectedNotInstance = Any() + ) + checkClass( + clazz = this.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$1\$Inner", + expectedInstance = this.Inner(), + expectedNotInstance = Any() + ) + } + + // Anonymous object inside of lambda. + ;{ + // $2 goes for this lambda itself. + + with(object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + }) { + checkClass( + clazz = this::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$3\$1", + expectedInstance = this, + expectedNotInstance = Any() + ) + checkClass( + clazz = this.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$3\$1\$Inner", + expectedInstance = this.Inner(), + expectedNotInstance = Any() + ) + } + }.invoke() + + // Anonymous object inside of non-inlined lambda. + runNonInlining { + with(object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + }) { + checkClass( + clazz = this::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$4\$1", + expectedInstance = this, + expectedNotInstance = Any() + ) + checkClass( + clazz = this.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$4\$1\$Inner", + expectedInstance = this.Inner(), + expectedNotInstance = Any() + ) + } + } + + // Anonymous object inside of inlined lambda. + run { + with(object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + }) { + checkClass( + clazz = this::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$5\$1", + expectedInstance = this, + expectedNotInstance = Any() + ) + checkClass( + clazz = this.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$5\$1\$Inner", + expectedInstance = this.Inner(), + expectedNotInstance = Any() + ) + } + } + + // Anonymous object inside of suspend lambda. + suspend { + with(object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + }) { + checkClass( + clazz = this::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$6\$1", + expectedInstance = this, + expectedNotInstance = Any() + ) + checkClass( + clazz = this.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$6\$1\$Inner", + expectedInstance = this.Inner(), + expectedNotInstance = Any() + ) + } + }.runCoroutine() + + // Anonymous object inside of suspend function. + suspend fun suspendFunWithAnonymousObject() { + // $1 goes for lambda created in `::suspendFunWithAnonymousObject.runCoroutine()` call. + + with(object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + }) { + checkClass( + clazz = this::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$suspendFunWithAnonymousObject\$2", + expectedInstance = this, + expectedNotInstance = Any() + ) + checkClass( + clazz = this.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjects\$suspendFunWithAnonymousObject\$2\$Inner", + expectedInstance = this.Inner(), + expectedNotInstance = Any() + ) + } + } + ::suspendFunWithAnonymousObject.runCoroutine() +} + +private fun checkAnonymousObjectsAssignedToProperty(args: Array) { + // Anonymous object. val obj = object : Any() { val captured = args - inner class Inner val innerKClass = Inner::class } - checkClass(obj::class, null, null, obj, Any()) - checkClass(obj.innerKClass, null, "Inner", obj.Inner(), Any()) + checkClass( + clazz = obj::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$obj\$1", + expectedInstance = obj, + expectedNotInstance = Any() + ) + checkClass( + clazz = obj.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$obj\$1\$Inner", + expectedInstance = obj.Inner(), + expectedNotInstance = Any() + ) - // Interfaces: - checkClass(Comparable::class, "kotlin.Comparable", "Comparable", 42, Any()) - checkClass(Interface::class, "codegen.kclass.kclass0.Interface", "Interface", object : Interface {}, Any()) + // Anonymous object inside of lambda. + ;{ + val obj = object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + } + checkClass( + clazz = obj::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$1\$obj\$1", + expectedInstance = obj, + expectedNotInstance = Any() + ) + checkClass( + clazz = obj.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$1\$obj\$1\$Inner", + expectedInstance = obj.Inner(), + expectedNotInstance = Any() + ) + }.invoke() + // Anonymous object inside of non-inlined lambda. + runNonInlining { + val obj = object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + } + checkClass( + clazz = obj::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$2\$obj\$1", + expectedInstance = obj, + expectedNotInstance = Any() + ) + checkClass( + clazz = obj.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$2\$obj\$1\$Inner", + expectedInstance = obj.Inner(), + expectedNotInstance = Any() + ) + } + + // Anonymous object inside of inlined lambda. + run { + val obj = object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + } + checkClass( + clazz = obj::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$3\$obj\$1", + expectedInstance = obj, + expectedNotInstance = Any() + ) + checkClass( + clazz = obj.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$3\$obj\$1\$Inner", + expectedInstance = obj.Inner(), + expectedNotInstance = Any() + ) + } + + // Anonymous object inside of suspend lambda. + suspend { + val obj = object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + } + checkClass( + clazz = obj::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$4\$obj\$1", + expectedInstance = obj, + expectedNotInstance = Any() + ) + checkClass( + clazz = obj.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$4\$obj\$1\$Inner", + expectedInstance = obj.Inner(), + expectedNotInstance = Any() + ) + }.runCoroutine() + + // Anonymous object inside of suspend function. + suspend fun suspendFunWithAnonymousObject() { + // $1 goes for lambda created in `::suspendFunWithAnonymousObject.runCoroutine()` call. + + val obj = object : Any() { + val captured = args + inner class Inner + val innerKClass = Inner::class + } + checkClass( + clazz = obj::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$suspendFunWithAnonymousObject\$obj\$1", + expectedInstance = obj, + expectedNotInstance = Any() + ) + checkClass( + clazz = obj.innerKClass, + expectedQualifiedName = null, + expectedSimpleName = "Inner", + expectedToStringName = "class codegen.kclass.kclass0.checkAnonymousObjectsAssignedToProperty\$suspendFunWithAnonymousObject\$obj\$1\$Inner", + expectedInstance = obj.Inner(), + expectedNotInstance = Any() + ) + } + ::suspendFunWithAnonymousObject.runCoroutine() +} + +private fun checkFunctionReferences() { + // TODO add tests for lambdas and anonymous functions, see KT-47194 + fun foo() = Unit + val ref = ::foo + checkClass( + clazz = ref::class, + expectedQualifiedName = null, + expectedSimpleName = null, + expectedToStringName = "class codegen.kclass.kclass0.checkFunctionReferences\$ref\$1", + expectedInstance = ref, + expectedNotInstance = Any() + ) +} + +private fun checkInterfaces() { + checkClass( + clazz = Comparable::class, + expectedQualifiedName = "kotlin.Comparable", + expectedSimpleName = "Comparable", + expectedToStringName = "class kotlin.Comparable", + expectedInstance = 42, + expectedNotInstance = Any() + ) + checkClass( + clazz = Interface::class, + expectedQualifiedName = "codegen.kclass.kclass0.Interface", + expectedSimpleName = "Interface", + expectedToStringName = "class codegen.kclass.kclass0.Interface", + expectedInstance = object : Interface {}, + expectedNotInstance = Any() + ) +} + +private fun checkEtc() { checkInstanceClass(Any(), Any::class) checkInstanceClass(42, Int::class) - assert(42::class == Int::class) + assertEquals(42::class, Int::class) + + class Local checkReifiedClass(Int::class) checkReifiedClass(Int::class) @@ -53,36 +574,43 @@ fun main(args: Array) { checkReifiedClass2(Any::class) checkReifiedClass(Local::class) checkReifiedClass2(Local::class) - checkReifiedClass(RootClass::class) - checkReifiedClass2(RootClass::class) + checkReifiedClass(TopLevel::class) + checkReifiedClass2(TopLevel::class) } -class RootClass { + +class TopLevel { class Nested } interface Interface -fun checkClass( +private fun checkClass( clazz: KClass<*>, - expectedQualifiedName: String?, expectedSimpleName: String?, + expectedQualifiedName: String?, expectedSimpleName: String?, expectedToStringName: String, expectedInstance: Any, expectedNotInstance: Any? ) { - assert(clazz.qualifiedName == expectedQualifiedName) - assert(clazz.simpleName == expectedSimpleName) + assertEquals(clazz.qualifiedName, expectedQualifiedName) + assertEquals(clazz.simpleName, expectedSimpleName) + assertEquals(clazz.toString(), expectedToStringName) - assert(clazz.isInstance(expectedInstance)) - if (expectedNotInstance != null) assert(!clazz.isInstance(expectedNotInstance)) + assertTrue(clazz.isInstance(expectedInstance)) + if (expectedNotInstance != null) assertTrue(!clazz.isInstance(expectedNotInstance)) } -fun checkInstanceClass(instance: Any, clazz: KClass<*>) { - assert(instance::class == clazz) +private fun checkInstanceClass(instance: Any, clazz: KClass<*>) { + assertEquals(instance::class, clazz) } -inline fun checkReifiedClass(expectedClass: KClass<*>) { - assert(T::class == expectedClass) +private inline fun checkReifiedClass(expectedClass: KClass<*>) { + assertEquals(T::class, expectedClass) } -inline fun checkReifiedClass2(expectedClass: KClass<*>) { +private inline fun checkReifiedClass2(expectedClass: KClass<*>) { checkReifiedClass(expectedClass) checkReifiedClass(expectedClass) } + +// Like `run` but without inlining. +private fun runNonInlining(block: () -> Unit) = block() + +private fun (suspend () -> T).runCoroutine() = startCoroutine(Continuation(EmptyCoroutineContext) { it.getOrThrow() }) diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfo.h b/kotlin-native/runtime/src/main/cpp/TypeInfo.h index a974e6bd133..c776f3bec0c 100644 --- a/kotlin-native/runtime/src/main/cpp/TypeInfo.h +++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.h @@ -56,6 +56,8 @@ enum Konan_TypeFlags { TF_SUSPEND_FUNCTION = 1 << 5, TF_HAS_FINALIZER = 1 << 6, TF_HAS_FREEZE_HOOK = 1 << 7, + TF_REFLECTION_SHOW_PKG_NAME = 1 << 8, // If package name is available in reflection, e.g. in `KClass.qualifiedName`. + TF_REFLECTION_SHOW_REL_NAME = 1 << 9 // If relative name is available in reflection, e.g. in `KClass.simpleName`. }; // Flags per object instance. @@ -115,13 +117,12 @@ struct TypeInfo { int32_t interfaceTableSize_; InterfaceTableRecord const* interfaceTable_; - // String for the fully qualified dot-separated name of the package containing class, - // or `null` if the class is local or anonymous. + // String for the fully qualified dot-separated name of the package containing class. ObjHeader* packageName_; // String for the qualified class name relative to the containing package - // (e.g. TopLevel.Nested1.Nested2), or simple class name if it is local, - // or `null` if the class is anonymous. + // (e.g. TopLevel.Nested1.Nested2) or the effective class name computed for + // local class or anonymous object (e.g. listOf$1). ObjHeader* relativeName_; // Various flags. diff --git a/kotlin-native/runtime/src/main/cpp/Types.cpp b/kotlin-native/runtime/src/main/cpp/Types.cpp index 57a6762caef..d5f2f04c44d 100644 --- a/kotlin-native/runtime/src/main/cpp/Types.cpp +++ b/kotlin-native/runtime/src/main/cpp/Types.cpp @@ -55,12 +55,22 @@ KBoolean Kotlin_TypeInfo_isInstance(KConstRef obj, KNativePtr typeInfo) { return IsInstance(obj, reinterpret_cast(typeInfo)); } -OBJ_GETTER(Kotlin_TypeInfo_getPackageName, KNativePtr typeInfo) { - RETURN_OBJ(reinterpret_cast(typeInfo)->packageName_); +OBJ_GETTER(Kotlin_TypeInfo_getPackageName, KNativePtr typeInfo, KBoolean checkFlags) { + const TypeInfo* type_info = reinterpret_cast(typeInfo); + if (!checkFlags || type_info->flags_ & TF_REFLECTION_SHOW_PKG_NAME) { + RETURN_OBJ(type_info->packageName_); + } else { + return NULL; + } } -OBJ_GETTER(Kotlin_TypeInfo_getRelativeName, KNativePtr typeInfo) { - RETURN_OBJ(reinterpret_cast(typeInfo)->relativeName_); +OBJ_GETTER(Kotlin_TypeInfo_getRelativeName, KNativePtr typeInfo, KBoolean checkFlags) { + const TypeInfo* type_info = reinterpret_cast(typeInfo); + if (!checkFlags || type_info->flags_ & TF_REFLECTION_SHOW_REL_NAME) { + RETURN_OBJ(type_info->relativeName_); + } else { + return NULL; + } } struct AssociatedObjectTableRecord { 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 06caf7180df..61e90e03779 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 @@ -9,25 +9,15 @@ import kotlin.reflect.KClass @ExportForCompiler internal class KClassImpl(private val typeInfo: NativePtr) : KClass { + // TODO: consider replacing '$' by another delimeter that can't be used in class name specified with backticks (``) override val simpleName: String? - get() { - val relativeName = getRelativeName(typeInfo) - ?: return null - - return relativeName.substringAfterLast(".") - } + get() = getRelativeName(typeInfo, true)?.substringAfterLast('.')?.substringAfterLast('$') override val qualifiedName: String? get() { - val packageName = getPackageName(typeInfo) - ?: return null - - val relativeName = getRelativeName(typeInfo)!! - return if (packageName.isEmpty()) { - relativeName - } else { - "$packageName.$relativeName" - } + val packageName = getPackageName(typeInfo, true) ?: return null + val relativeName = getRelativeName(typeInfo, true) ?: return null + return if (packageName.isEmpty()) relativeName else "$packageName.$relativeName" } override fun isInstance(value: Any?): Boolean = value != null && isInstance(value, this.typeInfo) @@ -38,7 +28,13 @@ internal class KClassImpl(private val typeInfo: NativePtr) : KClass override fun hashCode(): Int = typeInfo.hashCode() override fun toString(): String { - return "class " + (qualifiedName ?: simpleName ?: "") + val relativeName = getRelativeName(typeInfo, false) + val visibleName = if (relativeName != null) { + val packageName = getPackageName(typeInfo, false)!! + if (packageName.isEmpty()) relativeName else "$packageName.$relativeName" + } else "" + + return "class $visibleName" } internal fun findAssociatedObjectImpl(key: KClassImpl<*>): Any? = @@ -80,10 +76,10 @@ internal external fun getObjectTypeInfo(obj: Any): NativePtr internal external inline fun getClassTypeInfo(): NativePtr @GCUnsafeCall("Kotlin_TypeInfo_getPackageName") -private external fun getPackageName(typeInfo: NativePtr): String? +private external fun getPackageName(typeInfo: NativePtr, checkFlags: Boolean): String? @GCUnsafeCall("Kotlin_TypeInfo_getRelativeName") -private external fun getRelativeName(typeInfo: NativePtr): String? +private external fun getRelativeName(typeInfo: NativePtr, checkFlags: Boolean): String? @GCUnsafeCall("Kotlin_TypeInfo_isInstance") private external fun isInstance(obj: Any, typeInfo: NativePtr): Boolean