Skip to content

Commit

Permalink
Fix IllegalAccessException being thrown on an attempt to retrieve ser…
Browse files Browse the repository at this point in the history
…ializer for some private implementation classes from stdlib.

Fixes #2449
  • Loading branch information
sandwwraith committed Oct 11, 2023
1 parent a675cb3 commit 243eab4
Show file tree
Hide file tree
Showing 4 changed files with 75 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+
// There are no serializable objects in the stdlib anyway.
if (this.canonicalName.let { it.startsWith("java.") || it.startsWith("kotlin.") }) return null

This comment has been minimized.

Copy link
@pdvrieze

pdvrieze Oct 11, 2023

Contributor

I think it would be better to check whether the class itself is not accessible (private), using Modifier.isPublic(modifiers) although I haven't checked that that works for all cases.

// 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,55 @@ 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"
)
}
}
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 243eab4

Please sign in to comment.