Throw exception instead of returning null from KotlinClass(Module)Metadata.read()
This commit is contained in:
committed by
Space Team
parent
1c629e748f
commit
c3d363d1b8
@@ -1398,7 +1398,7 @@ public final class kotlinx/metadata/jvm/KotlinClassMetadata$Unknown : kotlinx/me
|
|||||||
|
|
||||||
public final class kotlinx/metadata/jvm/KotlinModuleMetadata {
|
public final class kotlinx/metadata/jvm/KotlinModuleMetadata {
|
||||||
public static final field Companion Lkotlinx/metadata/jvm/KotlinModuleMetadata$Companion;
|
public static final field Companion Lkotlinx/metadata/jvm/KotlinModuleMetadata$Companion;
|
||||||
public fun <init> ([B)V
|
public synthetic fun <init> ([BLkotlinx/metadata/internal/metadata/jvm/deserialization/ModuleMapping;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public final fun accept (Lkotlinx/metadata/jvm/KmModuleVisitor;)V
|
public final fun accept (Lkotlinx/metadata/jvm/KmModuleVisitor;)V
|
||||||
public final fun getBytes ()[B
|
public final fun getBytes ()[B
|
||||||
public static final fun read ([B)Lkotlinx/metadata/jvm/KotlinModuleMetadata;
|
public static final fun read ([B)Lkotlinx/metadata/jvm/KotlinModuleMetadata;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
/**
|
/**
|
||||||
* Returns a new [KmClass] instance created from this class metadata.
|
* Returns a new [KmClass] instance created from this class metadata.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if metadata is malformed and can't be transformed into [KmClass].
|
* @throws IllegalArgumentException if metadata is malformed or inconsistent and can't be transformed into [KmClass].
|
||||||
*/
|
*/
|
||||||
fun toKmClass(): KmClass = wrapIntoMetadataExceptionWhenNeeded {
|
fun toKmClass(): KmClass = wrapIntoMetadataExceptionWhenNeeded {
|
||||||
KmClass().apply(this::accept)
|
KmClass().apply(this::accept)
|
||||||
@@ -112,7 +112,7 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
/**
|
/**
|
||||||
* Creates a new [KmPackage] instance from this file facade metadata.
|
* Creates a new [KmPackage] instance from this file facade metadata.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if metadata is malformed and can't be transformed into [KmPackage].
|
* @throws IllegalArgumentException if metadata is malformed or inconsistent and can't be transformed into [KmPackage].
|
||||||
*/
|
*/
|
||||||
fun toKmPackage(): KmPackage = wrapIntoMetadataExceptionWhenNeeded {
|
fun toKmPackage(): KmPackage = wrapIntoMetadataExceptionWhenNeeded {
|
||||||
KmPackage().apply(this::accept)
|
KmPackage().apply(this::accept)
|
||||||
@@ -175,7 +175,7 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
* Creates a new [KmLambda] instance from this synthetic class metadata.
|
* Creates a new [KmLambda] instance from this synthetic class metadata.
|
||||||
* Returns `null` if this synthetic class does not represent a lambda.
|
* Returns `null` if this synthetic class does not represent a lambda.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if metadata is malformed and can't be transformed into [KmLambda].
|
* @throws IllegalArgumentException if metadata is malformed or inconsistent and can't be transformed into [KmLambda].
|
||||||
*/
|
*/
|
||||||
fun toKmLambda(): KmLambda? = wrapIntoMetadataExceptionWhenNeeded {
|
fun toKmLambda(): KmLambda? = wrapIntoMetadataExceptionWhenNeeded {
|
||||||
if (isLambda) KmLambda().apply(this::accept) else null
|
if (isLambda) KmLambda().apply(this::accept) else null
|
||||||
@@ -340,7 +340,7 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
/**
|
/**
|
||||||
* Creates a new [KmPackage] instance from this multi-file class part metadata.
|
* Creates a new [KmPackage] instance from this multi-file class part metadata.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if metadata is malformed and can't be transformed into [KmPackage].
|
* @throws IllegalArgumentException if metadata is malformed or inconsistent and can't be transformed into [KmPackage].
|
||||||
*/
|
*/
|
||||||
fun toKmPackage(): KmPackage = wrapIntoMetadataExceptionWhenNeeded {
|
fun toKmPackage(): KmPackage = wrapIntoMetadataExceptionWhenNeeded {
|
||||||
KmPackage().apply(this::accept)
|
KmPackage().apply(this::accept)
|
||||||
@@ -518,20 +518,23 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and parses the given annotation data of a Kotlin JVM class file and returns the correct type of [KotlinClassMetadata] encoded by
|
* Reads and parses the given annotation data of a Kotlin JVM class file and returns the correct type of [KotlinClassMetadata] encoded by
|
||||||
* this annotation, or `null` if this annotation data has an unsupported metadata version.
|
* this annotation, if metadata version is supported.
|
||||||
*
|
*
|
||||||
* [annotationData] may be obtained reflectively, constructed manually or with helper [kotlinx.metadata.jvm.Metadata] function,
|
* [annotationData] may be obtained reflectively, constructed manually or with helper [kotlinx.metadata.jvm.Metadata] function,
|
||||||
* or equivalent [KotlinClassHeader] can be used.
|
* or equivalent [KotlinClassHeader] can be used.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if the metadata cannot be parsed from binary format or is inconsistent with itself
|
* 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 Kotlin version is 1.7.0, the latest kotlinx-metadata-jvm can read binaries produced by Kotlin
|
||||||
|
* compilers from 1.0 to 1.8.* inclusively.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the metadata version is unsupported
|
||||||
|
*
|
||||||
|
* @see COMPATIBLE_METADATA_VERSION
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun read(annotationData: Metadata): KotlinClassMetadata? {
|
fun read(annotationData: Metadata): KotlinClassMetadata {
|
||||||
if (!JvmMetadataVersion(
|
checkMetadataVersionForRead(annotationData)
|
||||||
annotationData.metadataVersion,
|
|
||||||
(annotationData.extraInt and (1 shl 3)/* see JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG */) != 0
|
|
||||||
).isCompatibleWithCurrentCompilerVersion()
|
|
||||||
) return null
|
|
||||||
|
|
||||||
// All data is loaded lazily, no exceptions here to handle
|
// All data is loaded lazily, no exceptions here to handle
|
||||||
return when (annotationData.kind) {
|
return when (annotationData.kind) {
|
||||||
@@ -544,6 +547,26 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkMetadataVersionForRead(annotationData: Metadata) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun throwIfNotCompatible(jvmMetadataVersion: JvmMetadataVersion) {
|
||||||
|
if (!jvmMetadataVersion.isCompatibleWithCurrentCompilerVersion()) {
|
||||||
|
// 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."
|
||||||
|
throw IllegalArgumentException("Provided Metadata instance has version $jvmMetadataVersion, $postfix")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkMetadataVersion(version: IntArray) {
|
private fun checkMetadataVersion(version: IntArray) {
|
||||||
require(version.size >= 2 && version[0] >= 1 && (version[0] > 1 || version[1] >= 4)) {
|
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. " +
|
"This version of kotlinx-metadata-jvm doesn't support writing Kotlin metadata of version earlier than 1.4. " +
|
||||||
@@ -593,7 +616,11 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
|||||||
const val MULTI_FILE_CLASS_PART_KIND = 5
|
const val MULTI_FILE_CLASS_PART_KIND = 5
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The latest metadata version supported by this version of the library.
|
* 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.
|
||||||
|
*
|
||||||
|
* 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]`.
|
||||||
*
|
*
|
||||||
* @see Metadata.metadataVersion
|
* @see Metadata.metadataVersion
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import kotlinx.metadata.*
|
|||||||
import kotlinx.metadata.internal.accept
|
import kotlinx.metadata.internal.accept
|
||||||
import kotlinx.metadata.jvm.internal.IgnoreInApiDump
|
import kotlinx.metadata.jvm.internal.IgnoreInApiDump
|
||||||
import kotlinx.metadata.jvm.KotlinClassMetadata.Companion.COMPATIBLE_METADATA_VERSION
|
import kotlinx.metadata.jvm.KotlinClassMetadata.Companion.COMPATIBLE_METADATA_VERSION
|
||||||
|
import kotlinx.metadata.jvm.KotlinClassMetadata.Companion.throwIfNotCompatible
|
||||||
import kotlinx.metadata.jvm.internal.wrapIntoMetadataExceptionWhenNeeded
|
import kotlinx.metadata.jvm.internal.wrapIntoMetadataExceptionWhenNeeded
|
||||||
import kotlinx.metadata.jvm.internal.wrapWriteIntoIAE
|
import kotlinx.metadata.jvm.internal.wrapWriteIntoIAE
|
||||||
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf
|
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf
|
||||||
@@ -28,26 +29,26 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.serializeToByteArray
|
|||||||
* Represents the parsed metadata of a Kotlin JVM module file.
|
* Represents the parsed metadata of a Kotlin JVM module file.
|
||||||
*
|
*
|
||||||
* To create an instance of [KotlinModuleMetadata], load the contents of the `.kotlin_module` file into a byte array
|
* To create an instance of [KotlinModuleMetadata], load the contents of the `.kotlin_module` file into a byte array
|
||||||
* and call [KotlinModuleMetadata.read].
|
* and call [KotlinModuleMetadata.read]. Then it is possible to transform the result into [KmModule] with [KotlinModuleMetadata.toKmModule].
|
||||||
|
*
|
||||||
|
* `.kotlin_module` file is produced per Kotlin compilation, and contains auxiliary information, such as a map of all single- and multi-file facades ([KmModule.packageParts]),
|
||||||
|
* `@OptionalExpectation` declarations ([KmModule.optionalAnnotationClasses]), and module annotations ([KmModule.annotations).
|
||||||
*
|
*
|
||||||
* @property bytes the byte array representing the contents of a `.kotlin_module` file
|
* @property bytes the byte array representing the contents of a `.kotlin_module` file
|
||||||
*/
|
*/
|
||||||
@UnstableMetadataApi
|
@UnstableMetadataApi
|
||||||
class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes: ByteArray) {
|
class KotlinModuleMetadata private constructor(
|
||||||
@get:IgnoreInApiDump
|
@Suppress("MemberVisibilityCanBePrivate") val bytes: ByteArray,
|
||||||
internal val data: ModuleMapping = ModuleMapping.loadModuleMapping(
|
@get:IgnoreInApiDump internal val data: ModuleMapping
|
||||||
bytes, javaClass.name,
|
) {
|
||||||
skipMetadataVersionCheck = false,
|
|
||||||
isJvmPackageNameSupported = true
|
|
||||||
) {
|
|
||||||
// TODO: report incorrect versions of modules
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visits metadata of this module with a new [KmModule] instance and returns that instance.
|
* Visits metadata of this module with a new [KmModule] instance and returns that instance.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if parsed metadata is inconsistent and can't be transformed into [KmModule].
|
||||||
*/
|
*/
|
||||||
fun toKmModule(): KmModule =
|
fun toKmModule(): KmModule = wrapIntoMetadataExceptionWhenNeeded {
|
||||||
KmModule().apply(this::accept)
|
KmModule().apply(this::accept)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [KmModuleVisitor] that generates the metadata of a Kotlin JVM module file.
|
* A [KmModuleVisitor] that generates the metadata of a Kotlin JVM module file.
|
||||||
@@ -96,8 +97,10 @@ class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes:
|
|||||||
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default
|
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default
|
||||||
*/
|
*/
|
||||||
@Deprecated("Writer API is deprecated as excessive and cumbersome. Please use KotlinModuleMetadata.write(kmModule, metadataVersion)")
|
@Deprecated("Writer API is deprecated as excessive and cumbersome. Please use KotlinModuleMetadata.write(kmModule, metadataVersion)")
|
||||||
fun write(metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata =
|
fun write(metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata {
|
||||||
KotlinModuleMetadata(b.build().serializeToByteArray(JvmMetadataVersion(*metadataVersion), 0))
|
val bytes = b.build().serializeToByteArray(JvmMetadataVersion(*metadataVersion), 0)
|
||||||
|
return KotlinModuleMetadata(bytes, dataFromBytes(bytes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,19 +134,18 @@ class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes:
|
|||||||
* or `null` if this byte array encodes a module with an unsupported metadata version.
|
* or `null` if this byte array encodes a module with an unsupported metadata version.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if an error happened while parsing the given byte array,
|
* @throws IllegalArgumentException if an error happened while parsing the given byte array,
|
||||||
* which means that it's either not the content of a .kotlin_module file, or it has been corrupted.
|
* which means that it's either not the content of a `.kotlin_module` file, or it has been corrupted.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@UnstableMetadataApi
|
@UnstableMetadataApi
|
||||||
fun read(bytes: ByteArray): KotlinModuleMetadata? {
|
fun read(bytes: ByteArray): KotlinModuleMetadata {
|
||||||
return wrapIntoMetadataExceptionWhenNeeded {
|
return wrapIntoMetadataExceptionWhenNeeded {
|
||||||
val result = KotlinModuleMetadata(bytes)
|
val result = dataFromBytes(bytes)
|
||||||
when (result.data) {
|
when (result) {
|
||||||
ModuleMapping.EMPTY -> null
|
ModuleMapping.EMPTY, ModuleMapping.CORRUPTED ->
|
||||||
ModuleMapping.CORRUPTED ->
|
|
||||||
throw InconsistentKotlinMetadataException("Data is not the content of a .kotlin_module file, or it has been corrupted.")
|
throw InconsistentKotlinMetadataException("Data is not the content of a .kotlin_module file, or it has been corrupted.")
|
||||||
else -> result
|
|
||||||
}
|
}
|
||||||
|
KotlinModuleMetadata(bytes, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +155,7 @@ class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes:
|
|||||||
* @param metadataVersion metadata version to be written to the metadata (see [Metadata.metadataVersion]),
|
* @param metadataVersion metadata version to be written to the metadata (see [Metadata.metadataVersion]),
|
||||||
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default
|
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if [kmModule] is not correct and cannot be written or if [metadataVersion] is not supported for writing.
|
* @throws IllegalArgumentException if [kmModule] is not correct and can't be written or if [metadataVersion] is not supported for writing.
|
||||||
*/
|
*/
|
||||||
@UnstableMetadataApi
|
@UnstableMetadataApi
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -161,6 +163,13 @@ class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes:
|
|||||||
fun write(kmModule: KmModule, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata = wrapWriteIntoIAE {
|
fun write(kmModule: KmModule, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata = wrapWriteIntoIAE {
|
||||||
Writer().also { kmModule.accept(it) }.write(metadataVersion)
|
Writer().also { kmModule.accept(it) }.write(metadataVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dataFromBytes(bytes: ByteArray): ModuleMapping {
|
||||||
|
return ModuleMapping.loadModuleMapping(
|
||||||
|
bytes, "KotlinModuleMetadata", skipMetadataVersionCheck = false,
|
||||||
|
isJvmPackageNameSupported = true, reportIncompatibleVersionError = ::throwIfNotCompatible
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-3
@@ -8,10 +8,10 @@ package kotlinx.metadata.test
|
|||||||
import kotlinx.metadata.KmClass
|
import kotlinx.metadata.KmClass
|
||||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||||
import kotlinx.metadata.jvm.Metadata
|
import kotlinx.metadata.jvm.Metadata
|
||||||
|
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||||
import org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException
|
import org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.*
|
||||||
import kotlin.test.assertIs
|
|
||||||
|
|
||||||
class MetadataExceptionsTest {
|
class MetadataExceptionsTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -33,4 +33,35 @@ class MetadataExceptionsTest {
|
|||||||
}
|
}
|
||||||
assertIs<UninitializedPropertyAccessException>(e.cause)
|
assertIs<UninitializedPropertyAccessException>(e.cause)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private fun doTestVersion(version: IntArray, expectedText: String) {
|
||||||
|
val md = Metadata(metadataVersion = version)
|
||||||
|
val iae = assertFailsWith<IllegalArgumentException> { KotlinClassMetadata.read(md) }
|
||||||
|
assertContains(iae.message.orEmpty(), expectedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReadObsoleteVersion() {
|
||||||
|
doTestVersion(intArrayOf(0, 1, 0), "version 0.1.0, while minimum supported version is 1.1.0")
|
||||||
|
doTestVersion(intArrayOf(1, 0, 0), "version 1.0.0, while minimum supported version is 1.1.0")
|
||||||
|
doTestVersion(intArrayOf(1, 0, 255), "version 1.0.255, while minimum supported version is 1.1.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReadNewerVersion() {
|
||||||
|
val versionPlus2 = JvmMetadataVersion.INSTANCE.next().next()
|
||||||
|
doTestVersion(
|
||||||
|
versionPlus2.toArray(),
|
||||||
|
"version $versionPlus2, while maximum supported version is ${JvmMetadataVersion.INSTANCE_NEXT}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testInvalidVersion() {
|
||||||
|
doTestVersion(intArrayOf(), "instance does not have metadataVersion in it and therefore is malformed and cannot be read")
|
||||||
|
doTestVersion(
|
||||||
|
JvmMetadataVersion.INVALID_VERSION.toArray(),
|
||||||
|
"instance does not have metadataVersion in it and therefore is malformed and cannot be read"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user