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 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 getBytes ()[B
|
||||
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.
|
||||
*
|
||||
* @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 {
|
||||
KmClass().apply(this::accept)
|
||||
@@ -112,7 +112,7 @@ sealed class KotlinClassMetadata(val annotationData: 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 {
|
||||
KmPackage().apply(this::accept)
|
||||
@@ -175,7 +175,7 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
||||
* Creates a new [KmLambda] instance from this synthetic class metadata.
|
||||
* 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 {
|
||||
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.
|
||||
*
|
||||
* @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 {
|
||||
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
|
||||
* 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,
|
||||
* 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
|
||||
fun read(annotationData: Metadata): KotlinClassMetadata? {
|
||||
if (!JvmMetadataVersion(
|
||||
annotationData.metadataVersion,
|
||||
(annotationData.extraInt and (1 shl 3)/* see JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG */) != 0
|
||||
).isCompatibleWithCurrentCompilerVersion()
|
||||
) return null
|
||||
fun read(annotationData: Metadata): KotlinClassMetadata {
|
||||
checkMetadataVersionForRead(annotationData)
|
||||
|
||||
// All data is loaded lazily, no exceptions here to handle
|
||||
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) {
|
||||
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. " +
|
||||
@@ -593,7 +616,11 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ import kotlinx.metadata.*
|
||||
import kotlinx.metadata.internal.accept
|
||||
import kotlinx.metadata.jvm.internal.IgnoreInApiDump
|
||||
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.wrapWriteIntoIAE
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
@UnstableMetadataApi
|
||||
class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes: ByteArray) {
|
||||
@get:IgnoreInApiDump
|
||||
internal val data: ModuleMapping = ModuleMapping.loadModuleMapping(
|
||||
bytes, javaClass.name,
|
||||
skipMetadataVersionCheck = false,
|
||||
isJvmPackageNameSupported = true
|
||||
) {
|
||||
// TODO: report incorrect versions of modules
|
||||
}
|
||||
|
||||
class KotlinModuleMetadata private constructor(
|
||||
@Suppress("MemberVisibilityCanBePrivate") val bytes: ByteArray,
|
||||
@get:IgnoreInApiDump internal val data: ModuleMapping
|
||||
) {
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Deprecated("Writer API is deprecated as excessive and cumbersome. Please use KotlinModuleMetadata.write(kmModule, metadataVersion)")
|
||||
fun write(metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata =
|
||||
KotlinModuleMetadata(b.build().serializeToByteArray(JvmMetadataVersion(*metadataVersion), 0))
|
||||
fun write(metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata {
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
@UnstableMetadataApi
|
||||
fun read(bytes: ByteArray): KotlinModuleMetadata? {
|
||||
fun read(bytes: ByteArray): KotlinModuleMetadata {
|
||||
return wrapIntoMetadataExceptionWhenNeeded {
|
||||
val result = KotlinModuleMetadata(bytes)
|
||||
when (result.data) {
|
||||
ModuleMapping.EMPTY -> null
|
||||
ModuleMapping.CORRUPTED ->
|
||||
val result = dataFromBytes(bytes)
|
||||
when (result) {
|
||||
ModuleMapping.EMPTY, ModuleMapping.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]),
|
||||
* [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
|
||||
@JvmStatic
|
||||
@@ -161,6 +163,13 @@ class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes:
|
||||
fun write(kmModule: KmModule, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata = wrapWriteIntoIAE {
|
||||
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.jvm.KotlinClassMetadata
|
||||
import kotlinx.metadata.jvm.Metadata
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||
import org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.*
|
||||
|
||||
class MetadataExceptionsTest {
|
||||
@Test
|
||||
@@ -33,4 +33,35 @@ class MetadataExceptionsTest {
|
||||
}
|
||||
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