Skip to content

Commit

Permalink
Fix IllegalAccessException (#2469)
Browse files Browse the repository at this point in the history
 Being thrown on an attempt to retrieve serializer for some private implementation classes from stdlib.

Fixes #2449
  • Loading branch information
sandwwraith authored Oct 26, 2023
1 parent 919062f commit b44f03f
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 11 deletions.
3 changes: 3 additions & 0 deletions core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T> {
}

private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? {
// Special case to avoid IllegalAccessException on Java11+ (#2449)
// There are no serializable objects in the stdlib anyway.
if (this.canonicalName?.let { it.startsWith("java.") || it.startsWith("kotlin.") } != false) return null
// Check it is an object without using kotlin-reflect
val field =
declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == this && Modifier.isStatic(it.modifiers) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@
package kotlinx.serialization

import kotlinx.serialization.json.Json
import kotlinx.serialization.test.typeTokenOf
import org.junit.Test
import java.util.HashMap
import java.util.HashSet
import kotlin.collections.LinkedHashMap
import kotlin.collections.Map
import kotlin.collections.hashMapOf
import kotlin.collections.hashSetOf
import kotlin.test.assertEquals
import kotlin.reflect.*
import kotlin.test.*


class JavaCollectionsTest {
Expand All @@ -38,7 +40,7 @@ class JavaCollectionsTest {
)

@Test
fun test() {
fun testJavaCollectionsInsideClass() {
val original = HasHashMap("42", hashMapOf(1 to "1", 2 to "2"), hashSetOf(11), LinkedHashMap(), null)
val serializer = HasHashMap.serializer()
val string = Json.encodeToString(serializer = serializer, value = original)
Expand All @@ -49,4 +51,62 @@ class JavaCollectionsTest {
val restored = Json.decodeFromString(deserializer = serializer, string = string)
assertEquals(expected = original, actual = restored)
}

@Test
fun testTopLevelMaps() {
// Returning null here is a deliberate choice: map constructor functions may return different specialized
// implementations (e.g., kotlin.collections.EmptyMap or java.util.Collections.SingletonMap)
// that may or may not be generic. Since we generally cannot return a generic serializer using Java class only,
// all attempts to get map serializer using only .javaClass should return null.
assertNull(serializerOrNull(emptyMap<String, String>().javaClass))
assertNull(serializerOrNull(mapOf<String, String>("a" to "b").javaClass))
assertNull(serializerOrNull(mapOf<String, String>("a" to "b", "b" to "c").javaClass))
// Correct ways of retrieving map serializer:
assertContains(
serializer(typeTokenOf<Map<String, String>>()).descriptor.serialName,
"kotlin.collections.LinkedHashMap"
)
assertContains(
serializer(typeTokenOf<java.util.LinkedHashMap<String, String>>()).descriptor.serialName,
"kotlin.collections.LinkedHashMap"
)
assertContains(
serializer(typeOf<LinkedHashMap<String, String>>()).descriptor.serialName,
"kotlin.collections.LinkedHashMap"
)
}

@Test
fun testTopLevelSetsAndLists() {
// Same reasoning as for maps
assertNull(serializerOrNull(emptyList<String>().javaClass))
assertNull(serializerOrNull(listOf<String>("a").javaClass))
assertNull(serializerOrNull(listOf<String>("a", "b").javaClass))
assertNull(serializerOrNull(emptySet<String>().javaClass))
assertNull(serializerOrNull(setOf<String>("a").javaClass))
assertNull(serializerOrNull(setOf<String>("a", "b").javaClass))
assertContains(
serializer(typeTokenOf<Set<String>>()).descriptor.serialName,
"kotlin.collections.LinkedHashSet"
)
assertContains(
serializer(typeTokenOf<List<String>>()).descriptor.serialName,
"kotlin.collections.ArrayList"
)
assertContains(
serializer(typeTokenOf<java.util.LinkedHashSet<String>>()).descriptor.serialName,
"kotlin.collections.LinkedHashSet"
)
assertContains(
serializer(typeTokenOf<java.util.ArrayList<String>>()).descriptor.serialName,
"kotlin.collections.ArrayList"
)
}

@Test
fun testAnonymousObject() {
val obj: Any = object {}
assertNull(serializerOrNull(obj.javaClass))
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -209,15 +209,6 @@ class SerializerByTypeTest {
assertEquals(expected, json.encodeToString(serial2 as KSerializer<T>, value))
}

@PublishedApi
internal open class TypeBase<T>

public inline fun <reified T> typeTokenOf(): Type {
val base = object : TypeBase<T>() {}
val superType = base::class.java.genericSuperclass!!
return (superType as ParameterizedType).actualTypeArguments.first()!!
}

class IntBox(val i: Int)

object CustomIntSerializer : KSerializer<IntBox> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.test

import java.lang.reflect.*


@PublishedApi
internal open class TypeBase<T>

public inline fun <reified T> typeTokenOf(): Type {
val base = object : TypeBase<T>() {}
val superType = base::class.java.genericSuperclass!!
return (superType as ParameterizedType).actualTypeArguments.first()!!
}

0 comments on commit b44f03f

Please sign in to comment.