diff --git a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api index 7651b769ec1..80a07efa03e 100644 --- a/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api +++ b/libraries/kotlinx-metadata/jvm/api/kotlinx-metadata-jvm.api @@ -1440,10 +1440,14 @@ public abstract class kotlinx/metadata/jvm/KotlinClassMetadata { public static final field MULTI_FILE_CLASS_FACADE_KIND I public static final field MULTI_FILE_CLASS_PART_KIND I public static final field SYNTHETIC_CLASS_KIND I - public synthetic fun (Lkotlin/Metadata;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public abstract fun getFlags ()I + public abstract fun getVersion ()[I 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 abstract fun setFlags (I)V + public abstract fun setVersion ([I)V + public abstract fun write ()Lkotlin/Metadata; 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; @@ -1465,9 +1469,16 @@ public abstract class kotlinx/metadata/jvm/KotlinClassMetadata { } public final class kotlinx/metadata/jvm/KotlinClassMetadata$Class : kotlinx/metadata/jvm/KotlinClassMetadata { + public fun (Lkotlinx/metadata/KmClass;[II)V public final fun accept (Lkotlinx/metadata/KmClassVisitor;)V + public fun getFlags ()I public final fun getKmClass ()Lkotlinx/metadata/KmClass; + public fun getVersion ()[I + public fun setFlags (I)V + public final fun setKmClass (Lkotlinx/metadata/KmClass;)V + public fun setVersion ([I)V public final fun toKmClass ()Lkotlinx/metadata/KmClass; + public fun write ()Lkotlin/Metadata; } public final class kotlinx/metadata/jvm/KotlinClassMetadata$Class$Writer : kotlinx/metadata/internal/ClassWriter { @@ -1482,6 +1493,7 @@ 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 transform (Lkotlin/Metadata;Lkotlin/jvm/functions/Function1;)Lkotlin/Metadata; 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; @@ -1509,9 +1521,16 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$Companion { } public final class kotlinx/metadata/jvm/KotlinClassMetadata$FileFacade : kotlinx/metadata/jvm/KotlinClassMetadata { + public fun (Lkotlinx/metadata/KmPackage;[II)V public final fun accept (Lkotlinx/metadata/KmPackageVisitor;)V + public fun getFlags ()I public final fun getKmPackage ()Lkotlinx/metadata/KmPackage; + public fun getVersion ()[I + public fun setFlags (I)V + public final fun setKmPackage (Lkotlinx/metadata/KmPackage;)V + public fun setVersion ([I)V public final fun toKmPackage ()Lkotlinx/metadata/KmPackage; + public fun write ()Lkotlin/Metadata; } public final class kotlinx/metadata/jvm/KotlinClassMetadata$FileFacade$Writer : kotlinx/metadata/internal/PackageWriter { @@ -1523,7 +1542,14 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$FileFacade$Writer : } public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassFacade : kotlinx/metadata/jvm/KotlinClassMetadata { + public fun (Ljava/util/List;[II)V + public fun getFlags ()I public final fun getPartClassNames ()Ljava/util/List; + public fun getVersion ()[I + public fun setFlags (I)V + public final fun setPartClassNames (Ljava/util/List;)V + public fun setVersion ([I)V + public fun write ()Lkotlin/Metadata; } public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassFacade$Writer { @@ -1535,10 +1561,18 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassFacade } public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassPart : kotlinx/metadata/jvm/KotlinClassMetadata { + public fun (Lkotlinx/metadata/KmPackage;Ljava/lang/String;[II)V public final fun accept (Lkotlinx/metadata/KmPackageVisitor;)V public final fun getFacadeClassName ()Ljava/lang/String; + public fun getFlags ()I public final fun getKmPackage ()Lkotlinx/metadata/KmPackage; + public fun getVersion ()[I + public final fun setFacadeClassName (Ljava/lang/String;)V + public fun setFlags (I)V + public final fun setKmPackage (Lkotlinx/metadata/KmPackage;)V + public fun setVersion ([I)V public final fun toKmPackage ()Lkotlinx/metadata/KmPackage; + public fun write ()Lkotlin/Metadata; } public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassPart$Writer : kotlinx/metadata/internal/PackageWriter { @@ -1550,10 +1584,17 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassPart$W } public final class kotlinx/metadata/jvm/KotlinClassMetadata$SyntheticClass : kotlinx/metadata/jvm/KotlinClassMetadata { + public fun (Lkotlinx/metadata/KmLambda;[II)V public final fun accept (Lkotlinx/metadata/KmLambdaVisitor;)V + public fun getFlags ()I public final fun getKmLambda ()Lkotlinx/metadata/KmLambda; + public fun getVersion ()[I public final fun isLambda ()Z + public fun setFlags (I)V + public final fun setKmLambda (Lkotlinx/metadata/KmLambda;)V + public fun setVersion ([I)V public final fun toKmLambda ()Lkotlinx/metadata/KmLambda; + public fun write ()Lkotlin/Metadata; } public final class kotlinx/metadata/jvm/KotlinClassMetadata$SyntheticClass$Writer : kotlinx/metadata/internal/LambdaWriter { @@ -1565,10 +1606,16 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$SyntheticClass$Write } public final class kotlinx/metadata/jvm/KotlinClassMetadata$Unknown : kotlinx/metadata/jvm/KotlinClassMetadata { + public static final field Companion Lkotlinx/metadata/jvm/KotlinClassMetadata$Unknown$Companion; public static final field INSTANCE Lkotlinx/metadata/jvm/KotlinClassMetadata$Unknown; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; + public fun getFlags ()I + public fun getVersion ()[I + public fun setFlags (I)V + public fun setVersion ([I)V + public fun write ()Lkotlin/Metadata; +} + +public final class kotlinx/metadata/jvm/KotlinClassMetadata$Unknown$Companion { } public final class kotlinx/metadata/jvm/KotlinModuleMetadata { 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 9402eb58e82..e4972485525 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinClassMetadata.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinClassMetadata.kt @@ -12,18 +12,22 @@ package kotlinx.metadata.jvm import kotlinx.metadata.* import kotlinx.metadata.internal.* import kotlinx.metadata.jvm.internal.* +import kotlinx.metadata.jvm.internal.JvmReadUtils.checkMetadataVersion +import kotlinx.metadata.jvm.internal.JvmReadUtils.readMetadataImpl import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion -import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable import java.util.* /** * Represents the parsed metadata of a Kotlin JVM class file. Entry point for parsing metadata on JVM. * - * To create an instance of [KotlinClassMetadata], first obtain an instance of [Metadata] annotation on a class file, and then call [KotlinClassMetadata.read]. + * To create an instance of [KotlinClassMetadata], first obtain an instance of [Metadata] annotation on a class file, + * and then call [KotlinClassMetadata.readStrict] or [KotlinClassMetadata.readLenient], depending on your application (see 'Working with different versions' section in the readme). + * * [Metadata] annotation can be obtained either via reflection or created from data from a binary class file, using its constructor or helper function [kotlinx.metadata.jvm.Metadata]. * - * [KotlinClassMetadata] itself does not have any meaningful methods or properties; to work with it, it is required to do a `when` over subclasses. + * [KotlinClassMetadata] alone exposes only [KotlinClassMetadata.version] and [KotlinClassMetadata.flags]; + * to work with it, it is required to do a `when` over subclasses. * Different subclasses of [KotlinClassMetadata] represent different kinds of class files produced by Kotlin compiler. * It is recommended to study documentation for each subclass and decide what subclasses one has to handle in the `when` expression, * trying to cover as much as possible. @@ -32,11 +36,14 @@ import java.util.* * Most of the subclasses declare a property to view metadata as a Km data structure — for example, [KotlinClassMetadata.Class.kmClass]. * Some of them also can contain additional properties, e.g. [KotlinClassMetadata.MultiFileClassPart.facadeClassName]. * Km data structures represent Kotlin declarations and offer a variety of properties to introspect and alter them. - * After desired changes are made, it is possible to get a new raw metadata instance with a corresponding `write` function, such as [KotlinClassMetadata.writeClass]. + * After desired changes are made to a Km data structure or to [KotlinClassMetadata] itself, + * it is possible to get a new raw metadata instance with a [write] function. + * + * > If metadata has been read with [readLenient], [write] will throw an exception. * * Here is an example of reading a content name of some metadata: * ``` - * fun displayName(metadata: Metadata): String = when (val kcm = KotlinClassMetadata.read(metadata)) { + * fun displayName(metadata: Metadata): String = when (val kcm = KotlinClassMetadata.readStrict(metadata)) { * is KotlinClassMetadata.Class -> "Class ${kcm.kmClass.name}" * is KotlinClassMetadata.FileFacade -> "File facade with functions: ${kcm.kmPackage.functions.joinToString { it.name }}" * is KotlinClassMetadata.SyntheticClass -> kcm.kmLambda?.function?.name?.let { "Lambda $it" } ?: "Synthetic class" @@ -45,8 +52,32 @@ import java.util.* * is KotlinClassMetadata.Unknown -> "Unknown metadata" * } * ``` + * + * > In this example, only introspection (reading) is performed. In such cases, to support more broad range of metadata versions, you may also use [readLenient] method. */ -public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { +public sealed class KotlinClassMetadata { + + /** + * Encodes and writes this metadata to the new instance of [Metadata]. + * + * This method encodes all available data, including [version] and [flags]. + * Due to technical limitations, it is not possible to write the metadata when the specified version is less (lexicographically) than `[1, 4]` + * + * @throws IllegalArgumentException if metadata is malformed or [version] of this instance is less than 1.4 and cannot be written. + */ + public abstract fun write(): Metadata + + /** + * Version of this metadata. + */ + public abstract var version: IntArray + + /** + * Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values. + */ + public abstract var flags: Int + + internal var isAllowedToWrite: Boolean = true /** * Represents metadata of a class file containing a declaration of a Kotlin class. @@ -54,18 +85,34 @@ 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, lenient: Boolean) : KotlinClassMetadata(annotationData) { - + public class Class public constructor( /** - * Returns the [KmClass] representation of this metadata. + * [KmClass] representation of this metadata. * * Returns the same (mutable) [KmClass] instance every time. */ - public val kmClass: KmClass + public var kmClass: KmClass, + public override var version: IntArray, + public override var flags: Int, + ) : KotlinClassMetadata() { - init { - val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - kmClass = proto.toKmClass(strings) + internal constructor(annotationData: Metadata, lenient: Boolean) : this( + JvmReadUtils.readKmClass(annotationData), + annotationData.metadataVersion, + annotationData.extraInt + ) { + isAllowedToWrite = !lenient + } + + override fun write(): Metadata { + throwIfNotWriteable(isAllowedToWrite, "class") + checkMetadataVersion(version) + return wrapWriteIntoIAE { + val writer = ClassWriter(JvmStringTable()) + writer.writeClass(kmClass) + val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) + Metadata(CLASS_KIND, version, d1, d2, extraInt = flags) + } } /** @@ -124,18 +171,40 @@ 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, lenient: Boolean) : KotlinClassMetadata(annotationData) { - + public class FileFacade public constructor( /** - * Returns the [KmPackage] representation of this metadata. + * [KmPackage] representation of this metadata. * * Returns the same (mutable) [KmPackage] instance every time. */ - public val kmPackage: KmPackage + public var kmPackage: KmPackage, + /** + * Version of this metadata. + */ + public override var version: IntArray, + /** + * Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values. + */ + public override var flags: Int, + ) : KotlinClassMetadata() { - init { - val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - kmPackage = proto.toKmPackage(strings) + internal constructor(annotationData: Metadata, lenient: Boolean) : this( + JvmReadUtils.readKmPackage(annotationData), + annotationData.metadataVersion, + annotationData.extraInt + ) { + isAllowedToWrite = !lenient + } + + override fun write(): Metadata { + throwIfNotWriteable(isAllowedToWrite, "file facade") + checkMetadataVersion(version) + return wrapWriteIntoIAE { + val writer = PackageWriter(JvmStringTable()) + writer.writePackage(kmPackage) + val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) + Metadata(FILE_FACADE_KIND, version, d1, d2, extraInt = flags) + } } /** @@ -190,34 +259,52 @@ 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, lenient: Boolean) : KotlinClassMetadata(annotationData) { - private val functionData = - annotationData.data1.takeIf(Array<*>::isNotEmpty)?.let { data1 -> - JvmProtoBufUtil.readFunctionDataFrom(data1, annotationData.data2) + public class SyntheticClass( + /** + * [KmLambda] representation of this metadata, or `null` if this synthetic class does not represent a lambda. + * + * Returns the same (mutable) [KmLambda] instance every time. + */ + public var kmLambda: KmLambda?, + /** + * Version of this metadata. + */ + public override var version: IntArray, + /** + * Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values. + */ + public override var flags: Int, + ) : KotlinClassMetadata() { + + internal constructor(annotationData: Metadata, lenient: Boolean) : this( + JvmReadUtils.readKmLambda(annotationData), + annotationData.metadataVersion, + annotationData.extraInt + ) { + isAllowedToWrite = !lenient + } + + override fun write(): Metadata { + throwIfNotWriteable(isAllowedToWrite, if (isLambda) "lambda" else "synthetic class") + checkMetadataVersion(version) + return if (isLambda) wrapWriteIntoIAE { + val writer = LambdaWriter(JvmStringTable()) + writer.writeLambda(kmLambda!!) + val proto = writer.t?.build() + val (d1, d2) = + if (proto != null) writeProtoBufData(proto, writer.c) + else Pair(emptyArray(), emptyArray()) + Metadata(SYNTHETIC_CLASS_KIND, version, d1, d2, extraInt = flags) + } else { + Metadata(SYNTHETIC_CLASS_KIND, version, emptyArray(), emptyArray(), extraInt = flags) } + } /** * Returns `true` if this synthetic class is a class file compiled for a Kotlin lambda. */ public val isLambda: Boolean - get() = annotationData.data1.isNotEmpty() - - - /** - * Returns the [KmLambda] representation of this metadata, or `null` if this synthetic class does not represent a lambda. - * - * Returns the same (mutable) [KmLambda] instance every time. - */ - public val kmLambda: KmLambda? - - init { - if (!isLambda) { - kmLambda = null - } else { - val (strings, proto) = functionData!! - kmLambda = proto.toKmLambda(strings) - } - } + get() = kmLambda != null /** * Creates a new [KmLambda] instance from this synthetic class metadata. @@ -312,11 +399,37 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * @see MultiFileClassPart * @see JvmMultifileClass */ - public class MultiFileClassFacade internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { + public class MultiFileClassFacade( /** * JVM internal names of the part classes which this multi-file class combines. */ - public val partClassNames: List = annotationData.data1.asList() + public var partClassNames: List, + /** + * Version of this metadata. + */ + public override var version: IntArray, + /** + * Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values. + */ + public override var flags: Int, + ) : KotlinClassMetadata() { + + internal constructor(annotationData: Metadata, lenient: Boolean) : this( + annotationData.data1.asList(), + annotationData.metadataVersion, + annotationData.extraInt + ) { + isAllowedToWrite = !lenient + } + + override fun write(): Metadata { + throwIfNotWriteable(isAllowedToWrite, "multi-file class facade") + checkMetadataVersion(version) + return Metadata( + MULTI_FILE_CLASS_FACADE_KIND, version, + partClassNames.toTypedArray(), extraInt = flags + ) + } /** * A writer that generates the metadata of a multi-file class facade. @@ -361,24 +474,48 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * @see MultiFileClassFacade * @see JvmMultifileClass */ - public class MultiFileClassPart internal constructor(annotationData: Metadata, lenient: Boolean) : KotlinClassMetadata(annotationData) { + public class MultiFileClassPart public constructor( /** * Returns the [KmPackage] representation of this metadata. * * Returns the same (mutable) [KmPackage] instance every time. */ - public val kmPackage: KmPackage - - init { - val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2) - kmPackage = proto.toKmPackage(strings) - } - + public var kmPackage: KmPackage, /** * JVM internal name of the corresponding multi-file class facade. */ - public val facadeClassName: String - get() = annotationData.extraString + public var facadeClassName: String, + /** + * Version of this metadata. + */ + public override var version: IntArray, + /** + * Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values. + */ + public override var flags: Int, + ) : KotlinClassMetadata() { + + internal constructor(annotationData: Metadata, lenient: Boolean) : this( + JvmReadUtils.readKmPackage(annotationData), + annotationData.extraString, + annotationData.metadataVersion, + annotationData.extraInt + ) { + isAllowedToWrite = !lenient + } + + override fun write(): Metadata { + throwIfNotWriteable(isAllowedToWrite, "multi-file class part") + checkMetadataVersion(version) + return wrapWriteIntoIAE { + val writer = PackageWriter(JvmStringTable()) + writer.writePackage(kmPackage) + val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) + Metadata( + MULTI_FILE_CLASS_PART_KIND, version, d1, d2, facadeClassName, extraInt = flags + ) + } + } /** * Creates a new [KmPackage] instance from this multi-file class part metadata. @@ -436,13 +573,51 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * Represents metadata of an unknown class file. This class is used if an old version of this library is used against a new kind * of class files generated by the Kotlin compiler, unsupported by this library. */ - public data object Unknown : KotlinClassMetadata(Metadata()) + public class Unknown internal constructor(private val original: Metadata, private val lenient: Boolean) : KotlinClassMetadata() { + /** + * Version of this metadata. + */ + public override var version: IntArray = original.metadataVersion + + /** + * Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values. + */ + public override var flags: Int = original.extraInt + + override fun write(): Metadata { + throwIfNotWriteable(!lenient, "unknown kind") + return Metadata(original.kind, version, original.data1, original.data2, original.extraString, original.packageName, flags) + } + + @PublishedApi + @Deprecated("This declaration is intended for binary compatibility only", level = DeprecationLevel.HIDDEN) + internal companion object { + @JvmField // For binary compatibility with previous `data object Unknown` + public val INSTANCE: Unknown = Unknown(Metadata(kind = 99), true) + } + } /** * Collection of methods for reading and writing [KotlinClassMetadata], * as well as metadata kind constants and [COMPATIBLE_METADATA_VERSION] constant. */ public companion object { + + /** + * Utility method to combine reading and writing of metadata: + * First, [metadata] is parsed with [readStrict]; then, [transformer] is called on a read instance. + * [transformer] may mutate passed instance of [KotlinClassMetadata] to achieve a desired result. + * After transformation, [KotlinClassMetadata.write] method is called and its result becomes return value of this method. + * + * @throws IllegalArgumentException if metadata cannot be read or written + * + * @see readStrict + * @see write + */ + public fun transform(metadata: Metadata, transformer: (KotlinClassMetadata) -> Unit): Metadata { + return readStrict(metadata).apply(transformer).write() + } + /** * Writes contents of [kmClass] as the class metadata. * @@ -455,17 +630,13 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { */ @JvmStatic @JvmOverloads + @Deprecated("Use a KotlinClassMetadata.Class instance and its write() member function", level = DeprecationLevel.WARNING) public fun writeClass( kmClass: KmClass, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, extraInt: Int = 0, - ): Metadata = wrapWriteIntoIAE { - checkMetadataVersion(metadataVersion) - val writer = ClassWriter(JvmStringTable()) - writer.writeClass(kmClass) - val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) - Metadata(CLASS_KIND, metadataVersion, d1, d2, extraInt = extraInt) - } + ): Metadata = Class(kmClass, metadataVersion, extraInt).write() + /** * Writes [kmPackage] contents as the file facade metadata. @@ -479,17 +650,12 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { */ @JvmStatic @JvmOverloads + @Deprecated("Use a KotlinClassMetadata.FileFacade instance and its write() member function", level = DeprecationLevel.WARNING) public fun writeFileFacade( kmPackage: KmPackage, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, extraInt: Int = 0, - ): Metadata = wrapWriteIntoIAE { - checkMetadataVersion(metadataVersion) - val writer = PackageWriter(JvmStringTable()) - writer.writePackage(kmPackage) - val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) - Metadata(FILE_FACADE_KIND, metadataVersion, d1, d2, extraInt = extraInt) - } + ): Metadata = FileFacade(kmPackage, metadataVersion, extraInt).write() /** * Writes [kmLambda] as the synthetic class metadata. @@ -503,20 +669,12 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { */ @JvmStatic @JvmOverloads + @Deprecated("Use a KotlinClassMetadata.SyntheticClass instance and its write() member function", level = DeprecationLevel.WARNING) public fun writeLambda( kmLambda: KmLambda, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, extraInt: Int = 0, - ): Metadata = wrapWriteIntoIAE { - checkMetadataVersion(metadataVersion) - val writer = LambdaWriter(JvmStringTable()) - writer.writeLambda(kmLambda) - val proto = writer.t?.build() - val (d1, d2) = - if (proto != null) writeProtoBufData(proto, writer.c) - else Pair(emptyArray(), emptyArray()) - Metadata(SYNTHETIC_CLASS_KIND, metadataVersion, d1, d2, extraInt = extraInt) - } + ): Metadata = SyntheticClass(kmLambda, metadataVersion, extraInt).write() /** * Writes synthetic class metadata. @@ -530,13 +688,11 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { */ @JvmStatic @JvmOverloads + @Deprecated("Use a KotlinClassMetadata.SyntheticClass instance and its write() member function", level = DeprecationLevel.WARNING) public fun writeSyntheticClass( metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, extraInt: Int = 0, - ): Metadata { - checkMetadataVersion(metadataVersion) - return Metadata(SYNTHETIC_CLASS_KIND, metadataVersion, emptyArray(), emptyArray(), extraInt = extraInt) - } + ): Metadata = SyntheticClass(null, metadataVersion, extraInt).write() /** * Writes metadata of the multi-file class facade. @@ -551,16 +707,14 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { */ @JvmStatic @JvmOverloads + @Deprecated( + "Use a KotlinClassMetadata.MultiFileClassFacade instance and its write() member function", + level = DeprecationLevel.WARNING + ) public fun writeMultiFileClassFacade( partClassNames: List, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, extraInt: Int = 0, - ): Metadata { - checkMetadataVersion(metadataVersion) - return Metadata( - MULTI_FILE_CLASS_FACADE_KIND, metadataVersion, - partClassNames.toTypedArray(), extraInt = extraInt - ) - } + ): Metadata = MultiFileClassFacade(partClassNames, metadataVersion, extraInt).write() /** * Writes the metadata of the multi-file class part. @@ -575,20 +729,16 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { */ @JvmStatic @JvmOverloads + @Deprecated( + "Use a KotlinClassMetadata.MultiFileClassPart instance and its write() member function", + level = DeprecationLevel.WARNING + ) public fun writeMultiFileClassPart( kmPackage: KmPackage, facadeClassName: String, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, extraInt: Int = 0, - ): Metadata = wrapWriteIntoIAE { - checkMetadataVersion(metadataVersion) - val writer = PackageWriter(JvmStringTable()) - writer.writePackage(kmPackage) - val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c) - Metadata( - MULTI_FILE_CLASS_PART_KIND, metadataVersion, d1, d2, facadeClassName, extraInt = extraInt - ) - } + ): Metadata = MultiFileClassPart(kmPackage, facadeClassName, metadataVersion, extraInt).write() /** * Reads and parses the given annotation data of a Kotlin JVM class file and returns the correct type of [KotlinClassMetadata] encoded by @@ -607,62 +757,54 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { * @see COMPATIBLE_METADATA_VERSION */ @JvmStatic - @Deprecated("todo", ReplaceWith("KotlinClassMetadata.readStrict(annotationData)"), DeprecationLevel.WARNING) - public fun read(annotationData: Metadata): KotlinClassMetadata = readImpl(annotationData, lenient = false) + @Deprecated( + "read() throws an error if metadata version is too high. Use either readStrict() if you want to retain this behavior, or readLenient() if you want to try to read newer metadata.", + ReplaceWith("KotlinClassMetadata.readStrict(annotationData)"), + DeprecationLevel.WARNING + ) + public fun read(annotationData: Metadata): KotlinClassMetadata = readMetadataImpl(annotationData, lenient = false) + /** + * Reads and parses the given annotation data of a Kotlin JVM class file and returns the correct type of [KotlinClassMetadata] encoded by + * this annotation, if the metadata version is supported. + * + * [annotationData] may be obtained reflectively, constructed manually or with helper [kotlinx.metadata.jvm.Metadata] function, + * or equivalent [KotlinClassHeader] can be used. + * + * This method can read only supported metadata versions (see [COMPATIBLE_METADATA_VERSION] for definition). + * It will throw an exception if the metadata version is greater than what kotlinx-metadata-jvm understands. + * It is suitable when your tooling cannot tolerate reading potentially incomplete or incorrect information due to version differences. + * It is also the only method that allows metadata transformation and `KotlinClassMetadata.write` subsequent calls. + * + * @throws IllegalArgumentException if the metadata version is unsupported + * + * @see COMPATIBLE_METADATA_VERSION + */ @JvmStatic - public fun readStrict(annotationData: Metadata): KotlinClassMetadata = readImpl(annotationData, lenient = false) + public fun readStrict(annotationData: Metadata): KotlinClassMetadata = readMetadataImpl(annotationData, lenient = false) + /** + * Reads and parses the given annotation data of a Kotlin JVM class file and returns the correct type of [KotlinClassMetadata] encoded by + * this annotation. [KotlinClassMetadata] instances obtained from this method cannot be written. + * + * [annotationData] may be obtained reflectively, constructed manually or with helper [kotlinx.metadata.jvm.Metadata] function, + * or equivalent [KotlinClassHeader] can be used. + * + * This method makes best effort to read unsupported metadata versions. + * If metadata version is greater than [COMPATIBLE_METADATA_VERSION] + 1, this method may ignore parts of the metadata it does not understand but it will not throw an exception. + * Because obtained metadata can be incomplete, its [KotlinClassMetadata.write] method will throw an exception. + * This method still cannot read metadata produced by pre-1.0 compilers. + * + * @throws IllegalArgumentException if the metadata version is that of Kotlin 1.0 or metadata format has been changed in an unpredictable way and reading of incompatible metadata is not possible + * + * @see COMPATIBLE_METADATA_VERSION + */ @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, 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, lenient) - else -> Unknown - } - } - } - - 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, lenient) - } - - 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 (!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") - } - } - - private fun checkMetadataVersion(version: IntArray) { - require(version.size >= 2 && version[0] >= 1 && (version[0] > 1 || version[1] >= 4)) { - "This version of kotlinx-metadata-jvm doesn't support writing Kotlin metadata of version earlier than 1.4. " + - "Please change the version from ${version.toList()} to at least [1, 4]." - } - } + public fun readLenient(annotationData: Metadata): KotlinClassMetadata = readMetadataImpl(annotationData, lenient = true) 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") + throw IllegalArgumentException("This $name cannot be written because it represents metadata read in lenient mode") } /** @@ -708,7 +850,10 @@ public sealed class KotlinClassMetadata(internal val annotationData: Metadata) { /** * The latest stable metadata version supported by this version of the library. - * The library can read Kotlin metadata produced by Kotlin compilers from 1.0 up to and including this version + 1 minor. + * The library can read in strict mode Kotlin metadata produced by Kotlin compilers from 1.0 up to and including this version + 1 minor. + * + * In other words, metadata version is supported if it is greater or equal than 1.1, and less or equal than [COMPATIBLE_METADATA_VERSION] + 1 minor version. + * Note that metadata version is 1.1 for Kotlin < 1.4, and is equal to the language version starting from Kotlin 1.4. * * For example, if the latest supported stable Kotlin version is 1.7.0, kotlinx-metadata-jvm can read binaries produced by Kotlin compilers from 1.0 * to 1.8.* inclusively. In this case, this property will have the value `[1, 7, 0]`. 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 9dfd2348041..6fc9de0e503 100644 --- a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt @@ -17,7 +17,7 @@ package kotlinx.metadata.jvm import kotlinx.metadata.* import kotlinx.metadata.internal.toKmClass import kotlinx.metadata.jvm.KotlinClassMetadata.Companion.COMPATIBLE_METADATA_VERSION -import kotlinx.metadata.jvm.KotlinClassMetadata.Companion.throwIfNotCompatible +import kotlinx.metadata.jvm.internal.JvmReadUtils.throwIfNotCompatible import kotlinx.metadata.jvm.internal.wrapIntoMetadataExceptionWhenNeeded import kotlinx.metadata.jvm.internal.wrapWriteIntoIAE import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf diff --git a/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt new file mode 100644 index 00000000000..b8b9ad61b30 --- /dev/null +++ b/libraries/kotlinx-metadata/jvm/src/kotlinx/metadata/jvm/internal/JvmReadUtils.kt @@ -0,0 +1,81 @@ +/* + * 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.jvm.internal + +import kotlinx.metadata.KmClass +import kotlinx.metadata.KmLambda +import kotlinx.metadata.KmPackage +import kotlinx.metadata.internal.toKmClass +import kotlinx.metadata.internal.toKmLambda +import kotlinx.metadata.internal.toKmPackage +import kotlinx.metadata.jvm.KotlinClassMetadata +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil + +internal object JvmReadUtils { + internal fun readKmClass(annotationData: Metadata): KmClass { + val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(annotationData.requireNotEmpty(), annotationData.data2) + return proto.toKmClass(strings) + } + + internal fun readKmPackage(annotationData: Metadata): KmPackage { + val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2) + return proto.toKmPackage(strings) + } + + internal fun readKmLambda(annotationData: Metadata): KmLambda? { + val functionData = + annotationData.data1.takeIf(Array<*>::isNotEmpty)?.let { data1 -> + JvmProtoBufUtil.readFunctionDataFrom(data1, annotationData.data2) + } ?: return null + val (strings, proto) = functionData + return proto.toKmLambda(strings) + } + + internal fun readMetadataImpl(annotationData: Metadata, lenient: Boolean): KotlinClassMetadata { + checkMetadataVersionForRead(annotationData, lenient) + + return wrapIntoMetadataExceptionWhenNeeded { + when (annotationData.kind) { + KotlinClassMetadata.CLASS_KIND -> KotlinClassMetadata.Class(annotationData, lenient) + KotlinClassMetadata.FILE_FACADE_KIND -> KotlinClassMetadata.FileFacade(annotationData, lenient) + KotlinClassMetadata.SYNTHETIC_CLASS_KIND -> KotlinClassMetadata.SyntheticClass(annotationData, lenient) + KotlinClassMetadata.MULTI_FILE_CLASS_FACADE_KIND -> KotlinClassMetadata.MultiFileClassFacade(annotationData, lenient) + KotlinClassMetadata.MULTI_FILE_CLASS_PART_KIND -> KotlinClassMetadata.MultiFileClassPart(annotationData, lenient) + else -> KotlinClassMetadata.Unknown(annotationData, lenient) + } + } + } + + internal 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, lenient) + } + + 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 (!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") + } + } + + internal fun checkMetadataVersion(version: IntArray) { + require(version.size >= 2 && version[0] >= 1 && (version[0] > 1 || version[1] >= 4)) { + "This version of kotlinx-metadata-jvm doesn't support writing Kotlin metadata of version earlier than 1.4. " + + "Please change the version from ${version.toList()} to at least [1, 4]." + } + } +} 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 0951031e3d8..f7cb72b1fd6 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JavaUsageTest.java +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/JavaUsageTest.java @@ -30,9 +30,9 @@ public class JavaUsageTest { @Test public void testWritingBackWithDefaults() { Metadata m = MetadataSmokeTest.class.getAnnotation(Metadata.class); - KmClass clazz1 = ((KotlinClassMetadata.Class) Objects.requireNonNull(KotlinClassMetadata.readStrict(m))).getKmClass(); - Metadata written = KotlinClassMetadata.writeClass(clazz1); + KotlinClassMetadata clazz1 = ((KotlinClassMetadata) Objects.requireNonNull(KotlinClassMetadata.readStrict(m))); + Metadata written = clazz1.write(); assertArrayEquals(written.mv(), KotlinClassMetadata.COMPATIBLE_METADATA_VERSION); - assertEquals(0, written.xi()); + assertEquals(50, 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 29139457424..99b6eb816ea 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataExceptionsTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataExceptionsTest.kt @@ -29,7 +29,7 @@ class MetadataExceptionsTest { fun testWriteMalformedClass() { val kmClass = KmClass() // kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized val e = assertFailsWith { - KotlinClassMetadata.writeClass(kmClass) + KotlinClassMetadata.Class(kmClass, KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, 0).write() } assertIs(e.cause) } diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt index 09a95cc07c9..769dc090f23 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt @@ -63,7 +63,7 @@ class MetadataSmokeTest { } } - val annotationData = KotlinClassMetadata.writeClass(klass) + val annotationData = KotlinClassMetadata.Class(klass, KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, 0).write() // Then, produce the bytecode of a .class file with ASM @@ -153,18 +153,18 @@ class MetadataSmokeTest { fun foo(a: String, b: Int, c: Boolean) = Unit } - val classWithStableParameterNames = Test::class.java.readMetadataAsKmClass() + val classWithStableParameterNames = Test::class.java.readMetadataAsClass() - classWithStableParameterNames.constructors.forEach { assertFalse(Flag.Constructor.HAS_NON_STABLE_PARAMETER_NAMES(it.flags)) } - classWithStableParameterNames.functions.forEach { assertFalse(Flag.Function.HAS_NON_STABLE_PARAMETER_NAMES(it.flags)) } + classWithStableParameterNames.kmClass.constructors.forEach { assertFalse(Flag.Constructor.HAS_NON_STABLE_PARAMETER_NAMES(it.flags)) } + classWithStableParameterNames.kmClass.functions.forEach { assertFalse(Flag.Function.HAS_NON_STABLE_PARAMETER_NAMES(it.flags)) } - classWithStableParameterNames.constructors.forEach { assertFalse(it.hasNonStableParameterNames) } - classWithStableParameterNames.functions.forEach { assertFalse(it.hasNonStableParameterNames) } + classWithStableParameterNames.kmClass.constructors.forEach { assertFalse(it.hasNonStableParameterNames) } + classWithStableParameterNames.kmClass.functions.forEach { assertFalse(it.hasNonStableParameterNames) } - classWithStableParameterNames.constructors.forEach { it.hasNonStableParameterNames = true } - classWithStableParameterNames.functions.forEach { it.hasNonStableParameterNames = true } + classWithStableParameterNames.kmClass.constructors.forEach { it.hasNonStableParameterNames = true } + classWithStableParameterNames.kmClass.functions.forEach { it.hasNonStableParameterNames = true } - val newMetadata = KotlinClassMetadata.writeClass(classWithStableParameterNames) + val newMetadata = classWithStableParameterNames.write() val classWithUnstableParameterNames = newMetadata.readAsKmClass() @@ -180,11 +180,11 @@ class MetadataSmokeTest { fun metadataVersionEarlierThan1_4() { val dummy = MetadataSmokeTest::class.java.readMetadataAsKmClass() val mv = intArrayOf(1, 3) - assertFailsWith { KotlinClassMetadata.writeClass(dummy, mv) } // We can't write empty KmClass() - assertFailsWith { KotlinClassMetadata.writeFileFacade(KmPackage(), mv) } - assertFailsWith { KotlinClassMetadata.writeMultiFileClassFacade(listOf("A"), mv) } - assertFailsWith { KotlinClassMetadata.writeMultiFileClassPart(KmPackage(), "A", mv) } - assertFailsWith { KotlinClassMetadata.writeSyntheticClass(mv) } + assertFailsWith { KotlinClassMetadata.Class(dummy, mv, 0).write() } // We can't write empty KmClass() + assertFailsWith { KotlinClassMetadata.FileFacade(KmPackage(), mv, 0).write() } + assertFailsWith { KotlinClassMetadata.MultiFileClassFacade(listOf("A"), mv, 0).write() } + assertFailsWith { KotlinClassMetadata.MultiFileClassPart(KmPackage(), "A", mv, 0).write() } + assertFailsWith { KotlinClassMetadata.SyntheticClass(null, mv, 0).write() } KotlinModuleMetadata.write(KmModule(), mv) } @@ -196,16 +196,14 @@ class MetadataSmokeTest { // flags set. Since the current flags only apply to interfaces with default functions we modify // the metadata for the kotlin.coroutines.CoroutineContext interface. - val metadata = CoroutineContext::class.java.getMetadata() - val kmClass = metadata.readAsKmClass() + val metadata = CoroutineContext::class.java.readMetadataAsClass() + val kmClass = metadata.kmClass assertFalse(kmClass.isCompiledInCompatibilityMode) assertFalse(kmClass.hasMethodBodiesInInterface) kmClass.isCompiledInCompatibilityMode = true kmClass.hasMethodBodiesInInterface = true - val kmClassCopy = KotlinClassMetadata - .writeClass(kmClass, metadata.metadataVersion, metadata.extraInt) - .readAsKmClass() + val kmClassCopy = metadata.write().readAsKmClass() assertTrue(kmClassCopy.isCompiledInCompatibilityMode) assertTrue(kmClassCopy.hasMethodBodiesInInterface) } 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 6e54f150790..d304c71e969 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/Utils.kt @@ -18,3 +18,4 @@ internal fun Metadata.readAsKmClass(): KmClass { } internal fun Class<*>.readMetadataAsKmClass(): KmClass = getMetadata().readAsKmClass() +internal fun Class<*>.readMetadataAsClass(): KotlinClassMetadata.Class = KotlinClassMetadata.readStrict(getMetadata()) as KotlinClassMetadata.Class diff --git a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt index 6065aebc145..1c8010aef17 100644 --- a/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt +++ b/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/WritersContractTest.kt @@ -17,15 +17,15 @@ class WritersContractTest { // val fileFacadeMd = TODO @Test - @Ignore // TODO fun lenientDataCantBeWritten() { - val lenientClass = KotlinClassMetadata.readLenient(classMd) as KotlinClassMetadata.Class - assertFailsWith { KotlinClassMetadata.writeClass(lenientClass.kmClass) } + val lenientClass = KotlinClassMetadata.readLenient(classMd) + assertFailsWith { lenientClass.write() } } @Test fun oldVersionCantBeWritten() { val writeableClass = KotlinClassMetadata.readStrict(classMd) as KotlinClassMetadata.Class - assertFailsWith { KotlinClassMetadata.writeClass(writeableClass.kmClass, intArrayOf(1, 2, 0)) } + writeableClass.version = intArrayOf(1, 2, 0) + assertFailsWith { writeableClass.write() } } } 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 fd513d93c54..d5e1511b6df 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 @@ -81,7 +81,7 @@ private fun compileAndPrintAllFiles( "class" -> { val metadata = kotlinp.readClassFile(outputFile) val classFile = KotlinClassMetadata.readStrict(metadata) - val classFile2 = KotlinClassMetadata.readStrict(transformClassFileWithNodes(metadata, classFile)) + val classFile2 = KotlinClassMetadata.readStrict(classFile.write()) for ((sb, classFileToRender) in listOf( main to classFile, afterNodes to classFile2 @@ -129,21 +129,6 @@ private fun StringBuilder.appendFileName(file: File) { appendLine("// ------------------------------------------") } -// Reads the class file and writes it back with KmClass/KmFunction/... elements. -private fun transformClassFileWithNodes(metadata: Metadata, classFile: KotlinClassMetadata): Metadata = - when (classFile) { - is KotlinClassMetadata.Class -> - KotlinClassMetadata.writeClass(classFile.kmClass) - is KotlinClassMetadata.FileFacade -> - KotlinClassMetadata.writeFileFacade(classFile.kmPackage) - is KotlinClassMetadata.SyntheticClass -> - classFile.kmLambda?.let { KotlinClassMetadata.writeLambda(it) } ?: KotlinClassMetadata.writeSyntheticClass() - is KotlinClassMetadata.MultiFileClassPart -> - KotlinClassMetadata.writeMultiFileClassPart(classFile.kmPackage, classFile.facadeClassName) - is KotlinClassMetadata.MultiFileClassFacade -> KotlinClassMetadata.writeMultiFileClassFacade(classFile.partClassNames) - is KotlinClassMetadata.Unknown -> metadata - } - @OptIn(UnstableMetadataApi::class) private fun transformModuleFileWithNodes(moduleFile: KotlinModuleMetadata): ByteArray = KotlinModuleMetadata.write(moduleFile.kmModule) 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 b8817d85141..bb20a19744a 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,28 +28,19 @@ fun abiMetadataProcessor(annotationVisitor: AnnotationVisitor): AnnotationVisito } ?: intArrayOf(1, 4) val newHeader = runCatching { - when (val metadata = KotlinClassMetadata.readStrict(header)) { - is KotlinClassMetadata.Class -> { - val klass = metadata.kmClass - klass.removePrivateDeclarations() - KotlinClassMetadata.writeClass(klass, metadataVersion, header.extraInt) + KotlinClassMetadata.transform(header) { metadata -> + when (metadata) { + is KotlinClassMetadata.Class -> { + metadata.kmClass.removePrivateDeclarations() + } + is KotlinClassMetadata.FileFacade -> { + metadata.kmPackage.removePrivateDeclarations() + } + is KotlinClassMetadata.MultiFileClassPart -> { + metadata.kmPackage.removePrivateDeclarations() + } + else -> Unit } - is KotlinClassMetadata.FileFacade -> { - val pkg = metadata.kmPackage - pkg.removePrivateDeclarations() - KotlinClassMetadata.writeFileFacade(pkg, metadataVersion, header.extraInt) - } - is KotlinClassMetadata.MultiFileClassPart -> { - val pkg = metadata.kmPackage - pkg.removePrivateDeclarations() - KotlinClassMetadata.writeMultiFileClassPart( - pkg, - metadata.facadeClassName, - metadataVersion, - header.extraInt - ) - } - else -> header } }.getOrElse { cause -> // TODO: maybe jvm-abi-gen should throw this exception by default, and not only in tests.