[Native][IR] Move verbose RTTI for local classes and anonymous objects

^KT-45304
This commit is contained in:
Dmitriy Dolovov
2021-05-13 17:57:59 +03:00
parent 6bac016b3a
commit d42ff069f6
10 changed files with 680 additions and 77 deletions
@@ -258,6 +258,18 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
lazyValues[member] = newValue lazyValues[member] = newValue
} }
private val localClassNames = mutableMapOf<IrAttributeContainer, String>()
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) { val reflectionTypes: KonanReflectionTypes by lazy(PUBLICATION) {
KonanReflectionTypes(moduleDescriptor, KonanFqNames.internalPackageName) KonanReflectionTypes(moduleDescriptor, KonanFqNames.internalPackageName)
} }
@@ -121,6 +121,12 @@ internal val sharedVariablesPhase = makeKonanModuleLoweringPhase(
prerequisite = setOf(lateinitPhase) prerequisite = setOf(lateinitPhase)
) )
internal val inventNamesForLocalClasses = makeKonanModuleLoweringPhase(
::NativeInventNamesForLocalClasses,
name = "InventNamesForLocalClasses",
description = "Invent names for local classes and anonymous objects"
)
internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase( internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase(
lower = object : SameTypeCompilerPhase<Context, IrModuleFragment> { lower = object : SameTypeCompilerPhase<Context, IrModuleFragment> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<IrModuleFragment>, context: Context, input: IrModuleFragment): IrModuleFragment { override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<IrModuleFragment>, context: Context, input: IrModuleFragment): IrModuleFragment {
@@ -138,7 +144,7 @@ internal val extractLocalClassesFromInlineBodies = NamedCompilerPhase(
}, },
name = "ExtractLocalClassesFromInlineBodies", name = "ExtractLocalClassesFromInlineBodies",
description = "Extraction of local classes from inline bodies", description = "Extraction of local classes from inline bodies",
prerequisite = setOf(sharedVariablesPhase), prerequisite = setOf(sharedVariablesPhase), // TODO: add "soft" dependency on inventNamesForLocalClasses
nlevels = 0, nlevels = 0,
actions = modulePhaseActions actions = modulePhaseActions
) )
@@ -217,7 +223,7 @@ internal val localFunctionsPhase = makeKonanFileOpPhase(
}, },
name = "LocalFunctions", name = "LocalFunctions",
description = "Local function lowering", description = "Local function lowering",
prerequisite = setOf(sharedVariablesPhase) prerequisite = setOf(sharedVariablesPhase) // TODO: add "soft" dependency on inventNamesForLocalClasses
) )
internal val tailrecPhase = makeKonanFileLoweringPhase( internal val tailrecPhase = makeKonanFileLoweringPhase(
@@ -192,6 +192,7 @@ internal val allLoweringsPhase = NamedCompilerPhase(
arrayConstructorPhase then arrayConstructorPhase then
lateinitPhase then lateinitPhase then
sharedVariablesPhase then sharedVariablesPhase then
inventNamesForLocalClasses then
extractLocalClassesFromInlineBodies then extractLocalClassesFromInlineBodies then
inlinePhase then inlinePhase then
provisionalFunctionExpressionPhase then provisionalFunctionExpressionPhase then
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod 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.builtins.PrimitiveType
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.isPublicApi import org.jetbrains.kotlin.ir.symbols.isPublicApi
@@ -253,7 +254,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
interfaceTableSize, interfaceTablePtr, interfaceTableSize, interfaceTablePtr,
reflectionInfo.packageName, reflectionInfo.packageName,
reflectionInfo.relativeName, reflectionInfo.relativeName,
flagsFromClass(irClass), flagsFromClass(irClass) or reflectionInfo.reflectionFlags,
context.getLayoutBuilder(irClass).classId, context.getLayoutBuilder(irClass).classId,
llvmDeclarations.writableTypeInfoGlobal?.pointer, llvmDeclarations.writableTypeInfoGlobal?.pointer,
associatedObjects = genAssociatedObjects(irClass) associatedObjects = genAssociatedObjects(irClass)
@@ -496,8 +497,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
val objOffsetsPtr = staticData.placeGlobalConstArray("", int32Type, objOffsets) val objOffsetsPtr = staticData.placeGlobalConstArray("", int32Type, objOffsets)
val objOffsetsCount = objOffsets.size val objOffsetsCount = objOffsets.size
val reflectionInfo = ReflectionInfo(null, null)
val writableTypeInfoType = runtime.writableTypeInfoType val writableTypeInfoType = runtime.writableTypeInfoType
val writableTypeInfo = if (writableTypeInfoType == null) { val writableTypeInfo = if (writableTypeInfoType == null) {
null null
@@ -541,8 +540,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount, objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount,
interfaces = interfacesPtr, interfacesCount = interfaces.size, interfaces = interfacesPtr, interfacesCount = interfaces.size,
interfaceTableSize = interfaceTableSize, interfaceTable = interfaceTablePtr, interfaceTableSize = interfaceTableSize, interfaceTable = interfaceTablePtr,
packageName = reflectionInfo.packageName, packageName = ReflectionInfo.EMPTY.packageName,
relativeName = reflectionInfo.relativeName, relativeName = ReflectionInfo.EMPTY.relativeName,
flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0), flags = flagsFromClass(irClass) or (if (immutable) TF_IMMUTABLE else 0),
classId = typeHierarchyInfo.classIdLo, classId = typeHierarchyInfo.classIdLo,
writableTypeInfo = writableTypeInfo, writableTypeInfo = writableTypeInfo,
@@ -557,21 +556,46 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
private val OverriddenFunctionInfo.implementation get() = getImplementation(context) private val OverriddenFunctionInfo.implementation get() = getImplementation(context)
data class ReflectionInfo(val packageName: String?, val relativeName: String?) data class ReflectionInfo(val packageName: String?, val relativeName: String?, val reflectionFlags: Int) {
companion object {
private fun getReflectionInfo(irClass: IrClass): ReflectionInfo = when { val EMPTY = ReflectionInfo(null, null, 0)
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() }
)
} }
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() { fun dispose() {
debugRuntimeOrNull?.let { LLVMDisposeModule(it) } 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_SUSPEND_FUNCTION = 32
private const val TF_HAS_FINALIZER = 64 private const val TF_HAS_FINALIZER = 64
private const val TF_HAS_FREEZE_HOOK = 128 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
@@ -218,6 +218,9 @@ internal class FunctionReferenceLowering(val context: Context): FileLoweringPass
).apply { ).apply {
parent = this@FunctionReferenceBuilder.parent parent = this@FunctionReferenceBuilder.parent
createParameterDeclarations() createParameterDeclarations()
// copy the generated name for IrClass, partially solves KT-47194
context.copyLocalClassName(functionReference, this)
} }
private val functionReferenceThis = functionReferenceClass.thisReceiver!! private val functionReferenceThis = functionReferenceClass.thisReceiver!!
@@ -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)
}
}
@@ -6,6 +6,7 @@
package codegen.kclass.kclass0 package codegen.kclass.kclass0
import kotlin.test.* import kotlin.test.*
import kotlin.coroutines.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Test fun runTest() { @Test fun runTest() {
@@ -13,36 +14,556 @@ import kotlin.reflect.KClass
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
checkClass(Any::class, "kotlin.Any", "Any", Any(), null) checkBasics()
checkClass(Int::class, "kotlin.Int", "Int", 42, "17") checkLocalClasses(args)
checkClass(String::class, "kotlin.String", "String", "17", 42) checkAnonymousObjects(args)
checkClass(RootClass::class, "codegen.kclass.kclass0.RootClass", "RootClass", RootClass(), Any()) checkAnonymousObjectsAssignedToProperty(args)
checkClass(RootClass.Nested::class, "codegen.kclass.kclass0.RootClass.Nested", "Nested", RootClass.Nested(), Any()) 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<String>) {
// Local class.
class Local0 {
val captured = args val captured = args
inner class Inner inner class Inner
} }
checkClass(Local::class, null, "Local", Local(), Any()) checkClass(
checkClass(Local.Inner::class, null, "Inner", Local().Inner(), Any()) 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<String>) {
// 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<String>) {
// Anonymous object.
val obj = object : Any() { val obj = object : Any() {
val captured = args val captured = args
inner class Inner inner class Inner
val innerKClass = Inner::class val innerKClass = Inner::class
} }
checkClass(obj::class, null, null, obj, Any()) checkClass(
checkClass(obj.innerKClass, null, "Inner", obj.Inner(), Any()) 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: // Anonymous object inside of lambda.
checkClass(Comparable::class, "kotlin.Comparable", "Comparable", 42, Any()) ;{
checkClass(Interface::class, "codegen.kclass.kclass0.Interface", "Interface", object : Interface {}, Any()) 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(Any(), Any::class)
checkInstanceClass(42, Int::class) checkInstanceClass(42, Int::class)
assert(42::class == Int::class) assertEquals(42::class, Int::class)
class Local
checkReifiedClass<Int>(Int::class) checkReifiedClass<Int>(Int::class)
checkReifiedClass<Int?>(Int::class) checkReifiedClass<Int?>(Int::class)
@@ -53,36 +574,43 @@ fun main(args: Array<String>) {
checkReifiedClass2<Any?>(Any::class) checkReifiedClass2<Any?>(Any::class)
checkReifiedClass<Local>(Local::class) checkReifiedClass<Local>(Local::class)
checkReifiedClass2<Local>(Local::class) checkReifiedClass2<Local>(Local::class)
checkReifiedClass<RootClass>(RootClass::class) checkReifiedClass<TopLevel>(TopLevel::class)
checkReifiedClass2<RootClass>(RootClass::class) checkReifiedClass2<TopLevel>(TopLevel::class)
} }
class RootClass {
class TopLevel {
class Nested class Nested
} }
interface Interface interface Interface
fun checkClass( private fun checkClass(
clazz: KClass<*>, clazz: KClass<*>,
expectedQualifiedName: String?, expectedSimpleName: String?, expectedQualifiedName: String?, expectedSimpleName: String?, expectedToStringName: String,
expectedInstance: Any, expectedNotInstance: Any? expectedInstance: Any, expectedNotInstance: Any?
) { ) {
assert(clazz.qualifiedName == expectedQualifiedName) assertEquals(clazz.qualifiedName, expectedQualifiedName)
assert(clazz.simpleName == expectedSimpleName) assertEquals(clazz.simpleName, expectedSimpleName)
assertEquals(clazz.toString(), expectedToStringName)
assert(clazz.isInstance(expectedInstance)) assertTrue(clazz.isInstance(expectedInstance))
if (expectedNotInstance != null) assert(!clazz.isInstance(expectedNotInstance)) if (expectedNotInstance != null) assertTrue(!clazz.isInstance(expectedNotInstance))
} }
fun checkInstanceClass(instance: Any, clazz: KClass<*>) { private fun checkInstanceClass(instance: Any, clazz: KClass<*>) {
assert(instance::class == clazz) assertEquals(instance::class, clazz)
} }
inline fun <reified T> checkReifiedClass(expectedClass: KClass<*>) { private inline fun <reified T> checkReifiedClass(expectedClass: KClass<*>) {
assert(T::class == expectedClass) assertEquals(T::class, expectedClass)
} }
inline fun <reified T> checkReifiedClass2(expectedClass: KClass<*>) { private inline fun <reified T> checkReifiedClass2(expectedClass: KClass<*>) {
checkReifiedClass<T>(expectedClass) checkReifiedClass<T>(expectedClass)
checkReifiedClass<T?>(expectedClass) checkReifiedClass<T?>(expectedClass)
} }
// Like `run` but without inlining.
private fun runNonInlining(block: () -> Unit) = block()
private fun <T> (suspend () -> T).runCoroutine() = startCoroutine(Continuation(EmptyCoroutineContext) { it.getOrThrow() })
@@ -56,6 +56,8 @@ enum Konan_TypeFlags {
TF_SUSPEND_FUNCTION = 1 << 5, TF_SUSPEND_FUNCTION = 1 << 5,
TF_HAS_FINALIZER = 1 << 6, TF_HAS_FINALIZER = 1 << 6,
TF_HAS_FREEZE_HOOK = 1 << 7, 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. // Flags per object instance.
@@ -115,13 +117,12 @@ struct TypeInfo {
int32_t interfaceTableSize_; int32_t interfaceTableSize_;
InterfaceTableRecord const* interfaceTable_; InterfaceTableRecord const* interfaceTable_;
// String for the fully qualified dot-separated name of the package containing class, // String for the fully qualified dot-separated name of the package containing class.
// or `null` if the class is local or anonymous.
ObjHeader* packageName_; ObjHeader* packageName_;
// String for the qualified class name relative to the containing package // String for the qualified class name relative to the containing package
// (e.g. TopLevel.Nested1.Nested2), or simple class name if it is local, // (e.g. TopLevel.Nested1.Nested2) or the effective class name computed for
// or `null` if the class is anonymous. // local class or anonymous object (e.g. listOf$1).
ObjHeader* relativeName_; ObjHeader* relativeName_;
// Various flags. // Various flags.
+14 -4
View File
@@ -55,12 +55,22 @@ KBoolean Kotlin_TypeInfo_isInstance(KConstRef obj, KNativePtr typeInfo) {
return IsInstance(obj, reinterpret_cast<const TypeInfo*>(typeInfo)); return IsInstance(obj, reinterpret_cast<const TypeInfo*>(typeInfo));
} }
OBJ_GETTER(Kotlin_TypeInfo_getPackageName, KNativePtr typeInfo) { OBJ_GETTER(Kotlin_TypeInfo_getPackageName, KNativePtr typeInfo, KBoolean checkFlags) {
RETURN_OBJ(reinterpret_cast<const TypeInfo*>(typeInfo)->packageName_); const TypeInfo* type_info = reinterpret_cast<const TypeInfo*>(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) { OBJ_GETTER(Kotlin_TypeInfo_getRelativeName, KNativePtr typeInfo, KBoolean checkFlags) {
RETURN_OBJ(reinterpret_cast<const TypeInfo*>(typeInfo)->relativeName_); const TypeInfo* type_info = reinterpret_cast<const TypeInfo*>(typeInfo);
if (!checkFlags || type_info->flags_ & TF_REFLECTION_SHOW_REL_NAME) {
RETURN_OBJ(type_info->relativeName_);
} else {
return NULL;
}
} }
struct AssociatedObjectTableRecord { struct AssociatedObjectTableRecord {
@@ -9,25 +9,15 @@ import kotlin.reflect.KClass
@ExportForCompiler @ExportForCompiler
internal class KClassImpl<T : Any>(private val typeInfo: NativePtr) : KClass<T> { internal class KClassImpl<T : Any>(private val typeInfo: NativePtr) : KClass<T> {
// TODO: consider replacing '$' by another delimeter that can't be used in class name specified with backticks (``)
override val simpleName: String? override val simpleName: String?
get() { get() = getRelativeName(typeInfo, true)?.substringAfterLast('.')?.substringAfterLast('$')
val relativeName = getRelativeName(typeInfo)
?: return null
return relativeName.substringAfterLast(".")
}
override val qualifiedName: String? override val qualifiedName: String?
get() { get() {
val packageName = getPackageName(typeInfo) val packageName = getPackageName(typeInfo, true) ?: return null
?: return null val relativeName = getRelativeName(typeInfo, true) ?: return null
return if (packageName.isEmpty()) relativeName else "$packageName.$relativeName"
val relativeName = getRelativeName(typeInfo)!!
return if (packageName.isEmpty()) {
relativeName
} else {
"$packageName.$relativeName"
}
} }
override fun isInstance(value: Any?): Boolean = value != null && isInstance(value, this.typeInfo) override fun isInstance(value: Any?): Boolean = value != null && isInstance(value, this.typeInfo)
@@ -38,7 +28,13 @@ internal class KClassImpl<T : Any>(private val typeInfo: NativePtr) : KClass<T>
override fun hashCode(): Int = typeInfo.hashCode() override fun hashCode(): Int = typeInfo.hashCode()
override fun toString(): String { override fun toString(): String {
return "class " + (qualifiedName ?: simpleName ?: "<anonymous>") val relativeName = getRelativeName(typeInfo, false)
val visibleName = if (relativeName != null) {
val packageName = getPackageName(typeInfo, false)!!
if (packageName.isEmpty()) relativeName else "$packageName.$relativeName"
} else "<anonymous>"
return "class $visibleName"
} }
internal fun findAssociatedObjectImpl(key: KClassImpl<*>): Any? = internal fun findAssociatedObjectImpl(key: KClassImpl<*>): Any? =
@@ -80,10 +76,10 @@ internal external fun getObjectTypeInfo(obj: Any): NativePtr
internal external inline fun <reified T : Any> getClassTypeInfo(): NativePtr internal external inline fun <reified T : Any> getClassTypeInfo(): NativePtr
@GCUnsafeCall("Kotlin_TypeInfo_getPackageName") @GCUnsafeCall("Kotlin_TypeInfo_getPackageName")
private external fun getPackageName(typeInfo: NativePtr): String? private external fun getPackageName(typeInfo: NativePtr, checkFlags: Boolean): String?
@GCUnsafeCall("Kotlin_TypeInfo_getRelativeName") @GCUnsafeCall("Kotlin_TypeInfo_getRelativeName")
private external fun getRelativeName(typeInfo: NativePtr): String? private external fun getRelativeName(typeInfo: NativePtr, checkFlags: Boolean): String?
@GCUnsafeCall("Kotlin_TypeInfo_isInstance") @GCUnsafeCall("Kotlin_TypeInfo_isInstance")
private external fun isInstance(obj: Any, typeInfo: NativePtr): Boolean private external fun isInstance(obj: Any, typeInfo: NativePtr): Boolean