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

Shows "java.lang.IllegalAccessError: failed to access class ..." in KMP JVM target, when inheriting custom KSerializer #2495

Closed
Omico opened this issue Nov 3, 2023 · 3 comments

Comments

@Omico
Copy link

Omico commented Nov 3, 2023

Describe the bug
I implemented a general abstract KSerializer class, which converted the class into the Map, which caused the following error.

Exception in thread "main" java.lang.IllegalAccessError: failed to access class attribute.MultiplierSerializer from class CharacterHitDamage (attribute.MultiplierSerializer and CharacterHitDamage are in unnamed module of loader 'app')
	at CharacterHitDamage.<clinit>(CharacterHitDamage.kt:7)
	at SerializersModuleKt.<clinit>(SerializersModule.kt:22)
	at MainKt$prettyJson$1.invoke(Main.kt:22)
	at MainKt$prettyJson$1.invoke(Main.kt:20)
	at kotlinx.serialization.json.JsonKt.Json(Json.kt:199)
	at kotlinx.serialization.json.JsonKt.Json$default(Json.kt:197)
	at MainKt.<clinit>(Main.kt:20)

FAILURE: Build failed with an exception.

The MultiplierSerializer cannot be private after being inherited from MapifySerializer.

@Serializable(MultiplierSerializer::class)
data class Multiplier(
    private val map: Map<Int, Percentage>,
) : Map<Int, Percentage> by map

fun Multiplier(vararg pairs: Pair<Int, Percentage>): Multiplier = Multiplier(map = pairs.toMap())

// Cannot use private keyword here
private object MultiplierSerializer : MapifySerializer<Multiplier, Int, Percentage>(
    keySerializer = Int.serializer(),
    valueSerializer = Percentage.serializer(),
) {
    override fun deserialize(map: Map<Int, Percentage>): Multiplier = Multiplier(map = map)
    override fun serialize(value: Multiplier): Map<Int, Percentage> = value.toMap()
}

abstract class MapifySerializer<T : Any, Key, Value>(
    keySerializer: KSerializer<Key>,
    valueSerializer: KSerializer<Value>,
) : KSerializer<T> {
    abstract fun deserialize(map: Map<Key, Value>): T
    abstract fun serialize(value: T): Map<Key, Value>

    private val mapSerializer: KSerializer<Map<Key, Value>> =
        MapSerializer(
            keySerializer = keySerializer,
            valueSerializer = valueSerializer,
        )

    override val descriptor: SerialDescriptor = mapSerializer.descriptor

    override fun deserialize(decoder: Decoder): T = deserialize(decoder.decodeSerializableValue(mapSerializer))

    override fun serialize(encoder: Encoder, value: T): Unit =
        encoder.encodeSerializableValue(
            serializer = mapSerializer,
            value = serialize(value),
        )
}

// private keyword can be used here
private object MultiplierSerializer : KSerializer<Multiplier> {
    private val mapSerializer: KSerializer<Map<Int, Percentage>> =
        MapSerializer(
            keySerializer = Int.serializer(),
            valueSerializer = Percentage.serializer(),
        )

    override val descriptor: SerialDescriptor = mapSerializer.descriptor

    override fun deserialize(decoder: Decoder): Multiplier =
        Multiplier(
            map = decoder.decodeSerializableValue(mapSerializer),
        )

    override fun serialize(encoder: Encoder, value: Multiplier): Unit =
        encoder.encodeSerializableValue(
            serializer = mapSerializer,
            value = value,
        )
}

To Reproduce
See here, https://github.com/Omico/issue-kotlinx-serialization-2495

Expected behavior

Environment

  • Kotlin version: 1.9.20
  • Library version: 1.6.0
  • Kotlin platforms: KMP JVM
  • Gradle version: 8.4
  • IDE version (if bug is related to the IDE) 2023.2.4
  • Other relevant context [e.g. OS version, JRE version, ... ]
@pdvrieze
Copy link
Contributor

pdvrieze commented Nov 3, 2023

I've had a look at the bytecode (btw. the second version fails too on my system). The problem is the way the plugin references the serializer. In CharacterHitDamage the following bytecode is generated as part of the classinit.

      26: getstatic     #152                // Field attribute/MultiplierSerializer.INSTANCE:Lattribute/MultiplierSerializer;

This however uses the private type MultiplierSerializer. There is however a way to get the serializer safely - there is a public fun serializer(): KSerializer<Multiplier> in Multipliers.Companion. This is indirect but safer. It would require a change in the plugin though to generate this indirect code.

@iseki0
Copy link
Contributor

iseki0 commented Nov 3, 2023

Please also consider the JPMS boundaray🥰 #2429

@shanshin
Copy link
Contributor

shanshin commented Nov 6, 2023

@pdvrieze, thanks for the research!
Indeed, if a custom serializer is used, then if it has an private visibility modifier, then using it in the properties now will lead to an error.

For such cases, we can rewrite the getting of the serializer using a companion (if the serializable class has a companion), for the rest of the cases, leave it as it is.

@shanshin shanshin self-assigned this Nov 6, 2023
shanshin added a commit to JetBrains/kotlin that referenced this issue Nov 6, 2023
When a custom serializer is specified on a type and this type is used in a property of another serializable class, then on the JVM this leads to an error accessing the custom serializer class - because it is private and located in another package.

Fixes Kotlin/kotlinx.serialization#2495
shanshin added a commit to JetBrains/kotlin that referenced this issue Jan 8, 2024
When a custom serializer is specified on a type and this type is used in a property of another serializable class, then on the JVM this leads to an error accessing the custom serializer class - because it is private and located in another package.

Fixes Kotlin/kotlinx.serialization#2495
shanshin added a commit to JetBrains/kotlin that referenced this issue Jan 9, 2024
When a custom serializer is specified on a type and this type is used in a property of another serializable class, then on the JVM this leads to an error accessing the custom serializer class - because it is private and located in another package.

Fixes Kotlin/kotlinx.serialization#2495
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