Kotlinx.serialization: Custom serializer for inline class fails to compile

Created on 11 Sep 2020  路  6Comments  路  Source: Kotlin/kotlinx.serialization

Describe the bug
I'm trying to workaround current limitation on inline class serialization (#259). I'm adding my own KSerializer for some inline class but JVM backend fails to compile it. It stops with the message org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated.


Stacktrace

e: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated
   L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ILOAD 1
    ICONST_1
    IAND
    ICONST_0
    IF_ICMPNE L1
    NEW kotlinx/serialization/MissingFieldException
    DUP
    LDC "innerValue"
    INVOKESPECIAL kotlinx/serialization/MissingFieldException.<init> (Ljava/lang/String;)V
    CHECKCAST java/lang/Throwable
    ATHROW
   L1
    ALOAD 0
    DLOAD 2
    PUTFIELD DataClass.innerValue : D
    RETURN
   L2
    LOCALVARIABLE this LDataClass; L0 L2 0
    LOCALVARIABLE seen1 I L0 L2 1
    LOCALVARIABLE innerValue LInlineClass; L0 L2 2
    LOCALVARIABLE serializationConstructorMarker Lkotlinx/serialization/internal/SerializationConstructorMarker; L0 L2 3
    MAXSTACK = 3
    MAXLOCALS = 4

File being compiled: (16,1) in /Users/vsga/Workspace/playgrounds/kotlin-jvm/src/main/kotlin/Main.kt
The root cause org.jetbrains.kotlin.codegen.CompilationException was thrown at: org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:92)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.endVisit(FunctionCodegen.java:959)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:481)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:249)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:165)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.JVMCodegenUtilKt.generateMethod(JVMCodegenUtil.kt:138)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializableCodegenImpl.generateInternalConstructor(SerializableCodegenImpl.kt:81)
    at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen.generateSyntheticInternalConstructor(SerializableCodegen.kt:41)
    at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen.generate(SerializableCodegen.kt:33)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializableCodegenImpl$Companion.generateSerializableExtensions(SerializableCodegenImpl.kt:48)
    at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationCodegenExtension.generateClassSyntheticParts(SerializationCodegenExtension.kt:29)
    at org.jetbrains.kotlin.codegen.ImplementationBodyCodegen.generateSyntheticPartsAfterBody(ImplementationBodyCodegen.java:437)
    at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:131)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:301)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:285)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateClassesAndObjectsInFile(PackageCodegenImpl.java:119)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateFile(PackageCodegenImpl.java:138)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:70)
    at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generatePackage(CodegenFactory.kt:88)
    at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generateModule(CodegenFactory.kt:67)
    at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:616)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:203)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:164)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:51)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:86)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
    at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:346)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:102)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:240)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:90)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:601)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:93)
    at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1633)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:691)
    at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:830)
Caused by: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Couldn't transform method node:
<init> (ILInlineClass;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V:
   L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ILOAD 1
    ICONST_1
    IAND
    ICONST_0
    IF_ICMPNE L1
    NEW kotlinx/serialization/MissingFieldException
    DUP
    LDC "innerValue"
    INVOKESPECIAL kotlinx/serialization/MissingFieldException.<init> (Ljava/lang/String;)V
    CHECKCAST java/lang/Throwable
    ATHROW
   L1
    ALOAD 0
    DLOAD 2
    PUTFIELD DataClass.innerValue : D
    RETURN
   L2
    LOCALVARIABLE this LDataClass; L0 L2 0
    LOCALVARIABLE seen1 I L0 L2 1
    LOCALVARIABLE innerValue LInlineClass; L0 L2 2
    LOCALVARIABLE serializationConstructorMarker Lkotlinx/serialization/internal/SerializationConstructorMarker; L0 L2 3
    MAXSTACK = 3
    MAXLOCALS = 4

File is unknown
The root cause java.lang.AssertionError was thrown at: org.jetbrains.kotlin.codegen.optimization.MethodVerifier.transform(MethodVerifier.kt:28)
    at org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:92)
    at org.jetbrains.kotlin.codegen.FunctionCodegen.endVisit(FunctionCodegen.java:952)
    ... 50 more
