From b29309478eb2d2846328d677528cba0247bc219d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 24 Oct 2022 19:05:08 +0000 Subject: [PATCH] Take into account 'KTypeImpl.classifier' in its hashCode and equals Since KT-53308, we started to cache results of typeOf invocation in reflection. The cache uses the origin 'KClassifier' as a key with an optional list of 'KTypeProjection' in type argument position, and computed result as a value. Without classifier check, the caching produces incorrect execution in a very specific classloaders usage which is leveraged by IDEA source-tree: * All Kotlin stdlib and reflect classes are always loaded by the same classloader * Other classes that depend on Kotlin are loaded by separate classloaders The reproducer: * Attempt to use typeOf> from one classloader caches the resulting KType * Attempt to use typeOf> from another classloader for the same 'Foo' that differs only in classloader reuses the computed KType, meaning that KType type argument classifier is simply incorrect and points to the 'Foo' from the first classloader #KT-54611 Fixed #KT-54629 Fixed Merge-request: KT-MR-7470 Merged-by: Vsevolod Tolstopyatov --- .../reflection/classLoaders/kTypeEquality.kt | 25 +++++++++++++++++++ .../codegen/ReflectionClassLoaderTest.kt | 13 ++++++++++ .../kotlin/reflect/jvm/internal/KTypeImpl.kt | 5 ++-- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 compiler/testData/codegen/reflection/classLoaders/kTypeEquality.kt diff --git a/compiler/testData/codegen/reflection/classLoaders/kTypeEquality.kt b/compiler/testData/codegen/reflection/classLoaders/kTypeEquality.kt new file mode 100644 index 00000000000..3dc7da9e475 --- /dev/null +++ b/compiler/testData/codegen/reflection/classLoaders/kTypeEquality.kt @@ -0,0 +1,25 @@ +package test + +import kotlin.reflect.* +import kotlin.reflect.full.* +import kotlin.test.* + +class K { + fun getType() = typeOf>() +} + +class Test { + fun kClass(): Any = K::class + + fun KClass<*>.invokeGetType() = + java.declaredMethods.single { it.name == "getType" }.invoke(java.getDeclaredConstructor().newInstance()) as KType + + fun doTest(k1: KClass<*>, k2: KClass<*>) { + assertNotEquals(k1, k2) + + val type1 = k1.invokeGetType() + val type2 = k2.invokeGetType() + assertNotEquals((type1.arguments[0].type)?.classifier, (type2.arguments[0].type)?.classifier) + assertNotEquals(type1, type2) + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt b/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt index 1322a17f27a..9a6d0ecdfe8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt @@ -77,4 +77,17 @@ class ReflectionClassLoaderTest : CodegenTestCase() { ChildClassLoader(parent) ) } + + fun testKTypeEquality() { + /* + * Check that typeOf>() when clz is loaded by different classloaders + * differs in both its `equals` and its `classifier`. + * It is important in the face of KType caching + */ + loadFile("$prefix/kTypeEquality.kt") + doTest( + createClassLoader(), + createClassLoader() + ) + } } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt index 5dd3fe793d4..e147ae76940 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt @@ -39,7 +39,6 @@ internal class KTypeImpl( val type: KotlinType, computeJavaType: (() -> Type)? = null ) : KTypeBase { - @Suppress("UNCHECKED_CAST") private val computeJavaType = computeJavaType as? ReflectProperties.LazySoftVal ?: computeJavaType?.let(ReflectProperties::lazySoft) @@ -126,10 +125,10 @@ internal class KTypeImpl( } override fun equals(other: Any?) = - other is KTypeImpl && type == other.type + other is KTypeImpl && type == other.type && classifier == other.classifier && arguments == other.arguments override fun hashCode() = - type.hashCode() + (31 * ((31 * type.hashCode()) + classifier.hashCode())) + arguments.hashCode() override fun toString() = ReflectionObjectRenderer.renderType(type)