Document that read/write methods may throw IAE and make sure this is true:

- Replace !! with requireNotNull()
- Add try/catch to wrap all other exceptions (mainly protobuf parser ones)
- Add tests for malformed input for both read and write
- Hide InconsistentMetadataException from public API
- Replace its external usages with IllegalArgumentException
This commit is contained in:
Leonid Startsev
2023-01-24 13:38:58 +01:00
committed by Space Team
parent 64838d5ec9
commit 4b524b8589
10 changed files with 168 additions and 67 deletions
@@ -121,11 +121,6 @@ public final class kotlinx/metadata/FlagsKt {
public static final fun flagsOf ([Lkotlinx/metadata/Flag;)I public static final fun flagsOf ([Lkotlinx/metadata/Flag;)I
} }
public final class kotlinx/metadata/InconsistentKotlinMetadataException : java/lang/RuntimeException {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class kotlinx/metadata/KmAnnotation { public final class kotlinx/metadata/KmAnnotation {
public fun <init> (Ljava/lang/String;Ljava/util/Map;)V public fun <init> (Ljava/lang/String;Ljava/util/Map;)V
public fun equals (Ljava/lang/Object;)Z public fun equals (Ljava/lang/Object;)Z
@@ -26,6 +26,11 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION
* It is recommended to study documentation for each subclass and decide what subclasses one has to handle in the `when` expression, * 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. * trying to cover as much as possible.
* Normally, one would need at least a [Class] and a [FileFacade], as these are two most common kinds. * Normally, one would need at least a [Class] and a [FileFacade], as these are two most common kinds.
*
* Most of the subclasses offer a conversion method to transform metadata into a Km data structure — for example, [KotlinClassMetadata.Class.toKmClass].
* Km data structures represent Kotlin declarations and offer variety of properties to introspect and alter them.
* Note that parsing may be lazy, depending on the implementation; therefore, one may not see an [IllegalArgumentException] indicating malformed metadata
* until one calls `toKmClass` or a similar method.
*/ */
sealed class KotlinClassMetadata(val annotationData: Metadata) { sealed class KotlinClassMetadata(val annotationData: Metadata) {
@@ -37,16 +42,17 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
*/ */
class Class internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { class Class internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) {
private val classData by lazy(PUBLICATION) { private val classData by lazy(PUBLICATION) {
val data1 = (annotationData.data1.takeIf(Array<*>::isNotEmpty) JvmProtoBufUtil.readClassDataFrom(annotationData.requireNotEmpty(), annotationData.data2)
?: throw InconsistentKotlinMetadataException("data1 must not be empty"))
JvmProtoBufUtil.readClassDataFrom(data1, annotationData.data2)
} }
/** /**
* 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].
*/ */
fun toKmClass(): KmClass = fun toKmClass(): KmClass = wrapIntoMetadataExceptionWhenNeeded {
KmClass().apply(this::accept) KmClass().apply(this::accept)
}
/** /**
* Makes the given visitor visit metadata of this class. * Makes the given visitor visit metadata of this class.
@@ -100,16 +106,17 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
*/ */
class FileFacade internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { class FileFacade internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) {
private val packageData by lazy(PUBLICATION) { private val packageData by lazy(PUBLICATION) {
val data1 = (annotationData.data1.takeIf(Array<*>::isNotEmpty) JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2)
?: throw InconsistentKotlinMetadataException("data1 must not be empty"))
JvmProtoBufUtil.readPackageDataFrom(data1, annotationData.data2)
} }
/** /**
* 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].
*/ */
fun toKmPackage(): KmPackage = fun toKmPackage(): KmPackage = wrapIntoMetadataExceptionWhenNeeded {
KmPackage().apply(this::accept) KmPackage().apply(this::accept)
}
/** /**
* Makes the given visitor visit metadata of this file facade. * Makes the given visitor visit metadata of this file facade.
@@ -167,9 +174,12 @@ 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].
*/ */
fun toKmLambda(): KmLambda? = fun toKmLambda(): KmLambda? = wrapIntoMetadataExceptionWhenNeeded {
if (isLambda) KmLambda().apply(this::accept) else null if (isLambda) KmLambda().apply(this::accept) else null
}
/** /**
* Returns `true` if this synthetic class is a class file compiled for a Kotlin lambda. * Returns `true` if this synthetic class is a class file compiled for a Kotlin lambda.
@@ -181,13 +191,13 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* Makes the given visitor visit metadata of this file facade, if this synthetic class represents a Kotlin lambda * Makes the given visitor visit metadata of this file facade, if this synthetic class represents a Kotlin lambda
* (`isLambda` == true). * (`isLambda` == true).
* *
* Throws [IllegalStateException] if this synthetic class does not represent a Kotlin lambda. * Throws [IllegalArgumentException] if this synthetic class does not represent a Kotlin lambda.
* *
* @param v the visitor that must visit this lambda * @param v the visitor that must visit this lambda
*/ */
@Deprecated(VISITOR_API_MESSAGE) @Deprecated(VISITOR_API_MESSAGE)
fun accept(v: KmLambdaVisitor) { fun accept(v: KmLambdaVisitor) {
if (!isLambda) throw IllegalStateException( if (!isLambda) throw IllegalArgumentException(
"accept(KmLambdaVisitor) is only possible for synthetic classes which are lambdas (isLambda = true)" "accept(KmLambdaVisitor) is only possible for synthetic classes which are lambdas (isLambda = true)"
) )
@@ -318,9 +328,7 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
*/ */
class MultiFileClassPart internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) { class MultiFileClassPart internal constructor(annotationData: Metadata) : KotlinClassMetadata(annotationData) {
private val packageData by lazy(PUBLICATION) { private val packageData by lazy(PUBLICATION) {
val data1 = (annotationData.data1.takeIf(Array<*>::isNotEmpty) JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2)
?: throw InconsistentKotlinMetadataException("data1 must not be empty"))
JvmProtoBufUtil.readPackageDataFrom(data1, annotationData.data2)
} }
/** /**
@@ -331,9 +339,12 @@ 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].
*/ */
fun toKmPackage(): KmPackage = fun toKmPackage(): KmPackage = wrapIntoMetadataExceptionWhenNeeded {
KmPackage().apply(this::accept) KmPackage().apply(this::accept)
}
/** /**
* Makes the given visitor visit metadata of this multi-file class part. * Makes the given visitor visit metadata of this multi-file class part.
@@ -395,12 +406,16 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]` * [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]`
* @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]), * @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]),
* 0 by default * 0 by default
*
* @throws IllegalArgumentException if [kmClass] is not correct and cannot be written or if [metadataVersion] is not supported for writing.
*/ */
fun writeClass( fun writeClass(
kmClass: KmClass, kmClass: KmClass,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0 extraInt: Int = 0
): Class = Class.Writer().also { kmClass.accept(it) }.write(metadataVersion, extraInt) ): Class = wrapWriteIntoIAE {
Class.Writer().also { kmClass.accept(it) }.write(metadataVersion, extraInt)
}
/** /**
* Writes [kmPackage] contents as the file facade metadata. * Writes [kmPackage] contents as the file facade metadata.
@@ -409,12 +424,16 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]` * [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]`
* @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]), * @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]),
* 0 by default * 0 by default
*
* @throws IllegalArgumentException if [kmPackage] is not correct and cannot be written or if [metadataVersion] is not supported for writing.
*/ */
fun writeFileFacade( fun writeFileFacade(
kmPackage: KmPackage, kmPackage: KmPackage,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0 extraInt: Int = 0
): FileFacade = FileFacade.Writer().also { kmPackage.accept(it) }.write(metadataVersion, extraInt) ): FileFacade = wrapWriteIntoIAE {
FileFacade.Writer().also { kmPackage.accept(it) }.write(metadataVersion, extraInt)
}
/** /**
* Writes [kmLambda] as the synthetic class metadata. * Writes [kmLambda] as the synthetic class metadata.
@@ -423,12 +442,16 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]` * [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]`
* @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]), * @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]),
* 0 by default * 0 by default
*
* @throws IllegalArgumentException if [kmLambda] is not correct and cannot be written or if [metadataVersion] is not supported for writing.
*/ */
fun writeLambda( fun writeLambda(
kmLambda: KmLambda, kmLambda: KmLambda,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0 extraInt: Int = 0
): SyntheticClass = SyntheticClass.Writer().also { kmLambda.accept(it) }.write(metadataVersion, extraInt) ): SyntheticClass = wrapWriteIntoIAE {
SyntheticClass.Writer().also { kmLambda.accept(it) }.write(metadataVersion, extraInt)
}
/** /**
* Writes synthetic class metadata. * Writes synthetic class metadata.
@@ -437,6 +460,8 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]` * [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]`
* @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]), * @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]),
* 0 by default * 0 by default
*
* @throws IllegalArgumentException if [metadataVersion] is not supported for writing.
*/ */
fun writeSyntheticClass( fun writeSyntheticClass(
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
@@ -451,6 +476,8 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]` * [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]`
* @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]), * @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]),
* 0 by default * 0 by default
*
* @throws IllegalArgumentException if [metadataVersion] is not supported for writing.
*/ */
fun writeMultiFileClassFacade( fun writeMultiFileClassFacade(
partClassNames: List<String>, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, partClassNames: List<String>, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
@@ -465,24 +492,26 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
* [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]` * [KotlinClassMetadata.COMPATIBLE_METADATA_VERSION] by default. Cannot be less (lexicographically) than `[1, 4]`
* @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]), * @param extraInt the value of the class-level flags to be written to the metadata (see [Metadata.extraInt]),
* 0 by default * 0 by default
*
* @throws IllegalArgumentException if [kmPackage] is not correct and cannot be written or if [metadataVersion] is not supported for writing.
*/ */
fun writeMultiFileClassPart( fun writeMultiFileClassPart(
kmPackage: KmPackage, kmPackage: KmPackage,
facadeClassName: String, facadeClassName: String,
metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION,
extraInt: Int = 0 extraInt: Int = 0
): MultiFileClassPart = MultiFileClassPart.Writer().also { kmPackage.accept(it) }.write(facadeClassName, metadataVersion, extraInt) ): MultiFileClassPart = wrapWriteIntoIAE {
MultiFileClassPart.Writer().also { kmPackage.accept(it) }.write(facadeClassName, metadataVersion, extraInt)
}
/** /**
* 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 encodes an unsupported kind of Kotlin classes or has an unsupported metadata version. * this annotation, or `null` if this annotation data has an unsupported metadata version.
* *
* [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 [InconsistentKotlinMetadataException] if the metadata has inconsistencies which signal that it may have been * @throws IllegalArgumentException if the metadata cannot be parsed from binary format or is inconsistent with itself
* modified by a separate tool.
*/ */
@JvmStatic @JvmStatic
fun read(annotationData: Metadata): KotlinClassMetadata? { fun read(annotationData: Metadata): KotlinClassMetadata? {
@@ -492,19 +521,14 @@ sealed class KotlinClassMetadata(val annotationData: Metadata) {
).isCompatibleWithCurrentCompilerVersion() ).isCompatibleWithCurrentCompilerVersion()
) return null ) return null
return try { // All data is loaded lazily, no exceptions here to handle
when (annotationData.kind) { return when (annotationData.kind) {
CLASS_KIND -> Class(annotationData) CLASS_KIND -> Class(annotationData)
FILE_FACADE_KIND -> FileFacade(annotationData) FILE_FACADE_KIND -> FileFacade(annotationData)
SYNTHETIC_CLASS_KIND -> SyntheticClass(annotationData) SYNTHETIC_CLASS_KIND -> SyntheticClass(annotationData)
MULTI_FILE_CLASS_FACADE_KIND -> MultiFileClassFacade(annotationData) MULTI_FILE_CLASS_FACADE_KIND -> MultiFileClassFacade(annotationData)
MULTI_FILE_CLASS_PART_KIND -> MultiFileClassPart(annotationData) MULTI_FILE_CLASS_PART_KIND -> MultiFileClassPart(annotationData)
else -> Unknown(annotationData) else -> Unknown(annotationData)
}
} catch (e: InconsistentKotlinMetadataException) {
throw e
} catch (e: Throwable) {
throw InconsistentKotlinMetadataException("Exception occurred when reading Kotlin metadata", e)
} }
} }
@@ -3,7 +3,12 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@file:Suppress("MemberVisibilityCanBePrivate", "DEPRECATION") @file:Suppress(
"MemberVisibilityCanBePrivate",
"DEPRECATION",
"INVISIBLE_MEMBER", // InconsistentKotlinMetadataException
"INVISIBLE_REFERENCE"
)
package kotlinx.metadata.jvm package kotlinx.metadata.jvm
@@ -11,6 +16,8 @@ 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.internal.wrapIntoMetadataExceptionWhenNeeded
import kotlinx.metadata.jvm.internal.wrapWriteIntoIAE
import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf import org.jetbrains.kotlin.metadata.jvm.JvmModuleProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
@@ -123,25 +130,20 @@ class KotlinModuleMetadata(@Suppress("MemberVisibilityCanBePrivate") val bytes:
* Parses the given byte array with the .kotlin_module file content and returns the [KotlinModuleMetadata] instance, * Parses the given byte array with the .kotlin_module file content and returns the [KotlinModuleMetadata] instance,
* 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 [InconsistentKotlinMetadataException] 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? {
try { return wrapIntoMetadataExceptionWhenNeeded {
val result = KotlinModuleMetadata(bytes) val result = KotlinModuleMetadata(bytes)
if (result.data == ModuleMapping.EMPTY) return null when (result.data) {
ModuleMapping.EMPTY -> null
if (result.data == ModuleMapping.CORRUPTED) { ModuleMapping.CORRUPTED ->
throw InconsistentKotlinMetadataException("Data doesn't look like the content of a .kotlin_module file") throw InconsistentKotlinMetadataException("Data is not the content of a .kotlin_module file, or it has been corrupted.")
else -> result
} }
return result
} catch (e: InconsistentKotlinMetadataException) {
throw e
} catch (e: Throwable) {
throw InconsistentKotlinMetadataException("Exception occurred when reading Kotlin metadata", e)
} }
} }
@@ -150,10 +152,13 @@ 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.
*/ */
@UnstableMetadataApi @UnstableMetadataApi
fun write(kmModule: KmModule, metadataVersion: IntArray = COMPATIBLE_METADATA_VERSION): KotlinModuleMetadata = 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)
}
} }
} }
@@ -0,0 +1,38 @@
/*
* 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:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // InconsistentKotlinMetadataException
package kotlinx.metadata.jvm.internal
import kotlinx.metadata.InconsistentKotlinMetadataException
internal fun Metadata.requireNotEmpty(): Array<String> = data1.takeIf(Array<*>::isNotEmpty)
?: throw InconsistentKotlinMetadataException("Metadata is missing: kotlin.Metadata.data1 must not be an empty array")
internal inline fun <T> wrapIntoMetadataExceptionWhenNeeded(block: () -> T): T {
return try {
block()
} catch (e: Throwable) {
throw when (e) {
is IllegalArgumentException -> e // rethrow IAE as it is already correct exception type
is VirtualMachineError, is ThreadDeath -> e // rethrow VM errors
// other exceptions, like IOOBE or InvalidProtocolBufferException from proto parser
else -> InconsistentKotlinMetadataException("Exception occurred when reading Kotlin metadata", e)
}
}
}
internal inline fun <T> wrapWriteIntoIAE(block: () -> T): T {
return try {
block()
} catch (e: Throwable) {
throw when (e) {
is IllegalArgumentException -> e // rethrow IAE as it is already correct exception type
is VirtualMachineError, is ThreadDeath -> e // rethrow VM errors
// lateinit vars or proto writer can throw exceptions if required data is missing
else -> IllegalArgumentException("Kotlin metadata is not correct and can not be written", e)
}
}
}
@@ -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.KmClass
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.Metadata
import org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException
import org.junit.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
class MetadataExceptionsTest {
@Test
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))
val e = assertFailsWith<IllegalArgumentException> {
(KotlinClassMetadata.read(malformedMetadata) as KotlinClassMetadata.Class).toKmClass()
}
assertIs<InvalidProtocolBufferException>(e.cause)
}
@Test
fun testWriteMalformedClass() {
val kmClass = KmClass() // kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized
val e = assertFailsWith<IllegalArgumentException> {
KotlinClassMetadata.writeClass(kmClass)
}
assertIs<UninitializedPropertyAccessException>(e.cause)
}
}
@@ -1,8 +1,11 @@
/* /*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * 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 package kotlinx.metadata
class InconsistentKotlinMetadataException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) /**
* A generic exception that indicates problems with metadata deserialization.
*/
internal class InconsistentKotlinMetadataException(message: String, cause: Throwable? = null) : IllegalArgumentException(message, cause)
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@file:Suppress("DEPRECATION") @file:Suppress("DEPRECATION")
@@ -347,7 +347,7 @@ private inline fun ProtoBuf.TypeParameter.accept(
visit: (flags: Flags, name: String, id: Int, variance: KmVariance) -> KmTypeParameterVisitor?, visit: (flags: Flags, name: String, id: Int, variance: KmVariance) -> KmTypeParameterVisitor?,
c: ReadContext c: ReadContext
) { ) {
val variance = when (variance!!) { val variance = when (requireNotNull(variance)) {
ProtoBuf.TypeParameter.Variance.IN -> KmVariance.IN ProtoBuf.TypeParameter.Variance.IN -> KmVariance.IN
ProtoBuf.TypeParameter.Variance.OUT -> KmVariance.OUT ProtoBuf.TypeParameter.Variance.OUT -> KmVariance.OUT
ProtoBuf.TypeParameter.Variance.INV -> KmVariance.INVARIANT ProtoBuf.TypeParameter.Variance.INV -> KmVariance.INVARIANT
@@ -384,7 +384,7 @@ private fun ProtoBuf.Type.accept(v: KmTypeVisitor, c: ReadContext) {
} }
for (argument in argumentList) { for (argument in argumentList) {
val variance = when (argument.projection!!) { val variance = when (requireNotNull(argument.projection)) {
ProtoBuf.Type.Argument.Projection.IN -> KmVariance.IN ProtoBuf.Type.Argument.Projection.IN -> KmVariance.IN
ProtoBuf.Type.Argument.Projection.OUT -> KmVariance.OUT ProtoBuf.Type.Argument.Projection.OUT -> KmVariance.OUT
ProtoBuf.Type.Argument.Projection.INV -> KmVariance.INVARIANT ProtoBuf.Type.Argument.Projection.INV -> KmVariance.INVARIANT
@@ -451,13 +451,13 @@ private fun ProtoBuf.Contract.accept(v: KmContractVisitor, c: ReadContext) {
for (effect in effectList) { for (effect in effectList) {
if (!effect.hasEffectType()) continue if (!effect.hasEffectType()) continue
val effectType = when (effect.effectType!!) { val effectType = when (requireNotNull(effect.effectType)) {
ProtoBuf.Effect.EffectType.RETURNS_CONSTANT -> KmEffectType.RETURNS_CONSTANT ProtoBuf.Effect.EffectType.RETURNS_CONSTANT -> KmEffectType.RETURNS_CONSTANT
ProtoBuf.Effect.EffectType.CALLS -> KmEffectType.CALLS ProtoBuf.Effect.EffectType.CALLS -> KmEffectType.CALLS
ProtoBuf.Effect.EffectType.RETURNS_NOT_NULL -> KmEffectType.RETURNS_NOT_NULL ProtoBuf.Effect.EffectType.RETURNS_NOT_NULL -> KmEffectType.RETURNS_NOT_NULL
} }
val effectKind = if (!effect.hasKind()) null else when (effect.kind!!) { val effectKind = if (!effect.hasKind()) null else when (requireNotNull(effect.kind)) {
ProtoBuf.Effect.InvocationKind.AT_MOST_ONCE -> KmEffectInvocationKind.AT_MOST_ONCE ProtoBuf.Effect.InvocationKind.AT_MOST_ONCE -> KmEffectInvocationKind.AT_MOST_ONCE
ProtoBuf.Effect.InvocationKind.EXACTLY_ONCE -> KmEffectInvocationKind.EXACTLY_ONCE ProtoBuf.Effect.InvocationKind.EXACTLY_ONCE -> KmEffectInvocationKind.EXACTLY_ONCE
ProtoBuf.Effect.InvocationKind.AT_LEAST_ONCE -> KmEffectInvocationKind.AT_LEAST_ONCE ProtoBuf.Effect.InvocationKind.AT_LEAST_ONCE -> KmEffectInvocationKind.AT_LEAST_ONCE
@@ -491,7 +491,7 @@ private fun ProtoBuf.Expression.accept(v: KmEffectExpressionVisitor, c: ReadCont
if (hasConstantValue()) { if (hasConstantValue()) {
v.visitConstantValue( v.visitConstantValue(
when (constantValue!!) { when (requireNotNull(constantValue)) {
ProtoBuf.Expression.ConstantValue.TRUE -> true ProtoBuf.Expression.ConstantValue.TRUE -> true
ProtoBuf.Expression.ConstantValue.FALSE -> false ProtoBuf.Expression.ConstantValue.FALSE -> false
ProtoBuf.Expression.ConstantValue.NULL -> null ProtoBuf.Expression.ConstantValue.NULL -> null
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. * 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. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@@ -866,6 +866,7 @@ class KmType(var flags: Flags) : KmTypeVisitor() {
* Populates the given visitor with data in this type. * Populates the given visitor with data in this type.
* *
* @param visitor the visitor which will visit data in this type * @param visitor the visitor which will visit data in this type
* @throws IllegalArgumentException if type metadata is inconsistent
*/ */
@Deprecated(VISITOR_API_MESSAGE) @Deprecated(VISITOR_API_MESSAGE)
fun accept(visitor: KmTypeVisitor) { fun accept(visitor: KmTypeVisitor) {
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.kotlinp package org.jetbrains.kotlin.kotlinp
import kotlinx.metadata.InconsistentKotlinMetadataException
import kotlinx.metadata.jvm.KotlinClassMetadata import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.KotlinModuleMetadata import kotlinx.metadata.jvm.KotlinModuleMetadata
import kotlinx.metadata.jvm.UnstableMetadataApi import kotlinx.metadata.jvm.UnstableMetadataApi
@@ -30,7 +29,7 @@ class Kotlinp(private val settings: KotlinpSettings) {
val header = file.readKotlinClassHeader() ?: throw KotlinpException("file is not a Kotlin class file: $file") val header = file.readKotlinClassHeader() ?: throw KotlinpException("file is not a Kotlin class file: $file")
return try { return try {
KotlinClassMetadata.read(header) KotlinClassMetadata.read(header)
} catch (e: InconsistentKotlinMetadataException) { } catch (e: IllegalArgumentException) {
throw KotlinpException("inconsistent Kotlin metadata: ${e.message}") throw KotlinpException("inconsistent Kotlin metadata: ${e.message}")
} }
} }
@@ -171,7 +171,7 @@ private fun printType(type: KmType): String {
} else { } else {
val (variance, argumentType) = argument val (variance, argumentType) = argument
if (variance == null || argumentType == null) if (variance == null || argumentType == null)
throw InconsistentKotlinMetadataException("Variance and type must be set for non-star type projection") throw IllegalArgumentException("Variance and type must be set for non-star type projection")
val argumentTypeString = printType(argumentType) val argumentTypeString = printType(argumentType)
buildString { buildString {
if (variance != KmVariance.INVARIANT) { if (variance != KmVariance.INVARIANT) {