Caused by: java.lang.AssertionError: AFTER mandatory stack transformations: incorrect bytecode
    at org.jetbrains.kotlin.codegen.optimization.MethodVerifier.transform(MethodVerifier.kt:28)
    at org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer.transform(CompositeMethodTransformer.kt:25)
    at org.jetbrains.kotlin.codegen.optimization.OptimizationMethodVisitor.performTransformations(OptimizationMethodVisitor.kt:62)
    at org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:70)
    ... 51 more
Caused by: java.lang.RuntimeException: org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 16: Expected D, but found R
    at org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer.runAnalyzer(MethodTransformer.java:34)
    at org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer.analyze(MethodTransformer.java:44)
    at org.jetbrains.kotlin.codegen.optimization.MethodVerifier.transform(MethodVerifier.kt:26)
    ... 54 more
Caused by: org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 16: Expected D, but found R
    at org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:291)
    at org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer.runAnalyzer(MethodTransformer.java:31)
    ... 56 more
Caused by: org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException: Expected D, but found R
    at org.jetbrains.org.objectweb.asm.tree.analysis.BasicVerifier.copyOperation(BasicVerifier.java:102)
    at org.jetbrains.org.objectweb.asm.tree.analysis.BasicVerifier.copyOperation(BasicVerifier.java:43)
    at org.jetbrains.org.objectweb.asm.tree.analysis.Frame.execute(Frame.java:288)
    at org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:187)
    ... 57 more

Interesting that if I move @Serializable(with = InlineClassSerializer::class) to inline class site instead of property site, the compiler stops with another message Inline classes are not supported by kotlinx.serialization yet. Am I not supposed to workaround this limitation?

The real world situation is that I'm trying to write serialization for external inner class (namely kotlin.time.Duration). In another case I would just change my class to be non-inline.

It might be a duplicate of #1060 yet I didn't see any inline classes there.

To Reproduce

fun main() {
    val value = Json.decodeFromString<DataClass>("""
        { "innerValue" : 69.0 }
    """.trimIndent())
    println(value.innerValue.innerInnerValue)
}

@Serializable
data class DataClass(@Serializable(with = InlineClassSerializer::class) val innerValue: InlineClass)

inline class InlineClass(val innerInnerValue: Double)

object InlineClassSerializer : KSerializer<InlineClass> {
    override val descriptor: SerialDescriptor =
            PrimitiveSerialDescriptor("InlineClass", PrimitiveKind.DOUBLE)

    override fun deserialize(decoder: Decoder): InlineClass {
        return InlineClass(decoder.decodeDouble())
    }

    override fun serialize(encoder: Encoder, value: InlineClass) {
        encoder.encodeDouble(value.innerInnerValue)
    }
}

Expected behavior
69.0

Environment

  • Kotlin version: 1.4.10
  • Library version: 1.0.0-RC
  • Kotlin platforms: JVM
  • Gradle version: 6.3
2 bug

Most helpful comment

@InsanusMokrassar Yes, we'll share the details later. They will be working in the new IR backend

All 6 comments

No, inline classes are not supported even with custom serializers :(

Btw found a pretty straightforward workaround. Just wrap inline class in another non-inline class then add custom serializer for that and it works.

In case of kotlin.time.Duration:

@Serializable(with = DurationSerializer::class)
public class SerializableDuration(
    public val value: Duration
)

public object DurationSerializer : KSerializer<SerializableDuration> {

    override val descriptor: SerialDescriptor
        get() = PrimitiveSerialDescriptor(SerializableDuration::class.simpleName!!, PrimitiveKind.DOUBLE)

    override fun deserialize(decoder: Decoder): SerializableDuration {
        return SerializableDuration(decoder.decodeDouble().nanoseconds)
    }

    override fun serialize(encoder: Encoder, value: SerializableDuration) {
        encoder.encodeDouble(value.value.inNanoseconds)
    }
}

Hello, @sandwwraith , is there some news about inline classes support?

@InsanusMokrassar Yes, we'll share the details later. They will be working in the new IR backend

@InsanusMokrassar Yes, we'll share the details later. They will be working in the new IR backend

Thank you for the update:)

It looks like #1244 may be relevant.

Was this page helpful?
0 / 5 - 0 ratings