1 /*
2 * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 package kotlinx.serialization.internal
6
7 import kotlinx.serialization.KSerializer
8 import java.lang.ref.SoftReference
9 import java.util.concurrent.ConcurrentHashMap
10 import kotlin.reflect.KClass
11 import kotlin.reflect.KClassifier
12 import kotlin.reflect.KType
13 import kotlin.reflect.KTypeProjection
14
15 /*
16 * By default, we use ClassValue-based caches to avoid classloader leaks,
17 * but ClassValue is not available on Android, thus we attempt to check it dynamically
18 * and fallback to ConcurrentHashMap-based cache.
19 */
20 private val useClassValue = try {
21 Class.forName("java.lang.ClassValue")
22 true
23 } catch (_: Throwable) {
24 false
25 }
26
27 /**
28 * Creates a **strongly referenced** cache of values associated with [Class].
29 * Serializers are computed using provided [factory] function.
30 *
31 * `null` values are not supported, though there aren't any technical limitations.
32 */
createCachenull33 internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
34 return if (useClassValue) ClassValueCache(factory) else ConcurrentHashMapCache(factory)
35 }
36
37 /**
38 * Creates a **strongly referenced** cache of values associated with [Class].
39 * Serializers are computed using provided [factory] function.
40 *
41 * `null` values are not supported, though there aren't any technical limitations.
42 */
createParametrizedCachenull43 internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
44 return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory)
45 }
46
47 private class ClassValueCache<T>(val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
48 private val classValue = ClassValueReferences<CacheEntry<T>>()
49
getnull50 override fun get(key: KClass<Any>): KSerializer<T>? {
51 return classValue
52 .getOrSet(key.java) { CacheEntry(compute(key)) }
53 .serializer
54 }
55 }
56
57 /**
58 * A class that combines the capabilities of ClassValue and SoftReference.
59 * Softly binds the calculated value to the specified class.
60 *
61 * [SoftReference] used to prevent class loaders from leaking,
62 * since the value can transitively refer to an instance of type [Class], this may prevent the loader from
63 * being collected during garbage collection.
64 *
65 * In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned.
66 *
67 * However, the value can be collected during garbage collection (thanks to [SoftReference])
68 * - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache.
69 *
70 * An important requirement for a function generating a value is that it must be stable, so that each time it is called for the same class, the function returns similar values.
71 * In the case of serializers, these should be instances of the same class filled with equivalent values.
72 */
73 @SuppressAnimalSniffer
74 private class ClassValueReferences<T> : ClassValue<MutableSoftReference<T>>() {
computeValuenull75 override fun computeValue(type: Class<*>): MutableSoftReference<T> {
76 return MutableSoftReference()
77 }
78
getOrSetnull79 inline fun getOrSet(key: Class<*>, crossinline factory: () -> T): T {
80 val ref: MutableSoftReference<T> = get(key)
81
82 ref.reference.get()?.let { return it }
83
84 // go to the slow path and create serializer with blocking, also wrap factory block
85 return ref.getOrSetWithLock { factory() }
86 }
87
88 }
89
90 /**
91 * Wrapper over `SoftReference`, used to store a mutable value.
92 */
93 private class MutableSoftReference<T> {
94 // volatile because of situations like https://stackoverflow.com/a/7855774
95 @JvmField
96 @Volatile
97 var reference: SoftReference<T> = SoftReference(null)
98
99 /*
100 It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class
101 This way access to reference is blocked only for one serializable class, and not for all
102 */
103 @Synchronized
getOrSetWithLocknull104 fun getOrSetWithLock(factory: () -> T): T {
105 // exit function if another thread has already filled in the `reference` with non-null value
106 reference.get()?.let { return it }
107
108 val value = factory()
109 reference = SoftReference(value)
110 return value
111 }
112 }
113
114 private class ClassValueParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) :
115 ParametrizedSerializerCache<T> {
116 private val classValue = ClassValueReferences<ParametrizedCacheEntry<T>>()
117
getnull118 override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
119 return classValue.getOrSet(key.java) { ParametrizedCacheEntry() }
120 .computeIfAbsent(types) { compute(key, types) }
121 }
122 }
123
124 /**
125 * We no longer support Java 6, so the only place we use this cache is Android, where there
126 * are no classloader leaks issue, thus we can safely use strong references and do not bother
127 * with WeakReference wrapping.
128 */
129 private class ConcurrentHashMapCache<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
130 private val cache = ConcurrentHashMap<Class<*>, CacheEntry<T>>()
131
getnull132 override fun get(key: KClass<Any>): KSerializer<T>? {
133 return cache.getOrPut(key.java) {
134 CacheEntry(compute(key))
135 }.serializer
136 }
137 }
138
139
140 private class ConcurrentHashMapParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) :
141 ParametrizedSerializerCache<T> {
142 private val cache = ConcurrentHashMap<Class<*>, ParametrizedCacheEntry<T>>()
143
getnull144 override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
145 return cache.getOrPut(key.java) { ParametrizedCacheEntry() }
146 .computeIfAbsent(types) { compute(key, types) }
147 }
148 }
149
150 /**
151 * Wrapper for cacheable serializer of some type.
152 * Used to store cached serializer or indicates that the serializer is not cacheable.
153 *
154 * If serializer for type is not cacheable then value of [serializer] is `null`.
155 */
156 private class CacheEntry<T>(@JvmField val serializer: KSerializer<T>?)
157
158 /**
159 * Workaround of https://youtrack.jetbrains.com/issue/KT-54611 and https://github.com/Kotlin/kotlinx.serialization/issues/2065
160 */
161 private class KTypeWrapper(private val origin: KType) : KType {
162 override val annotations: List<Annotation>
163 get() = origin.annotations
164 override val arguments: List<KTypeProjection>
165 get() = origin.arguments
166 override val classifier: KClassifier?
167 get() = origin.classifier
168 override val isMarkedNullable: Boolean
169 get() = origin.isMarkedNullable
170
equalsnull171 override fun equals(other: Any?): Boolean {
172 if (other == null) return false
173 if (origin != (other as? KTypeWrapper)?.origin) return false
174
175 val kClassifier = classifier
176 if (kClassifier is KClass<*>) {
177 val otherClassifier = (other as? KType)?.classifier
178 if (otherClassifier == null || otherClassifier !is KClass<*>) {
179 return false
180 }
181 return kClassifier.java == otherClassifier.java
182 } else {
183 return false
184 }
185 }
186
hashCodenull187 override fun hashCode(): Int {
188 return origin.hashCode()
189 }
190
toStringnull191 override fun toString(): String {
192 return "KTypeWrapper: $origin"
193 }
194 }
195
196 private class ParametrizedCacheEntry<T> {
197 private val serializers: ConcurrentHashMap<List<KTypeWrapper>, Result<KSerializer<T>?>> = ConcurrentHashMap()
computeIfAbsentnull198 inline fun computeIfAbsent(types: List<KType>, producer: () -> KSerializer<T>?): Result<KSerializer<T>?> {
199 val wrappedTypes = types.map { KTypeWrapper(it) }
200 return serializers.getOrPut(wrappedTypes) {
201 kotlin.runCatching { producer() }
202 }
203 }
204 }
205
206