1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
/*
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization
import kotlinx.serialization.json.Json
import java.net.URLClassLoader
import kotlin.reflect.*
import kotlin.test.*
class SerializerByTypeCacheTest {
@Serializable
class Holder(val i: Int)
@Suppress("UNCHECKED_CAST")
@Test
fun testCaching() {
val typeOfKType = typeOf<Holder>()
val parameterKType = typeOf<List<Holder>>().arguments[0].type!!
assertSame(serializer(), serializer<Holder>())
assertSame(serializer(typeOfKType), serializer(typeOfKType))
assertSame(serializer(parameterKType), serializer(parameterKType))
assertSame(serializer(), serializer(typeOfKType) as KSerializer<Holder>)
assertSame(serializer(parameterKType) as KSerializer<Holder>, serializer(typeOfKType) as KSerializer<Holder>)
}
/**
* Checking the case when a parameterized type is loaded in different parallel [ClassLoader]s.
*
* If the main type is loaded by a common parent [ClassLoader] (for example, a bootstrap for [List]),
* and the element class is loaded by different loaders, then some implementations of the [KType] (e.g. `KTypeImpl` from reflection) may not see the difference between them.
*
* As a result, a serializer for another loader will be returned from the cache, and it will generate instances, when working with which we will get an [ClassCastException].
*
* The test checks the correctness of the cache for such cases - that different serializers for different loaders will be returned.
*
* [see](https://youtrack.jetbrains.com/issue/KT-54523).
*/
@Test
fun testDifferentClassLoaders() {
val elementKType1 = SimpleKType(loadClass().kotlin)
val elementKType2 = SimpleKType(loadClass().kotlin)
// Java class must be same (same name)
assertEquals(elementKType1.classifier.java.canonicalName, elementKType2.classifier.java.canonicalName)
// class loaders must be different
assertNotSame(elementKType1.classifier.java.classLoader, elementKType2.classifier.java.classLoader)
// due to the incorrect definition of the `equals`, KType-s are equal
assertEquals(elementKType1, elementKType2)
// create parametrized type `List<Foo>`
val kType1 = SingleParametrizedKType(List::class, elementKType1)
val kType2 = SingleParametrizedKType(List::class, elementKType2)
val serializer1 = serializer(kType1)
val serializer2 = serializer(kType2)
// when taking a serializers from cache, we must distinguish between KType-s, despite the fact that they are equivalent
assertNotSame(serializer1, serializer2)
// serializers must work correctly
Json.decodeFromString(serializer1, "[{\"i\":1}]")
Json.decodeFromString(serializer2, "[{\"i\":1}]")
}
/**
* Load class `example.Foo` via new class loader. Compiled class-file located in the resources.
*/
private fun loadClass(): Class<*> {
val classesUrl = this::class.java.classLoader.getResource("class_loaders/classes/")
val loader1 = URLClassLoader(arrayOf(classesUrl), this::class.java.classLoader)
return loader1.loadClass("example.Foo")
}
private class SimpleKType(override val classifier: KClass<*>): KType {
override val annotations: List<Annotation> = emptyList()
override val arguments: List<KTypeProjection> = emptyList()
override val isMarkedNullable: Boolean = false
override fun equals(other: Any?): Boolean {
if (other !is SimpleKType) return false
return classifier.java.canonicalName == other.classifier.java.canonicalName
}
override fun hashCode(): Int {
return classifier.java.canonicalName.hashCode()
}
}
private class SingleParametrizedKType(override val classifier: KClass<*>, val parameterType: KType): KType {
override val annotations: List<Annotation> = emptyList()
override val arguments: List<KTypeProjection> = listOf(KTypeProjection(KVariance.INVARIANT, parameterType))
override val isMarkedNullable: Boolean = false
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SingleParametrizedKType
if (classifier != other.classifier) return false
if (parameterType != other.parameterType) return false
return true
}
override fun hashCode(): Int {
var result = classifier.hashCode()
result = 31 * result + parameterType.hashCode()
return result
}
}
}
|