Skip to content

Commit

Permalink
[KxSerialization] Fix "IllegalAccessError: Update to static final field"
Browse files Browse the repository at this point in the history
Fixed #KT-57647

For value classes, if you add code to companion anonymous init block in IR, it will be executed in the instance constructor. This causes an error when initializing static fields, because writing to them can only occur from the <clinit> method.

The solution is to transfer the static field initialization code from an anonymous init block to the IR initializer of this field
  • Loading branch information
shanshin committed Apr 21, 2023
1 parent ea2e0bd commit 035172c
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,4 @@ abstract class AbstractJvmBlackBoxCodegenTestBase<R : ResultingArtifact.Frontend

enableMetaInfoHandler()
}

private fun TestConfigurationBuilder.configureModernJavaTest(jdkKind: TestJdkKind, jvmTarget: JvmTarget) {
defaultDirectives {
JDK_KIND with jdkKind
JVM_TARGET with jvmTarget
+WITH_STDLIB
+USE_JAVAC_BASED_ON_JVM_TARGET
+IGNORE_DEXING
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@

package org.jetbrains.kotlin.test.runners.codegen

import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.HandlersStepBuilder
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.backend.handlers.*
import org.jetbrains.kotlin.test.backend.ir.IrBackendInput
import org.jetbrains.kotlin.test.builders.*
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.DUMP_SMAP
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.RUN_DEX_CHECKER
import org.jetbrains.kotlin.test.directives.ConfigurationDirectives
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact
import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact
import org.jetbrains.kotlin.test.model.*
Expand Down Expand Up @@ -181,3 +187,13 @@ fun HandlersStepBuilder<BinaryArtifacts.Jvm>.inlineHandlers() {
::SMAPDumpHandler
)
}

fun TestConfigurationBuilder.configureModernJavaTest(jdkKind: TestJdkKind, jvmTarget: JvmTarget) {
defaultDirectives {
JvmEnvironmentConfigurationDirectives.JDK_KIND with jdkKind
JvmEnvironmentConfigurationDirectives.JVM_TARGET with jvmTarget
+ConfigurationDirectives.WITH_STDLIB
+CodegenTestDirectives.USE_JAVAC_BASED_ON_JVM_TARGET
+CodegenTestDirectives.IGNORE_DEXING
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
val kSerializerType = kSerializerClass.typeWith(compilerContext.irBuiltIns.anyType)
val arrayType = compilerContext.irBuiltIns.arrayClass.typeWith(kSerializerType)

return addValPropertyWithJvmField(arrayType, SerialEntityNames.CACHED_CHILD_SERIALIZERS_PROPERTY_NAME) {
+createArrayOfExpression(kSerializerType, cacheableSerializers.map { it ?: irNull() })
return addValPropertyWithJvmFieldInitializer(arrayType, SerialEntityNames.CACHED_CHILD_SERIALIZERS_PROPERTY_NAME) {
createArrayOfExpression(kSerializerType, cacheableSerializers.map { it ?: irNull() })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,30 @@ interface IrBuilderWithPluginContext {
}
}

fun IrClass.addValPropertyWithJvmFieldInitializer(
type: IrType,
name: Name,
visibility: DescriptorVisibility = DescriptorVisibilities.PRIVATE,
initializer: IrBuilderWithScope.() -> IrExpression
): IrProperty {
return generateSimplePropertyWithBackingField(name, type, this, visibility).apply {
val field = backingField!!

val builder = DeclarationIrBuilder(
compilerContext,
field.symbol,
field.startOffset,
field.endOffset
)
field.initializer = IrExpressionBodyImpl(builder.initializer())

val annotationCtor = compilerContext.jvmFieldClassSymbol.constructors.single { it.owner.isPrimary }
val annotationType = compilerContext.jvmFieldClassSymbol.defaultType

field.annotations += IrConstructorCallImpl.fromSymbolOwner(startOffset, endOffset, annotationType, annotationCtor)
}
}

/**
* Add all statements to the builder, except the last one.
* The last statement should be an expression, it will return as a result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,6 @@ public final class ListOfUsers : java/lang/Object {
AASTORE
ALOAD (0)
PUTSTATIC (ListOfUsers, $childSerializers, [Lkotlinx/serialization/KSerializer;)
LABEL (L1)
LINENUMBER (13)
RETURN
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,6 @@ public final class Container : java/lang/Object {
AASTORE
ALOAD (0)
PUTSTATIC (Container, $childSerializers, [Lkotlinx/serialization/KSerializer;)
LABEL (L1)
LINENUMBER (19)
RETURN
}

Expand Down
31 changes: 31 additions & 0 deletions plugins/kotlinx-serialization/testData/jdk11BoxIr/kt57647.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// TARGET_BACKEND: JVM_IR

// WITH_STDLIB
// IGNORE_DEXING

import kotlinx.serialization.*
import java.util.UUID

@Serializable
@JvmInline
value class Id(val id: @Contextual UUID) {
companion object {
fun random() = Id(UUID.randomUUID())
}
}

@Serializable
@JvmInline
value class Parametrized<T: Any>(val l: List<T>)

fun pageMain () {
val id: Id = Id.random()
println(id)
}


fun box(): String {
println(System.getProperty("java.version"))
pageMain()
return "OK"
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ fun main(args: Array<String>) {
model("boxIr")
}

testClass<AbstractSerializationJdk11IrBoxTest> {
model("jdk11BoxIr")
}

testClass<AbstractSerializationFirBlackBoxTest> {
model("boxIr")
model("firMembers")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ open class AbstractSerializationIrBoxTest : AbstractIrBlackBoxCodegenTest() {
}
}

open class AbstractSerializationJdk11IrBoxTest : AbstractIrBlackBoxCodegenTest() {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
builder.configureForKotlinxSerialization(useJdk11 = true)
}
}

open class AbstractSerializationWithoutRuntimeIrBoxTest : AbstractIrBlackBoxCodegenTest() {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ package org.jetbrains.kotlinx.serialization
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
import org.jetbrains.kotlin.test.bind
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.runners.codegen.configureModernJavaTest
import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
import org.jetbrains.kotlin.test.services.RuntimeClasspathProvider
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationComponentRegistrar
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIntrinsicsState
import org.jetbrains.kotlinx.serialization.compiler.fir.FirSerializationExtensionRegistrar
import java.io.File

private val librariesPaths = listOfNotNull(RuntimeLibraryInClasspathTest.coreLibraryPath, RuntimeLibraryInClasspathTest.jsonLibraryPath)
Expand Down Expand Up @@ -45,8 +48,16 @@ class SerializationRuntimeClasspathProvider(testServices: TestServices) : Runtim
}
}

fun TestConfigurationBuilder.configureForKotlinxSerialization(noLibraries: Boolean = false) {
fun TestConfigurationBuilder.configureForKotlinxSerialization(
noLibraries: Boolean = false,
useJdk11: Boolean = false
) {
useConfigurators(::SerializationEnvironmentConfigurator.bind(noLibraries))

if (useJdk11) {
configureModernJavaTest(TestJdkKind.FULL_JDK_11, JvmTarget.JVM_11)
}

if (!noLibraries) {
useCustomRuntimeClasspathProviders(::SerializationRuntimeClasspathProvider)
}
Expand Down

0 comments on commit 035172c

Please sign in to comment.