Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serializing emptyMap() throws IllegalAccessException #2449

Closed
michal-mally opened this issue Sep 21, 2023 · 5 comments
Closed

Serializing emptyMap() throws IllegalAccessException #2449

michal-mally opened this issue Sep 21, 2023 · 5 comments

Comments

@michal-mally
Copy link

Describe the bug

Calling kotlinx.serialization.serializerOrNull(emptyMap<String, String>().javaClass) causes

Exception in thread "main" java.lang.IllegalAccessException: class kotlinx.serialization.internal.PlatformKt cannot access a member of class kotlin.collections.EmptyMap with modifiers "public static final"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
	at java.base/java.lang.reflect.Field.checkAccess(Field.java:1102)
	at java.base/java.lang.reflect.Field.get(Field.java:423)
	at kotlinx.serialization.internal.PlatformKt.findObjectSerializer(Platform.kt:161)
	at kotlinx.serialization.internal.PlatformKt.constructSerializerForGivenTypeArgs(Platform.kt:48)
	at kotlinx.serialization.SerializersKt__SerializersJvmKt.reflectiveOrContextual$SerializersKt__SerializersJvmKt(SerializersJvm.kt:169)
	at kotlinx.serialization.SerializersKt__SerializersJvmKt.typeSerializer$SerializersKt__SerializersJvmKt(SerializersJvm.kt:160)
	at kotlinx.serialization.SerializersKt__SerializersJvmKt.serializerByJavaTypeImpl$SerializersKt__SerializersJvmKt(SerializersJvm.kt:108)
	at kotlinx.serialization.SerializersKt__SerializersJvmKt.serializerOrNull(SerializersJvm.kt:98)
	at kotlinx.serialization.SerializersKt.serializerOrNull(Unknown Source)
	at kotlinx.serialization.SerializersKt__SerializersJvmKt.serializerOrNull(SerializersJvm.kt:55)
	at kotlinx.serialization.SerializersKt.serializerOrNull(Unknown Source)

To Reproduce

import kotlinx.serialization.serializerOrNull

fun main() {
    serializerOrNull(emptyMap<String, String>().javaClass)
}

Expected behavior

Serializer able to serialize emptyMap() into {} is returned.

Environment

  • Kotlin version: 1.9.10
  • Library version: 1.6.0
  • Kotlin platforms: JVM
  • Gradle version: 8.3
  • Other relevant context: Java Temurin 17.0.4
@qwwdfsad
Copy link
Collaborator

Java's integrity checks strike: we should avoid reflective accesses to the classes from java.*

@pdvrieze
Copy link
Contributor

This also brings the question, is it legitimate for the intrinsic to kick in here on the interface (without @Serializable annotation) and just substitute in a MapSerializer? If it is valid, maybe it should be done (also addressing the polymorphic serialization of interface issue on native).

@sandwwraith
Copy link
Member

Which JVM version are you running on?

@michal-mally
Copy link
Author

@sandwwraith it is in the initial report: Java Temurin 17.0.4.

sandwwraith added a commit that referenced this issue Oct 11, 2023
…ializer for some private implementation classes from stdlib.

Fixes #2449
@pdvrieze
Copy link
Contributor

I've had a look at the code. What is going on is that this code is trying to determine whether the type is a Kotlin object by reading the static INSTANCE field that has the type.

While EmptyMap has a public INSTANCE the object itself is private. This private object type is exposed through the call to .javaClass.

While the error message is far from clear it is not clear that this code should be correct (should it return null instead)?

Another aspect is that due to java reflection, this triggers serializerByJavaTypeImpl (from SerializersJvm.kt) which differs from serializerByKClassImpl in that the latter indirectly invokes builtinParametrizedSerializer where the former doesn't (it will never resolve to the actual map/list serializers). Considering it however, there is no way to determine the member serializer (as humans we "know" that EmptyMap has no members, but the code doesn't unless we want to do that as builtin). This means that there is not so much a bug as an edge case that might be handled specially.

While the example is a bit silly, the use case I see occurring is where there is a value that needs to be serialized based on its dynamic type (there are good reasons you may not want to do this, but nevertheless quite some web frameworks do this). Thinking to do that requires some way to get the member type (and it serializer) which is way too involved (common base types, polymorphic serialization,...), so a framework would need to do that itself. But in the end we are running against the nature of kotlinx.serialization being based on static serialization.

As to the use case, it would be better to use a special class SerializableValue<T>(serializer: KSerializer<T>, value: T) that is created using a inline reified factory function. SerializableValue could be serializable with a custom serializer that does the obvious thing, but such a type would probably be framework specific as it is still not clear as to what the correct way is to deal with the fundamental "polymorphic" nature of the values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants