summaryrefslogtreecommitdiff
path: root/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt
blob: b8dd35d15082069a93d82004293c7c9848cfe1d6 (plain)
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
        }
    }
}