diff --git a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt index 4ad465d2ea9..c80ff086c59 100644 --- a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt +++ b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt @@ -25,7 +25,7 @@ class JvmMetadataVersion(versionArray: IntArray, val isStrictSemantics: Boolean) } override fun isCompatibleWithCurrentCompilerVersion(): Boolean { - return isCompatibleInternal(INSTANCE_NEXT) + return isCompatibleInternal(if (isStrictSemantics) INSTANCE else INSTANCE_NEXT) } fun isCompatible(metadataVersionFromLanguageVersion: JvmMetadataVersion): Boolean { diff --git a/libraries/kotlinx-metadata/jvm/ReadMe.md b/libraries/kotlinx-metadata/jvm/ReadMe.md index 2af87c0d77f..badbc1a063f 100644 --- a/libraries/kotlinx-metadata/jvm/ReadMe.md +++ b/libraries/kotlinx-metadata/jvm/ReadMe.md @@ -35,18 +35,20 @@ dependencies { ## Overview -The entry point for reading the Kotlin metadata of a `.class` file is [`KotlinClassMetadata.read`](src/kotlinx/metadata/jvm/KotlinClassMetadata.kt). +The entry point for reading the Kotlin metadata of a `.class` file is [`KotlinClassMetadata.readStrict`](src/kotlinx/metadata/jvm/KotlinClassMetadata.kt). The data it takes is the [`kotlin.Metadata`](../../stdlib/jvm/runtime/kotlin/Metadata.kt) annotation on the class file generated by the Kotlin compiler. Obtain the `kotlin.Metadata` annotation reflectively or construct it from binary representation (e.g. by reading classfile with `org.objectweb.asm.ClassReader`), -and then use `KotlinClassMetadata.read` to obtain the correct instance of the class metadata. +and then use `KotlinClassMetadata.readStrict` to obtain the correct instance of the class metadata. ```kotlin val metadataAnnotation = Metadata( // pass arguments here ) -val metadata = KotlinClassMetadata.read(metadataAnnotation) +val metadata = KotlinClassMetadata.readStrict(metadataAnnotation) ``` +> There are other methods of reading metadata, but `readStrict` is a preferred one. See the differences in [working with different versions section](#working-with-different-versions). + `KotlinClassMetadata` is a sealed class, with subclasses representing all the different kinds of classes generated by the Kotlin compiler. Unless you are sure that you are reading a class of a specific kind and can do a simple cast, a `when` is a good choice to handle all the possibilities: @@ -112,6 +114,9 @@ val klass = KmClass().apply { ... } +``` + +Then, you can encode a resulting KmClass to an annotation. val annotation = KotlinClassMetadata.writeClass(klass) @@ -138,3 +143,39 @@ val module = metadata.kmModule val bytes = KotlinModuleMetadata.write(module) File("META-INF/main.kotlin_module").writeBytes(bytes) ``` + +## Working with different versions + +### Short guide + +There are two methods to read metadata: + +`readStrict()`: This method allows you to read the metadata strictly, meaning it will throw an exception if the metadata version is greater than what kotlinx-metadata-jvm understands. +It's suitable when your tooling can't tolerate reading potentially incomplete or incorrect information due to version differences. +It's also the only method that allows metadata transformation and `KotlinClassMetadata.write` subsequent calls. + +`readLenient()`: This method allows you to read the metadata leniently. +If the metadata version is higher than what kotlinx-metadata-jvm can interpret, it may ignore parts of the metadata it doesn't understand but it won't throw an exception. +It’s more suitable when your tooling needs to read metadata of possibly newer Kotlin versions and can handle incomplete data, because it is interested only in part of it (e.g. visibility of declarations) +**Metadata read in lenient mode can not be written back.** + +### Detailed explanation + +Kotlin compiler and its features evolve over time, and so its metadata format. Metadata format version is equal to the Kotlin compiler version. +As you might guess, evolving metadata format usually involves adding new fields for new Kotlin language features. Therefore, +some problems may occur when you're reading new metadata with an older version of Kotlin compiler or kotlinx-metadata-jvm library. + +By default, the Kotlin compiler (and similar, kotlinx-metadata-jvm library) have forward compatibility for versions not higher than current + 1. +It means that Kotlin compiler 2.1 can read metadata from Kotlin compiler 2.2, but not 2.3. The same is true for `KotlinClassMetadata.readStrict()` +method: it will throw an exception if you try to read metadata with version higher than `COMPATIBLE_METADATA_VERSION` + 1. +Such restriction comes from the fact that higher metadata versions (e.g. 2.3) might have some unknown fields that we skip during reading; therefore, if we write +transformed metadata back, missing some fields may result in corrupted metadata that is no longer valid for version 2.3. + +However, there are a lot of use-cases for metadata introspection alone, without further transformations — for example, binary-compatibility-validator which is interested only in visibility and modality of declarations. +For such use-cases it seems over restrictive to prohibit reading newer metadata versions (and therefore, requiring authors to do frequent updates of kotlinx-metadata-jvm dependency), +so there is a relaxed version of the reading method: `KotlinClassMetadata.readLenient()`. It is a best-effort reading method that will potentially skip all unknown fields, +but still provide some access to metadata. Keep in mind that this method has limitations: + +1. Metadata returned by this method can not be written back, because we are not sure if it is still valid format for newer versions. It is intended for introspection alone. +2. While some unknown fields are skipped, we cannot guarantee that metadata is not changed in the other unpredictable ways in the future. `readLenient()` tries its best, but still may throw a decoding exception if metadata cannot be read at all. + diff --git a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api index 3db82127018..7651b769ec1 100644 --- a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api +++ b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api @@ -1442,6 +1442,8 @@ public abstract class kotlinx/metadata/jvm/KotlinClassMetadata { public static final field SYNTHETIC_CLASS_KIND I public synthetic fun (Lkotlin/Metadata;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun read (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata; + public static final fun readLenient (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata; + public static final fun readStrict (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata; public static final fun writeClass (Lkotlinx/metadata/KmClass;)Lkotlin/Metadata; public static final fun writeClass (Lkotlinx/metadata/KmClass;[I)Lkotlin/Metadata; public static final fun writeClass (Lkotlinx/metadata/KmClass;[II)Lkotlin/Metadata; @@ -1478,6 +1480,8 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$Class$Writer : kotli public final class kotlinx/metadata/jvm/KotlinClassMetadata$Companion { public final fun read (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata; + public final fun readLenient (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata; + public final fun readStrict (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata; public final fun writeClass (Lkotlinx/metadata/KmClass;)Lkotlin/Metadata; public final fun writeClass (Lkotlinx/metadata/KmClass;[I)Lkotlin/Metadata; public final fun writeClass (Lkotlinx/metadata/KmClass;[II)Lkotlin/Metadata; diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinClassMetadata.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinClassMetadata.kt index 9a276b887ed..9402eb58e82 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinClassMetadata.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinClassMetadata.kt @@ -54,16 +54,18 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * Anything that does not belong to a Kotlin class (top-level declarations) is not present in * [Class] metadata, even if such declaration was in the same source file. See [FileFacade] for details. */ - public class Class internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { + public class Class internal constructor(annotationData: Metadata, lenient: Boolean) : KotlinClassMetadata(annotationData) { /** * Returns the [KmClass] representation of this metadata. * * Returns the same (mutable) [KmClass] instance every time. */ - public val kmClass: KmClass = run { + public val kmClass: KmClass + + init { val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - proto.toKmClass(strings) + kmClass = proto.toKmClass(strings) } /** @@ -122,16 +124,18 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * If Kotlin source file contains both classes and top-level declarations, only top-level declarations would be available in the corresponding file facade. * Classes would have their own JVM classfiles and their own metadata of [Class] kind. */ - public class FileFacade internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { + public class FileFacade internal constructor(annotationData: Metadata, lenient: Boolean) : KotlinClassMetadata(annotationData) { /** * Returns the [KmPackage] representation of this metadata. * * Returns the same (mutable) [KmPackage] instance every time. */ - public val kmPackage: KmPackage = run { + public val kmPackage: KmPackage + + init { val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - proto.toKmPackage(strings) + kmPackage = proto.toKmPackage(strings) } /** @@ -186,7 +190,7 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * Represents metadata of a class file containing a synthetic class, e.g. a class for lambda, `$DefaultImpls` class for interface * method implementations, `$WhenMappings` class for optimized `when` over enums, etc. */ - public class SyntheticClass internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { + public class SyntheticClass internal constructor(annotationData: Metadata, lenient: Boolean) : KotlinClassMetadata(annotationData) { private val functionData = annotationData.data1.takeIf(Array<*>::isNotEmpty)?.let { data1 -> JvmProtoBufUtil.readFunctionDataFrom(data1, annotationData.data2) @@ -204,9 +208,15 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * * Returns the same (mutable) [KmLambda] instance every time. */ - public val kmLambda: KmLambda? = if (!isLambda) null else { - val (strings, proto) = functionData!! - proto.toKmLambda(strings) + public val kmLambda: KmLambda? + + init { + if (!isLambda) { + kmLambda = null + } else { + val (strings, proto) = functionData!! + kmLambda = proto.toKmLambda(strings) + } } /** @@ -351,15 +361,17 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * @see MultiFileClassFacade * @see JvmMultifileClass */ - public class MultiFileClassPart internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { + public class MultiFileClassPart internal constructor(annotationData: Metadata, lenient: Boolean) : KotlinClassMetadata(annotationData) { /** * Returns the [KmPackage] representation of this metadata. * * Returns the same (mutable) [KmPackage] instance every time. */ - public val kmPackage: KmPackage = run { + public val kmPackage: KmPackage + + init { val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - proto.toKmPackage(strings) + kmPackage = proto.toKmPackage(strings) } /** @@ -595,37 +607,48 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * @see COMPATIBLE_METADATA_VERSION */ @JvmStatic - public fun read(annotationData: Metadata): KotlinClassMetadata { - checkMetadataVersionForRead(annotationData) + @Deprecated("todo", ReplaceWith("KotlinClassMetadata.readStrict(annotationData)"), DeprecationLevel.WARNING) + public fun read(annotationData: Metadata): KotlinClassMetadata = readImpl(annotationData, lenient = false) + + @JvmStatic + public fun readStrict(annotationData: Metadata): KotlinClassMetadata = readImpl(annotationData, lenient = false) + + @JvmStatic + public fun readLenient(annotationData: Metadata): KotlinClassMetadata = readImpl(annotationData, lenient = true) + + private fun readImpl(annotationData: Metadata, lenient: Boolean): KotlinClassMetadata { + checkMetadataVersionForRead(annotationData, lenient) return wrapIntoMetadataExceptionWhenNeeded { when (annotationData.kind) { - CLASS_KIND -> Class(annotationData) - FILE_FACADE_KIND -> FileFacade(annotationData) - SYNTHETIC_CLASS_KIND -> SyntheticClass(annotationData) + CLASS_KIND -> Class(annotationData, lenient) + FILE_FACADE_KIND -> FileFacade(annotationData, lenient) + SYNTHETIC_CLASS_KIND -> SyntheticClass(annotationData, lenient) MULTI_FILE_CLASS_FACADE_KIND -> MultiFileClassFacade(annotationData) - MULTI_FILE_CLASS_PART_KIND -> MultiFileClassPart(annotationData) + MULTI_FILE_CLASS_PART_KIND -> MultiFileClassPart(annotationData, lenient) else -> Unknown } } } - private fun checkMetadataVersionForRead(annotationData: Metadata) { + private fun checkMetadataVersionForRead(annotationData: Metadata, lenient: Boolean) { if (annotationData.metadataVersion.isEmpty()) throw IllegalArgumentException("Provided Metadata instance does not have metadataVersion in it and therefore is malformed and cannot be read.") val jvmMetadataVersion = JvmMetadataVersion( annotationData.metadataVersion, (annotationData.extraInt and (1 shl 3)/* see JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG */) != 0 ) - throwIfNotCompatible(jvmMetadataVersion) + throwIfNotCompatible(jvmMetadataVersion, lenient) } - internal fun throwIfNotCompatible(jvmMetadataVersion: JvmMetadataVersion) { - if (!jvmMetadataVersion.isCompatibleWithCurrentCompilerVersion()) { + internal fun throwIfNotCompatible(jvmMetadataVersion: JvmMetadataVersion, lenient: Boolean) { + val isAtLeast110 = jvmMetadataVersion.isAtLeast(1, 1, 0) + val isCompatible = if (lenient) isAtLeast110 else jvmMetadataVersion.isCompatibleWithCurrentCompilerVersion() + if (!isCompatible) { // Kotlin 1.0 produces classfiles with metadataVersion = 1.1.0, while 1.0.0 represents unsupported pre-1.0 Kotlin (see JvmMetadataVersion.kt:39) val postfix = - if (!jvmMetadataVersion.isAtLeast(1, 1, 0)) "while minimum supported version is 1.1.0 (Kotlin 1.0)." - else "while maximum supported version is ${JvmMetadataVersion.INSTANCE_NEXT}. To support newer versions, update the kotlinx-metadata-jvm library." + if (!isAtLeast110) "while minimum supported version is 1.1.0 (Kotlin 1.0)." + else "while maximum supported version is ${if (jvmMetadataVersion.isStrictSemantics) JvmMetadataVersion.INSTANCE else JvmMetadataVersion.INSTANCE_NEXT}. To support newer versions, update the kotlinx-metadata-jvm library." throw IllegalArgumentException("Provided Metadata instance has version $jvmMetadataVersion, $postfix") } } @@ -637,6 +660,11 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { } } + internal fun throwIfNotWriteable(writeable: Boolean, name: String) { + if (writeable) return + throw IllegalArgumentException("This $name cannot be written because it represents $name read in lenient mode") + } + /** * A class file kind signifying that the corresponding class file contains a declaration of a Kotlin class. * diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt index 0c04857ecc3..9dfd2348041 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt @@ -189,8 +189,8 @@ public class KotlinModuleMetadata private constructor( private fun dataFromBytes(bytes: ByteArray): ModuleMapping { return ModuleMapping.loadModuleMapping( bytes, "KotlinModuleMetadata", skipMetadataVersionCheck = false, - isJvmPackageNameSupported = true, reportIncompatibleVersionError = ::throwIfNotCompatible - ) + isJvmPackageNameSupported = true + ) { throwIfNotCompatible(it, lenient = false /* TODO */) } } } } diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/DifferentVersionsTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/DifferentVersionsTest.kt new file mode 100644 index 00000000000..8bd646b6af7 --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/DifferentVersionsTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlinx.metadata.test + +import kotlinx.metadata.jvm.KotlinClassMetadata +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion +import org.junit.Ignore +import org.junit.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertIs + +class DifferentVersionsTest { + val metadata = DifferentVersionsTest::class.java.getMetadata() + + fun Metadata.changeVersion(newVersion: IntArray) = Metadata( + kind, newVersion, + bytecodeVersion, data1, data2, extraString, packageName, extraInt + ) + + fun Metadata.addFlag(flags: Int) = Metadata( + kind, metadataVersion, bytecodeVersion, data1, data2, + extraString, packageName, + extraInt or flags + ) + + @Test + fun readsCurrentVersion() { + assertContentEquals(KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, metadata.metadataVersion) + assertIs(KotlinClassMetadata.readStrict(metadata)) + assertIs(KotlinClassMetadata.readLenient(metadata)) + } + + @Test + fun readsNextVersion() { + val md = metadata.changeVersion(JvmMetadataVersion.INSTANCE_NEXT.toArray()) + assertIs(KotlinClassMetadata.readStrict(md)) + assertIs(KotlinClassMetadata.readLenient(md)) + } + + @Test + fun readsN2Version() { + val versionPlus2 = JvmMetadataVersion.INSTANCE.next().next() + val md = metadata.changeVersion(versionPlus2.toArray()) + assertFailsWith { KotlinClassMetadata.readStrict(md) } + assertIs(KotlinClassMetadata.readLenient(md)) + } + + @Test + fun readsArbitraryFutureVersion() { + val md = metadata.changeVersion(intArrayOf(2, 5, 0)) + assertFailsWith { KotlinClassMetadata.readStrict(md) } + assertIs(KotlinClassMetadata.readLenient(md)) + } + + @Test + fun lenientCantReadPre1Version() { // strict is tested in MetadataExceptionsTest.testReadObsoleteVersion + val md = metadata.changeVersion(intArrayOf(1, 0, 0)) + assertFailsWith { KotlinClassMetadata.readLenient(md) } + } + + @Test + fun readsStrictSemanticsFlag() { + val md = metadata.changeVersion(JvmMetadataVersion.INSTANCE_NEXT.toArray()).addFlag(1 shl 3) + assertFailsWith { KotlinClassMetadata.readStrict(md) } + assertIs(KotlinClassMetadata.readLenient(md)) + } + + @Test // We decided to allow reading pre-release flag by both versions + fun readsPreReleaseFlag() { + val md = metadata.changeVersion(JvmMetadataVersion.INSTANCE_NEXT.toArray()).addFlag(1 shl 1) + assertIs(KotlinClassMetadata.readStrict(md)) + assertIs(KotlinClassMetadata.readLenient(md)) + } +} diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JavaUsageTest.java b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JavaUsageTest.java index ee9d7e0354a..0951031e3d8 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JavaUsageTest.java +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JavaUsageTest.java @@ -21,16 +21,16 @@ public class JavaUsageTest { @Test public void testKotlinClassHeader() { Metadata m = MetadataSmokeTest.class.getAnnotation(Metadata.class); - KmClass clazz1 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.read(m))).getKmClass(); + KmClass clazz1 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.readStrict(m))).getKmClass(); KotlinClassHeader kh = new KotlinClassHeader(m.k(), m.mv(), m.d1(), m.d2(), m.xs(), m.pn(), m.xi()); - KmClass clazz2 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.read(kh))).getKmClass(); + KmClass clazz2 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.readStrict(kh))).getKmClass(); assertEquals(clazz1.getName(), clazz2.getName()); } @Test public void testWritingBackWithDefaults() { Metadata m = MetadataSmokeTest.class.getAnnotation(Metadata.class); - KmClass clazz1 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.read(m))).getKmClass(); + KmClass clazz1 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.readStrict(m))).getKmClass(); Metadata written = KotlinClassMetadata.writeClass(clazz1); assertArrayEquals(written.mv(), KotlinClassMetadata.COMPATIBLE_METADATA_VERSION); assertEquals(0, written.xi()); diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataExceptionsTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataExceptionsTest.kt index c307dc3896e..29139457424 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataExceptionsTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataExceptionsTest.kt @@ -20,7 +20,7 @@ class MetadataExceptionsTest { val malformedMetadata = Metadata(KotlinClassMetadata.CLASS_KIND, KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, arrayOf(malformedInput)) val e = assertFailsWith { - (KotlinClassMetadata.read(malformedMetadata) as KotlinClassMetadata.Class) + (KotlinClassMetadata.readStrict(malformedMetadata) as KotlinClassMetadata.Class) } assertIs(e.cause) } @@ -36,7 +36,7 @@ class MetadataExceptionsTest { private fun doTestVersion(version: IntArray, expectedText: String) { val md = Metadata(metadataVersion = version) - val iae = assertFailsWith { KotlinClassMetadata.read(md) } + val iae = assertFailsWith { KotlinClassMetadata.readStrict(md) } assertContains(iae.message.orEmpty(), expectedText) } diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt index fcc296b0622..6e54f150790 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt @@ -13,7 +13,7 @@ internal fun Class<*>.getMetadata(): Metadata { } internal fun Metadata.readAsKmClass(): KmClass { - val clazz = KotlinClassMetadata.read(this) as? KotlinClassMetadata.Class + val clazz = KotlinClassMetadata.readStrict(this) as? KotlinClassMetadata.Class return clazz?.kmClass ?: error("Not a KotlinClassMetadata.Class: $clazz") } diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt new file mode 100644 index 00000000000..6065aebc145 --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlinx.metadata.test + +import kotlinx.metadata.jvm.KotlinClassMetadata +import org.junit.Ignore +import org.junit.Test +import kotlin.test.assertFailsWith + +class WritersContractTest { + val classMd = WritersContractTest::class.java.getMetadata() + val l: () -> Unit = {} + val lambdaMd = l::class.java.getMetadata() +// val fileFacadeMd = TODO + + @Test + @Ignore // TODO + fun lenientDataCantBeWritten() { + val lenientClass = KotlinClassMetadata.readLenient(classMd) as KotlinClassMetadata.Class + assertFailsWith { KotlinClassMetadata.writeClass(lenientClass.kmClass) } + } + + @Test + fun oldVersionCantBeWritten() { + val writeableClass = KotlinClassMetadata.readStrict(classMd) as KotlinClassMetadata.Class + assertFailsWith { KotlinClassMetadata.writeClass(writeableClass.kmClass, intArrayOf(1, 2, 0)) } + } +} diff --git a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt index 2e85e7c8291..ea6652d174c 100644 --- a/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt +++ b/libraries/tools/kotlinp/src/org/jetbrains/kotlin/kotlinp/Kotlinp.kt @@ -31,7 +31,7 @@ class Kotlinp(private val settings: KotlinpSettings) { internal fun readMetadata(metadata: Metadata): KotlinClassMetadata { return try { - KotlinClassMetadata.read(metadata) + KotlinClassMetadata.readLenient(metadata) } catch (e: IllegalArgumentException) { throw KotlinpException("inconsistent Kotlin metadata: ${e.message}") } diff --git a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt index eba0086482a..fd513d93c54 100644 --- a/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt +++ b/libraries/tools/kotlinp/test/org/jetbrains/kotlin/kotlinp/test/KotlinpTestUtils.kt @@ -80,8 +80,8 @@ private fun compileAndPrintAllFiles( } "class" -> { val metadata = kotlinp.readClassFile(outputFile) - val classFile = kotlinp.readMetadata(metadata) - val classFile2 = KotlinClassMetadata.read(transformClassFileWithNodes(metadata, classFile)) + val classFile = KotlinClassMetadata.readStrict(metadata) + val classFile2 = KotlinClassMetadata.readStrict(transformClassFileWithNodes(metadata, classFile)) for ((sb, classFileToRender) in listOf( main to classFile, afterNodes to classFile2 diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt index abedb0862f6..b8817d85141 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiMetadataProcessor.kt @@ -28,7 +28,7 @@ fun abiMetadataProcessor(annotationVisitor: AnnotationVisitor): AnnotationVisito } ?: intArrayOf(1, 4) val newHeader = runCatching { - when (val metadata = KotlinClassMetadata.read(header)) { + when (val metadata = KotlinClassMetadata.readStrict(header)) { is KotlinClassMetadata.Class -> { val klass = metadata.kmClass klass.removePrivateDeclarations() diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt index 8cd7028d8e5..ac7c337062b 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/handlers/utils.kt @@ -49,7 +49,7 @@ fun renderMetadata(pretty: Pretty, tree: JCAnnotation): String { extraString = args[JvmAnnotationNames.METADATA_EXTRA_STRING_FIELD_NAME].stringValue() ?: "", packageName = args[JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME].stringValue() ?: "", ) - val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.read(metadata)) + val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.readStrict(metadata)) // "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else // to avoid them being interpreted as Java comments. val sanitized = text.split('\n').dropLast(1).map { it.replace("/*", "(*").replace("*/", "*)") } diff --git a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt index ea0c6bab54c..430e96781bd 100644 --- a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt +++ b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt @@ -105,7 +105,7 @@ internal data class Kapt4ContextBinaryArtifact( } private fun Printer.renderMetadata(metadata: Metadata) { - val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.read(metadata)) + val text = Kotlinp(KotlinpSettings(isVerbose = true, sortDeclarations = true)).renderClassFile(KotlinClassMetadata.readLenient(metadata)) // "/*" and "*/" delimiters are used in kotlinp, for example to render type parameter names. Replace them with something else // to avoid them being interpreted as Java comments. val sanitized = text.split('\n') @@ -119,4 +119,4 @@ private fun Printer.renderMetadata(metadata: Metadata) { } println(" */") println("@kotlin.Metadata()") -} \ No newline at end of file +}