Provide a separate class for representing metadata version in kotlinx-metadata

Separate class allows encapsulating comparison, equality, and structure of version parts.
It also makes more sense from API standpoint than plain `IntArray`.
This commit is contained in:
Leonid Startsev
2023-11-08 12:56:05 +01:00
committed by Space Team
parent f101e5a336
commit 721af75f40
16 changed files with 323 additions and 89 deletions
@@ -1310,6 +1310,24 @@ public final class kotlinx/metadata/jvm/JvmMetadataUtil {
public static final fun toJvmInternalName (Ljava/lang/String;)Ljava/lang/String;
}
public final class kotlinx/metadata/jvm/JvmMetadataVersion : java/lang/Comparable {
public static final field CURRENT Lkotlinx/metadata/jvm/JvmMetadataVersion;
public static final field Companion Lkotlinx/metadata/jvm/JvmMetadataVersion$Companion;
public fun <init> (II)V
public fun <init> (III)V
public synthetic fun compareTo (Ljava/lang/Object;)I
public fun compareTo (Lkotlinx/metadata/jvm/JvmMetadataVersion;)I
public fun equals (Ljava/lang/Object;)Z
public final fun getMajor ()I
public final fun getMinor ()I
public final fun getPatch ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class kotlinx/metadata/jvm/JvmMetadataVersion$Companion {
}
public final class kotlinx/metadata/jvm/JvmMethodSignature : kotlinx/metadata/jvm/JvmMemberSignature {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
@@ -1441,12 +1459,12 @@ public abstract class kotlinx/metadata/jvm/KotlinClassMetadata {
public static final field MULTI_FILE_CLASS_PART_KIND I
public static final field SYNTHETIC_CLASS_KIND I
public abstract fun getFlags ()I
public abstract fun getVersion ()[I
public abstract fun getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
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 setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)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;
@@ -1469,14 +1487,14 @@ public abstract class kotlinx/metadata/jvm/KotlinClassMetadata {
}
public final class kotlinx/metadata/jvm/KotlinClassMetadata$Class : kotlinx/metadata/jvm/KotlinClassMetadata {
public fun <init> (Lkotlinx/metadata/KmClass;[II)V
public fun <init> (Lkotlinx/metadata/KmClass;Lkotlinx/metadata/jvm/JvmMetadataVersion;I)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 getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
public fun setFlags (I)V
public final fun setKmClass (Lkotlinx/metadata/KmClass;)V
public fun setVersion ([I)V
public fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public final fun toKmClass ()Lkotlinx/metadata/KmClass;
public fun write ()Lkotlin/Metadata;
}
@@ -1521,14 +1539,14 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$Companion {
}
public final class kotlinx/metadata/jvm/KotlinClassMetadata$FileFacade : kotlinx/metadata/jvm/KotlinClassMetadata {
public fun <init> (Lkotlinx/metadata/KmPackage;[II)V
public fun <init> (Lkotlinx/metadata/KmPackage;Lkotlinx/metadata/jvm/JvmMetadataVersion;I)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 getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
public fun setFlags (I)V
public final fun setKmPackage (Lkotlinx/metadata/KmPackage;)V
public fun setVersion ([I)V
public fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public final fun toKmPackage ()Lkotlinx/metadata/KmPackage;
public fun write ()Lkotlin/Metadata;
}
@@ -1542,13 +1560,13 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$FileFacade$Writer :
}
public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassFacade : kotlinx/metadata/jvm/KotlinClassMetadata {
public fun <init> (Ljava/util/List;[II)V
public fun <init> (Ljava/util/List;Lkotlinx/metadata/jvm/JvmMetadataVersion;I)V
public fun getFlags ()I
public final fun getPartClassNames ()Ljava/util/List;
public fun getVersion ()[I
public fun getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
public fun setFlags (I)V
public final fun setPartClassNames (Ljava/util/List;)V
public fun setVersion ([I)V
public fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public fun write ()Lkotlin/Metadata;
}
@@ -1561,16 +1579,16 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassFacade
}
public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassPart : kotlinx/metadata/jvm/KotlinClassMetadata {
public fun <init> (Lkotlinx/metadata/KmPackage;Ljava/lang/String;[II)V
public fun <init> (Lkotlinx/metadata/KmPackage;Ljava/lang/String;Lkotlinx/metadata/jvm/JvmMetadataVersion;I)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 fun getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
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 fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public final fun toKmPackage ()Lkotlinx/metadata/KmPackage;
public fun write ()Lkotlin/Metadata;
}
@@ -1584,15 +1602,15 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$MultiFileClassPart$W
}
public final class kotlinx/metadata/jvm/KotlinClassMetadata$SyntheticClass : kotlinx/metadata/jvm/KotlinClassMetadata {
public fun <init> (Lkotlinx/metadata/KmLambda;[II)V
public fun <init> (Lkotlinx/metadata/KmLambda;Lkotlinx/metadata/jvm/JvmMetadataVersion;I)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 fun getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
public final fun isLambda ()Z
public fun setFlags (I)V
public final fun setKmLambda (Lkotlinx/metadata/KmLambda;)V
public fun setVersion ([I)V
public fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public final fun toKmLambda ()Lkotlinx/metadata/KmLambda;
public fun write ()Lkotlin/Metadata;
}
@@ -1609,9 +1627,9 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$Unknown : kotlinx/me
public static final field Companion Lkotlinx/metadata/jvm/KotlinClassMetadata$Unknown$Companion;
public static final field INSTANCE Lkotlinx/metadata/jvm/KotlinClassMetadata$Unknown;
public fun getFlags ()I
public fun getVersion ()[I
public fun getVersion ()Lkotlinx/metadata/jvm/JvmMetadataVersion;
public fun setFlags (I)V
public fun setVersion ([I)V
public fun setVersion (Lkotlinx/metadata/jvm/JvmMetadataVersion;)V
public fun write ()Lkotlin/Metadata;
}
@@ -1626,14 +1644,14 @@ public final class kotlinx/metadata/jvm/KotlinModuleMetadata {
public static final fun read ([B)Lkotlinx/metadata/jvm/KotlinModuleMetadata;
public final fun toKmModule ()Lkotlinx/metadata/jvm/KmModule;
public static final fun write (Lkotlinx/metadata/jvm/KmModule;)[B
public static final fun write (Lkotlinx/metadata/jvm/KmModule;[I)[B
public static final fun write (Lkotlinx/metadata/jvm/KmModule;Lkotlinx/metadata/jvm/JvmMetadataVersion;)[B
}
public final class kotlinx/metadata/jvm/KotlinModuleMetadata$Companion {
public final fun read ([B)Lkotlinx/metadata/jvm/KotlinModuleMetadata;
public final fun write (Lkotlinx/metadata/jvm/KmModule;)[B
public final fun write (Lkotlinx/metadata/jvm/KmModule;[I)[B
public static synthetic fun write$default (Lkotlinx/metadata/jvm/KotlinModuleMetadata$Companion;Lkotlinx/metadata/jvm/KmModule;[IILjava/lang/Object;)[B
public final fun write (Lkotlinx/metadata/jvm/KmModule;Lkotlinx/metadata/jvm/JvmMetadataVersion;)[B
public static synthetic fun write$default (Lkotlinx/metadata/jvm/KotlinModuleMetadata$Companion;Lkotlinx/metadata/jvm/KmModule;Lkotlinx/metadata/jvm/JvmMetadataVersion;ILjava/lang/Object;)[B
}
public final class kotlinx/metadata/jvm/KotlinModuleMetadata$Writer {
@@ -0,0 +1,116 @@
/*
* 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
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion
/**
* Version of the metadata inside JVM classfile.
*
* Starting from Kotlin 1.4, the metadata version is equal to the language version.
* It consists of major and minor versions. Patch version usually does not affect the metadata format, but it is present for completeness because
* compiler writes its full version to the `@Metadata` annotation; therefore, the patch version is accounted for in comparisons and equality.
* All components (major, minor, and patch) should be non-negative.
*
* While the metadata version is currently equal to the language version, this may be changed in the future, including adding or removing components.
* Therefore, it is recommended to rely on the [compareTo] and [equals] method instead of accessing components directly.
*
* Note that the metadata version is 1.1 for Kotlin compilers from 1.0 until 1.4, and is 1.0 or less for pre-1.0 compilers.
* Metadata with versions less than 1.1 is considered incompatible and cannot be read or written.
*
* The library can read in strict mode only compatible versions of metadata. For definition of compatible version, see documentation for [CURRENT] property.
*
* @property major Major component of version
* @property minor Minor component of version
* @property patch Patch component of version
*/
public class JvmMetadataVersion(public val major: Int, public val minor: Int, public val patch: Int) : Comparable<JvmMetadataVersion> {
/**
* Creates a new [JvmMetadataVersion] instance with specified [major] and [minor] versions and a patch version equal to 0.
*/
public constructor(major: Int, minor: Int) : this(major, minor, 0)
internal constructor(intArray: IntArray) : this(intArray[0], intArray[1], intArray[2])
@JvmName("toIntArray")
internal fun toIntArray(): IntArray = intArrayOf(major, minor, patch)
init {
require(major >= 0) { "Major version should be not less than 0" }
require(minor >= 0) { "Minor version should be not less than 0" }
require(patch >= 0) { "Patch version should be not less than 0" }
}
/**
* Compares this JvmMetadataVersion object with another JvmMetadataVersion object.
*
* Comparison is based on integer values of version parts with [major] being most significant one, then [minor], and finally [patch].
*
* @return a negative integer, zero, or a positive integer if this JvmMetadataVersion object is less than, equal to, or greater than [other].
*/
override fun compareTo(other: JvmMetadataVersion): Int {
val majors = major.compareTo(other.major)
if (majors != 0) return majors
val minors = minor.compareTo(other.minor)
return if (minors != 0) minors else patch.compareTo(other.patch)
}
/**
* Returns a string representation of the version number.
* The string representation is in the format "major.minor.patch".
*/
override fun toString(): String = "$major.$minor.$patch"
/**
* Calculates the hash code value for the object based on major, minor, and patch components.
*/
override fun hashCode(): Int {
var result = major
result = 31 * result + minor
result = 31 * result + patch
return result
}
/**
* Checks if the current JvmMetadataVersion object is equal to another object.
*
* Instances of JvmMetadataVersion are equal if they have the same major, minor, and patch components.
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as JvmMetadataVersion
if (major != other.major) return false
if (minor != other.minor) return false
if (patch != other.patch) return false
return true
}
/**
* Companion object to hold pre-defined JvmMetadataVersion instances.
*/
public companion object {
/**
* The latest stable metadata version supported by this version of the library.
* 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, the metadata version is supported if it is greater or equal than 1.1, and less or equal than [CURRENT] + 1 minor version.
* Note that the metadata version is 1.1 for Kotlin from 1.0 until 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 in strict mode binaries produced by Kotlin compilers from 1.0
* to 1.8.* inclusively. In this case, this property will have the value `1.7.0`.
*
* @see Metadata.metadataVersion
* @see KotlinClassMetadata.readStrict
*/
@JvmField
public val CURRENT: JvmMetadataVersion = JvmMetadataVersion(CompilerMetadataVersion.INSTANCE.toArray())
}
}
@@ -178,7 +178,7 @@ public final class KotlinClassHeader implements Metadata {
public static final int MULTI_FILE_CLASS_PART_KIND = 5;
/**
* Use {@link KotlinClassMetadata#COMPATIBLE_METADATA_VERSION} instead
* Use {@link kotlinx.metadata.jvm.JvmMetadataVersion#CURRENT} instead
*/
@Deprecated
public static final int[] COMPATIBLE_METADATA_VERSION = Arrays.copyOf(JvmMetadataVersion.INSTANCE.toArray(), 3);
@@ -4,6 +4,7 @@
*/
@file:Suppress(
"DEPRECATION_ERROR", // Deprecated .accept implementation
"DEPRECATION", // COMPATIBLE_METADATA_VERSION as default param
"UNUSED_PARAMETER" // For deprecated Writer.write
)
@@ -12,9 +13,8 @@ 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.JvmMetadataVersion as CompilerMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
import java.util.*
@@ -61,16 +61,16 @@ 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]`
* Due to technical limitations, it is not possible to write the metadata when the specified version is less than 1.4.
*
* @throws IllegalArgumentException if metadata is malformed or [version] of this instance is less than 1.4 and cannot be written.
* @throws IllegalArgumentException if metadata is malformed or metadata was read in lenient mode and cannot be written back or [version] of this instance is less than 1.4.
*/
public abstract fun write(): Metadata
/**
* Version of this metadata.
*/
public abstract var version: IntArray
public abstract var version: JvmMetadataVersion
/**
* Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values.
@@ -92,13 +92,13 @@ public sealed class KotlinClassMetadata {
* Returns the same (mutable) [KmClass] instance every time.
*/
public var kmClass: KmClass,
public override var version: IntArray,
public override var version: JvmMetadataVersion,
public override var flags: Int,
) : KotlinClassMetadata() {
internal constructor(annotationData: Metadata, lenient: Boolean) : this(
JvmReadUtils.readKmClass(annotationData),
annotationData.metadataVersion,
JvmMetadataVersion(annotationData.metadataVersion),
annotationData.extraInt
) {
isAllowedToWrite = !lenient
@@ -111,7 +111,7 @@ public sealed class KotlinClassMetadata {
val writer = ClassWriter(JvmStringTable())
writer.writeClass(kmClass)
val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c)
Metadata(CLASS_KIND, version, d1, d2, extraInt = flags)
Metadata(CLASS_KIND, version.toIntArray(), d1, d2, extraInt = flags)
}
}
@@ -181,7 +181,7 @@ public sealed class KotlinClassMetadata {
/**
* Version of this metadata.
*/
public override var version: IntArray,
public override var version: JvmMetadataVersion,
/**
* Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values.
*/
@@ -190,7 +190,7 @@ public sealed class KotlinClassMetadata {
internal constructor(annotationData: Metadata, lenient: Boolean) : this(
JvmReadUtils.readKmPackage(annotationData),
annotationData.metadataVersion,
JvmMetadataVersion(annotationData.metadataVersion),
annotationData.extraInt
) {
isAllowedToWrite = !lenient
@@ -203,7 +203,7 @@ public sealed class KotlinClassMetadata {
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)
Metadata(FILE_FACADE_KIND, version.toIntArray(), d1, d2, extraInt = flags)
}
}
@@ -269,7 +269,7 @@ public sealed class KotlinClassMetadata {
/**
* Version of this metadata.
*/
public override var version: IntArray,
public override var version: JvmMetadataVersion,
/**
* Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values.
*/
@@ -278,7 +278,7 @@ public sealed class KotlinClassMetadata {
internal constructor(annotationData: Metadata, lenient: Boolean) : this(
JvmReadUtils.readKmLambda(annotationData),
annotationData.metadataVersion,
JvmMetadataVersion(annotationData.metadataVersion),
annotationData.extraInt
) {
isAllowedToWrite = !lenient
@@ -294,9 +294,9 @@ public sealed class KotlinClassMetadata {
val (d1, d2) =
if (proto != null) writeProtoBufData(proto, writer.c)
else Pair(emptyArray<String>(), emptyArray<String>())
Metadata(SYNTHETIC_CLASS_KIND, version, d1, d2, extraInt = flags)
Metadata(SYNTHETIC_CLASS_KIND, version.toIntArray(), d1, d2, extraInt = flags)
} else {
Metadata(SYNTHETIC_CLASS_KIND, version, emptyArray<String>(), emptyArray<String>(), extraInt = flags)
Metadata(SYNTHETIC_CLASS_KIND, version.toIntArray(), emptyArray<String>(), emptyArray<String>(), extraInt = flags)
}
}
@@ -407,7 +407,7 @@ public sealed class KotlinClassMetadata {
/**
* Version of this metadata.
*/
public override var version: IntArray,
public override var version: JvmMetadataVersion,
/**
* Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values.
*/
@@ -416,7 +416,7 @@ public sealed class KotlinClassMetadata {
internal constructor(annotationData: Metadata, lenient: Boolean) : this(
annotationData.data1.asList(),
annotationData.metadataVersion,
JvmMetadataVersion(annotationData.metadataVersion),
annotationData.extraInt
) {
isAllowedToWrite = !lenient
@@ -426,7 +426,7 @@ public sealed class KotlinClassMetadata {
throwIfNotWriteable(isAllowedToWrite, "multi-file class facade")
checkMetadataVersion(version)
return Metadata(
MULTI_FILE_CLASS_FACADE_KIND, version,
MULTI_FILE_CLASS_FACADE_KIND, version.toIntArray(),
partClassNames.toTypedArray<String>(), extraInt = flags
)
}
@@ -488,7 +488,7 @@ public sealed class KotlinClassMetadata {
/**
* Version of this metadata.
*/
public override var version: IntArray,
public override var version: JvmMetadataVersion,
/**
* Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values.
*/
@@ -498,7 +498,7 @@ public sealed class KotlinClassMetadata {
internal constructor(annotationData: Metadata, lenient: Boolean) : this(
JvmReadUtils.readKmPackage(annotationData),
annotationData.extraString,
annotationData.metadataVersion,
JvmMetadataVersion(annotationData.metadataVersion),
annotationData.extraInt
) {
isAllowedToWrite = !lenient
@@ -512,7 +512,7 @@ public sealed class KotlinClassMetadata {
writer.writePackage(kmPackage)
val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c)
Metadata(
MULTI_FILE_CLASS_PART_KIND, version, d1, d2, facadeClassName, extraInt = flags
MULTI_FILE_CLASS_PART_KIND, version.toIntArray(), d1, d2, facadeClassName, extraInt = flags
)
}
}
@@ -577,7 +577,7 @@ public sealed class KotlinClassMetadata {
/**
* Version of this metadata.
*/
public override var version: IntArray = original.metadataVersion
public override var version: JvmMetadataVersion = JvmMetadataVersion(original.metadataVersion)
/**
* Additional classfile-level flags of this metadata. See [Metadata.extraInt] for possible values.
@@ -586,14 +586,23 @@ public sealed class KotlinClassMetadata {
override fun write(): Metadata {
throwIfNotWriteable(!lenient, "unknown kind")
return Metadata(original.kind, version, original.data1, original.data2, original.extraString, original.packageName, flags)
checkMetadataVersion(version)
return Metadata(
original.kind,
version.toIntArray(),
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)
public val INSTANCE: Unknown = Unknown(Metadata(kind = 99, metadataVersion = JvmMetadataVersion.CURRENT.toIntArray()), true)
}
}
@@ -635,7 +644,7 @@ public sealed class KotlinClassMetadata {
kmClass: KmClass,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0,
): Metadata = Class(kmClass, metadataVersion, extraInt).write()
): Metadata = Class(kmClass, JvmMetadataVersion(metadataVersion), extraInt).write()
/**
@@ -655,7 +664,7 @@ public sealed class KotlinClassMetadata {
kmPackage: KmPackage,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0,
): Metadata = FileFacade(kmPackage, metadataVersion, extraInt).write()
): Metadata = FileFacade(kmPackage, JvmMetadataVersion(metadataVersion), extraInt).write()
/**
* Writes [kmLambda] as the synthetic class metadata.
@@ -674,7 +683,7 @@ public sealed class KotlinClassMetadata {
kmLambda: KmLambda,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0,
): Metadata = SyntheticClass(kmLambda, metadataVersion, extraInt).write()
): Metadata = SyntheticClass(kmLambda, JvmMetadataVersion(metadataVersion), extraInt).write()
/**
* Writes synthetic class metadata.
@@ -692,7 +701,7 @@ public sealed class KotlinClassMetadata {
public fun writeSyntheticClass(
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0,
): Metadata = SyntheticClass(null, metadataVersion, extraInt).write()
): Metadata = SyntheticClass(null, JvmMetadataVersion(metadataVersion), extraInt).write()
/**
* Writes metadata of the multi-file class facade.
@@ -714,7 +723,7 @@ public sealed class KotlinClassMetadata {
public fun writeMultiFileClassFacade(
partClassNames: List<String>, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0,
): Metadata = MultiFileClassFacade(partClassNames, metadataVersion, extraInt).write()
): Metadata = MultiFileClassFacade(partClassNames, JvmMetadataVersion(metadataVersion), extraInt).write()
/**
* Writes the metadata of the multi-file class part.
@@ -738,7 +747,7 @@ public sealed class KotlinClassMetadata {
facadeClassName: String,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0,
): Metadata = MultiFileClassPart(kmPackage, facadeClassName, metadataVersion, extraInt).write()
): Metadata = MultiFileClassPart(kmPackage, facadeClassName, JvmMetadataVersion(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
@@ -771,14 +780,14 @@ public sealed class KotlinClassMetadata {
* [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).
* This method can read only supported metadata versions (see [JvmMetadataVersion.CURRENT] 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
* @throws IllegalArgumentException if the metadata version is unsupported or if metadata is corrupted
*
* @see COMPATIBLE_METADATA_VERSION
* @see JvmMetadataVersion.CURRENT
*/
@JvmStatic
public fun readStrict(annotationData: Metadata): KotlinClassMetadata = readMetadataImpl(annotationData, lenient = false)
@@ -791,13 +800,13 @@ public sealed class KotlinClassMetadata {
* 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.
* If metadata version is greater than [JvmMetadataVersion.CURRENT] + 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
* @see JvmMetadataVersion.CURRENT
*/
@JvmStatic
public fun readLenient(annotationData: Metadata): KotlinClassMetadata = readMetadataImpl(annotationData, lenient = true)
@@ -807,6 +816,13 @@ public sealed class KotlinClassMetadata {
throw IllegalArgumentException("This $name cannot be written because it represents metadata read in lenient mode")
}
internal fun checkMetadataVersion(version: JvmMetadataVersion) {
require(version.major >= 1 && (version.major > 1 || version.minor >= 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 to at least [1, 4]."
}
}
/**
* A class file kind signifying that the corresponding class file contains a declaration of a Kotlin class.
*
@@ -860,8 +876,10 @@ public sealed class KotlinClassMetadata {
*
* @see Metadata.metadataVersion
*/
@JvmField // TODO: move it somewhere since it is also used in KotlinModuleMetadata?
public val COMPATIBLE_METADATA_VERSION: IntArray = JvmMetadataVersion.INSTANCE.toArray().copyOf()
@JvmField
@Deprecated("Use JvmMetadataVersion.CURRENT instead", ReplaceWith("JvmMetadataVersion.CURRENT"), DeprecationLevel.WARNING)
public val COMPATIBLE_METADATA_VERSION: IntArray = CompilerMetadataVersion.INSTANCE.toArray().copyOf()
}
}
@@ -21,7 +21,7 @@ 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
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.PackageParts
import org.jetbrains.kotlin.metadata.jvm.deserialization.serializeToByteArray
@@ -181,9 +181,9 @@ public class KotlinModuleMetadata private constructor(
@UnstableMetadataApi
@JvmStatic
@JvmOverloads
public fun write(kmModule: KmModule, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): ByteArray = wrapWriteIntoIAE {
public fun write(kmModule: KmModule, metadataVersion: JvmMetadataVersion = JvmMetadataVersion.CURRENT): ByteArray = wrapWriteIntoIAE {
val w = Writer().also { it.writeModule(kmModule) }
return w.b.build().serializeToByteArray(JvmMetadataVersion(*metadataVersion), 0)
return w.b.build().serializeToByteArray(CompilerMetadataVersion(metadataVersion.toIntArray(), false), 0)
}
private fun dataFromBytes(bytes: ByteArray): ModuleMapping {
@@ -71,11 +71,4 @@ internal object JvmReadUtils {
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]."
}
}
}
@@ -29,7 +29,7 @@ class DifferentVersionsTest {
@Test
fun readsCurrentVersion() {
assertContentEquals(KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, metadata.metadataVersion)
assertContentEquals(JvmMetadataVersion.INSTANCE.toArray(), metadata.metadataVersion)
assertIs<KotlinClassMetadata.Class>(KotlinClassMetadata.readStrict(metadata))
assertIs<KotlinClassMetadata.Class>(KotlinClassMetadata.readLenient(metadata))
}
@@ -51,7 +51,7 @@ class DifferentVersionsTest {
@Test
fun readsArbitraryFutureVersion() {
val md = metadata.changeVersion(intArrayOf(2, 5, 0))
val md = metadata.changeVersion(intArrayOf(7, 5, 0))
assertFailsWith<IllegalArgumentException> { KotlinClassMetadata.readStrict(md) }
assertIs<KotlinClassMetadata.Class>(KotlinClassMetadata.readLenient(md))
}
@@ -7,6 +7,7 @@ package kotlinx.metadata.test;
import kotlin.Metadata;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.jvm.JvmMetadataVersion;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.Test;
@@ -32,7 +33,8 @@ public class JavaUsageTest {
Metadata m = MetadataSmokeTest.class.getAnnotation(Metadata.class);
KotlinClassMetadata clazz1 = ((KotlinClassMetadata) Objects.requireNonNull(KotlinClassMetadata.readStrict(m)));
Metadata written = clazz1.write();
assertArrayEquals(written.mv(), KotlinClassMetadata.COMPATIBLE_METADATA_VERSION);
//noinspection KotlinInternalInJava
assertArrayEquals(written.mv(), JvmMetadataVersion.CURRENT.toIntArray());
assertEquals(50, written.xi());
}
}
@@ -0,0 +1,36 @@
/*
* 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.JvmMetadataVersion
import kotlin.test.*
class JvmMetadataVersionTest {
fun mv(major: Int, minor: Int, patch: Int) = JvmMetadataVersion(major, minor, patch)
@Test
fun testEquality() {
assertEquals(mv(1, 2, 3), mv(1, 2, 3))
assertNotEquals(mv(0, 2, 3), mv(1, 2, 3))
assertNotEquals(mv(1, 0, 3), mv(1, 2, 3))
assertNotEquals(mv(1, 2, 3), mv(1, 2, 0))
}
@Test
fun testComparison() {
assertTrue(mv(1, 2, 3) > mv(1, 2, 0))
assertTrue(mv(1, 0, 3) < mv(1, 2, 0))
assertTrue(mv(1, 2, 3) < mv(2, 0, 0))
}
@Test
fun testConstructor() {
assertFailsWith<IllegalArgumentException> { mv(-1, 0, 0) }
assertFailsWith<IllegalArgumentException> { mv(0, -1, 0) }
assertFailsWith<IllegalArgumentException> { mv(0, 0, -1) }
}
}
@@ -6,9 +6,10 @@
package kotlinx.metadata.test
import kotlinx.metadata.KmClass
import kotlinx.metadata.jvm.JvmMetadataVersion
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.Metadata
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion
import org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException
import org.junit.Test
import kotlin.test.*
@@ -18,7 +19,7 @@ class MetadataExceptionsTest {
fun testReadMalformedInput() {
val malformedInput = "abcdefdkdgwaydgyuawdfg543awyudfuiawty" // random string guaranteed by dropping ball on a keyboard
val malformedMetadata =
Metadata(KotlinClassMetadata.CLASS_KIND, KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, arrayOf(malformedInput))
Metadata(KotlinClassMetadata.CLASS_KIND, CompilerMetadataVersion.INSTANCE.toArray(), arrayOf(malformedInput))
val e = assertFailsWith<IllegalArgumentException> {
(KotlinClassMetadata.readStrict(malformedMetadata) as KotlinClassMetadata.Class)
}
@@ -29,7 +30,7 @@ class MetadataExceptionsTest {
fun testWriteMalformedClass() {
val kmClass = KmClass() // kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized
val e = assertFailsWith<IllegalArgumentException> {
KotlinClassMetadata.Class(kmClass, KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, 0).write()
KotlinClassMetadata.Class(kmClass, JvmMetadataVersion.CURRENT, 0).write()
}
assertIs<UninitializedPropertyAccessException>(e.cause)
}
@@ -49,10 +50,10 @@ class MetadataExceptionsTest {
@Test
fun testReadNewerVersion() {
val versionPlus2 = JvmMetadataVersion.INSTANCE.next().next()
val versionPlus2 = CompilerMetadataVersion.INSTANCE.next().next()
doTestVersion(
versionPlus2.toArray(),
"version $versionPlus2, while maximum supported version is ${JvmMetadataVersion.INSTANCE_NEXT}"
"version $versionPlus2, while maximum supported version is ${CompilerMetadataVersion.INSTANCE_NEXT}"
)
}
@@ -60,7 +61,7 @@ class MetadataExceptionsTest {
fun testInvalidVersion() {
doTestVersion(intArrayOf(), "instance does not have metadataVersion in it and therefore is malformed and cannot be read")
doTestVersion(
JvmMetadataVersion.INVALID_VERSION.toArray(),
CompilerMetadataVersion.INVALID_VERSION.toArray(),
"instance does not have metadataVersion in it and therefore is malformed and cannot be read"
)
}
@@ -63,7 +63,7 @@ class MetadataSmokeTest {
}
}
val annotationData = KotlinClassMetadata.Class(klass, KotlinClassMetadata.COMPATIBLE_METADATA_VERSION, 0).write()
val annotationData = KotlinClassMetadata.Class(klass, JvmMetadataVersion.CURRENT, 0).write()
// Then, produce the bytecode of a .class file with ASM
@@ -179,7 +179,7 @@ class MetadataSmokeTest {
@OptIn(UnstableMetadataApi::class)
fun metadataVersionEarlierThan1_4() {
val dummy = MetadataSmokeTest::class.java.readMetadataAsKmClass()
val mv = intArrayOf(1, 3)
val mv = JvmMetadataVersion(1, 3)
assertFailsWith<IllegalArgumentException> { KotlinClassMetadata.Class(dummy, mv, 0).write() } // We can't write empty KmClass()
assertFailsWith<IllegalArgumentException> { KotlinClassMetadata.FileFacade(KmPackage(), mv, 0).write() }
assertFailsWith<IllegalArgumentException> { KotlinClassMetadata.MultiFileClassFacade(listOf("A"), mv, 0).write() }
@@ -5,27 +5,47 @@
package kotlinx.metadata.test
import kotlinx.metadata.jvm.JvmMetadataVersion
import kotlinx.metadata.jvm.KotlinClassMetadata
import org.junit.Ignore
import kotlinx.metadata.jvm.Metadata
import org.junit.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class WritersContractTest {
val classMd = WritersContractTest::class.java.getMetadata()
val l: () -> Unit = {}
val lambdaMd = l::class.java.getMetadata()
// val fileFacadeMd = TODO
val fileFacadeMd = Class.forName("kotlinx.metadata.test.testdata.FileFacade").getMetadata()
val multiFileFacadeMd = Class.forName("kotlinx.metadata.test.testdata.MultiFileClassFacade").getMetadata()
val multiFilePartMd = Class.forName("kotlinx.metadata.test.testdata.MultiFileClassFacade__MultiFileClassFacade1Kt").getMetadata()
val unknown = Metadata(99, metadataVersion = intArrayOf(2, 0, 0), extraString = "blabla")
val everyType = listOf(classMd, fileFacadeMd, lambdaMd, multiFileFacadeMd, multiFilePartMd, unknown)
@Test
fun lenientDataCantBeWritten() {
val lenientClass = KotlinClassMetadata.readLenient(classMd)
fun lenientDataCantBeWritten() = everyType.forEach { md ->
val lenientClass = KotlinClassMetadata.readLenient(md)
assertFailsWith<IllegalArgumentException> { lenientClass.write() }
}
@Test
fun oldVersionCantBeWritten() {
val writeableClass = KotlinClassMetadata.readStrict(classMd) as KotlinClassMetadata.Class
writeableClass.version = intArrayOf(1, 2, 0)
fun oldVersionCantBeWritten() = everyType.forEach { md ->
val writeableClass = KotlinClassMetadata.readStrict(md)
writeableClass.version = JvmMetadataVersion(1, 2)
assertFailsWith<IllegalArgumentException> { writeableClass.write() }
}
@Test
fun transformIsIdentical() = everyType.forEach { before ->
val after = KotlinClassMetadata.transform(before) { }
// note: data1 and data2 will always differ
assertEquals(before.kind, after.kind)
assertContentEquals(before.metadataVersion, after.metadataVersion)
assertEquals(before.extraInt, after.extraInt)
assertEquals(before.extraString, after.extraString)
assertEquals(before.packageName, after.packageName)
}
}
@@ -0,0 +1,9 @@
/*
* 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.
*/
@file:JvmName("FileFacade")
package kotlinx.metadata.test.testdata
fun insideFileFacade() {}
@@ -0,0 +1,10 @@
/*
* 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.
*/
@file:JvmMultifileClass
@file:JvmName("MultiFileClassFacade")
package kotlinx.metadata.test.testdata
fun partOne() {}
@@ -0,0 +1,10 @@
/*
* 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.
*/
@file:JvmMultifileClass
@file:JvmName("MultiFileClassFacade")
package kotlinx.metadata.test.testdata
fun partTwo() {}