Add KmVersionRequirementKind.UNKNOWN

for cases when the version requirement cannot be read correctly (missing from the table).

It usually happens when we read the inner class' old metadata, which stored everything
in the outer class' table.

Fixes #KT-60870


Merge-request: KT-MR-13337
Merged-by: Leonid Startsev <leonid.startsev@jetbrains.com>
This commit is contained in:
Leonid Startsev
2023-12-12 08:47:41 +00:00
committed by Space Team
parent 8a863e00ba
commit 9933423a0b
9 changed files with 141 additions and 31 deletions
@@ -1106,6 +1106,7 @@ public final class kotlinx/metadata/KmVersionRequirement : kotlinx/metadata/KmVe
public final fun setLevel (Lkotlinx/metadata/KmVersionRequirementLevel;)V
public final fun setMessage (Ljava/lang/String;)V
public final fun setVersion (Lkotlinx/metadata/KmVersion;)V
public fun toString ()Ljava/lang/String;
public fun visit (Lkotlinx/metadata/KmVersionRequirementVersionKind;Lkotlinx/metadata/KmVersionRequirementLevel;Ljava/lang/Integer;Ljava/lang/String;)V
public fun visitVersion (III)V
}
@@ -1123,6 +1124,7 @@ public final class kotlinx/metadata/KmVersionRequirementVersionKind : java/lang/
public static final field API_VERSION Lkotlinx/metadata/KmVersionRequirementVersionKind;
public static final field COMPILER_VERSION Lkotlinx/metadata/KmVersionRequirementVersionKind;
public static final field LANGUAGE_VERSION Lkotlinx/metadata/KmVersionRequirementVersionKind;
public static final field UNKNOWN Lkotlinx/metadata/KmVersionRequirementVersionKind;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lkotlinx/metadata/KmVersionRequirementVersionKind;
public static fun values ()[Lkotlinx/metadata/KmVersionRequirementVersionKind;
@@ -12,23 +12,26 @@ import kotlinx.metadata.KmPackage
import kotlinx.metadata.internal.toKmClass
import kotlinx.metadata.internal.toKmLambda
import kotlinx.metadata.internal.toKmPackage
import kotlinx.metadata.jvm.KmModule
import kotlinx.metadata.jvm.KmPackageParts
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import kotlinx.metadata.jvm.*
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion as CompilerMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
internal object JvmReadUtils {
private fun isLessThan14(annotationData: Metadata): Boolean {
return JvmMetadataVersion(annotationData.metadataVersion) < JvmMetadataVersion(1, 4, 0)
}
internal fun readKmClass(annotationData: Metadata): KmClass {
val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(annotationData.requireNotEmpty(), annotationData.data2)
return proto.toKmClass(strings)
return proto.toKmClass(strings, isLessThan14(annotationData))
}
internal fun readKmPackage(annotationData: Metadata): KmPackage {
val (strings, proto) = JvmProtoBufUtil.readPackageDataFrom(annotationData.requireNotEmpty(), annotationData.data2)
return proto.toKmPackage(strings)
return proto.toKmPackage(strings, isLessThan14(annotationData))
}
internal fun readKmLambda(annotationData: Metadata): KmLambda? {
@@ -37,7 +40,7 @@ internal object JvmReadUtils {
JvmProtoBufUtil.readFunctionDataFrom(data1, annotationData.data2)
} ?: return null
val (strings, proto) = functionData
return proto.toKmLambda(strings)
return proto.toKmLambda(strings, isLessThan14(annotationData))
}
internal fun readMetadataImpl(annotationData: Metadata, lenient: Boolean): KotlinClassMetadata {
@@ -72,7 +75,7 @@ internal object JvmReadUtils {
}
for (classProto in data.moduleData.optionalAnnotations) {
v.optionalAnnotationClasses.add(classProto.toKmClass(data.moduleData.nameResolver))
v.optionalAnnotationClasses.add(classProto.toKmClass(data.moduleData.nameResolver, false))
}
return v
}
@@ -80,21 +83,21 @@ internal object JvmReadUtils {
private fun checkMetadataVersionForRead(annotationData: Metadata, lenient: Boolean) {
if (annotationData.metadataVersion.isEmpty())
throw IllegalArgumentException("Provided Metadata instance does not have metadataVersion in it and therefore is malformed and cannot be read.")
val jvmMetadataVersion = JvmMetadataVersion(
val jvmMetadataVersion = CompilerMetadataVersion(
annotationData.metadataVersion,
(annotationData.extraInt and (1 shl 3)/* see JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG */) != 0
)
throwIfNotCompatible(jvmMetadataVersion, lenient)
}
internal fun throwIfNotCompatible(jvmMetadataVersion: JvmMetadataVersion, lenient: Boolean) {
internal fun throwIfNotCompatible(jvmMetadataVersion: CompilerMetadataVersion, lenient: Boolean) {
val isAtLeast110 = jvmMetadataVersion.isAtLeast(1, 1, 0)
val isCompatible = if (lenient) isAtLeast110 else jvmMetadataVersion.isCompatibleWithCurrentCompilerVersion()
if (!isCompatible) {
// Kotlin 1.0 produces classfiles with metadataVersion = 1.1.0, while 1.0.0 represents unsupported pre-1.0 Kotlin (see JvmMetadataVersion.kt:39)
val postfix =
if (!isAtLeast110) "while minimum supported version is 1.1.0 (Kotlin 1.0)."
else "while maximum supported version is ${if (jvmMetadataVersion.isStrictSemantics) JvmMetadataVersion.INSTANCE else JvmMetadataVersion.INSTANCE_NEXT}. To support newer versions, update the kotlinx-metadata-jvm library."
else "while maximum supported version is ${if (jvmMetadataVersion.isStrictSemantics) CompilerMetadataVersion.INSTANCE else CompilerMetadataVersion.INSTANCE_NEXT}. To support newer versions, update the kotlinx-metadata-jvm library."
throw IllegalArgumentException("Provided Metadata instance has version $jvmMetadataVersion, $postfix")
}
}
@@ -0,0 +1,73 @@
/*
* 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.internal.ClassWriter
import kotlinx.metadata.jvm.JvmMetadataVersion
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.Metadata
import kotlinx.metadata.jvm.internal.writeProtoBufData
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class UnknownVersionRequirementTest {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.RequireKotlin("1.8.0", "foobar")
class Sample
private val requireKotlinValue = "KmVersionRequirement(kind=LANGUAGE_VERSION, level=ERROR, version=1.8.0, errorCode=null, message=foobar)"
private fun corrupt(original: KmClass): Pair<Array<String>, Array<String>> {
val writer = ClassWriter(JvmStringTable())
writer.writeClass(original)
writer.t.addVersionRequirement(239) // invalid
val (d1, d2) = writeProtoBufData(writer.t.build(), writer.c)
return d1 to d2
}
@Test
fun incorrectVersionRequirementFailsIfMetadataIsNew() {
val (d1, d2) = corrupt(Sample::class.java.readMetadataAsKmClass())
val incorrect =
Metadata(KotlinClassMetadata.CLASS_KIND, JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toIntArray(), d1, d2, extraInt = 0)
assertFailsWith<IllegalArgumentException> {
incorrect.readMetadataAsClass()
}
}
@Test
fun incorrectVersionRequirementHandledAsUnknown() {
val original = Sample::class.java.readMetadataAsKmClass()
assertEquals(1, original.versionRequirements.size)
assertEquals(
requireKotlinValue,
original.versionRequirements.single().toString()
)
val (d1, d2) = corrupt(original)
val incorrect =
Metadata(KotlinClassMetadata.CLASS_KIND, intArrayOf(1, 1, 3), d1, d2, extraInt = 0)
val withInvalidRequirement = incorrect.readMetadataAsClass()
assertEquals(2, withInvalidRequirement.kmClass.versionRequirements.size)
assertEquals(
"[$requireKotlinValue, KmVersionRequirement(kind=UNKNOWN, level=HIDDEN, version=256.256.256, errorCode=null, message=null)]",
withInvalidRequirement.kmClass.versionRequirements.toString()
)
withInvalidRequirement.version = JvmMetadataVersion.LATEST_STABLE_SUPPORTED
val rewritten = withInvalidRequirement.write().readAsKmClass()
assertEquals(1, rewritten.versionRequirements.size)
assertEquals(
requireKotlinValue,
rewritten.versionRequirements.single().toString()
)
}
}
@@ -18,4 +18,7 @@ internal fun Metadata.readAsKmClass(): KmClass {
}
internal fun Class<*>.readMetadataAsKmClass(): KmClass = getMetadata().readAsKmClass()
internal fun Class<*>.readMetadataAsClass(): KotlinClassMetadata.Class = KotlinClassMetadata.readStrict(getMetadata()) as KotlinClassMetadata.Class
internal fun Class<*>.readMetadataAsClass(): KotlinClassMetadata.Class = getMetadata().readMetadataAsClass()
internal fun Metadata.readMetadataAsClass(): KotlinClassMetadata.Class = KotlinClassMetadata.readStrict(this) as KotlinClassMetadata.Class
@@ -1034,6 +1034,10 @@ public class KmVersionRequirement : KmVersionRequirementVisitor() {
visitor.visitVersion(version.major, version.minor, version.patch)
visitor.visitEnd()
}
override fun toString(): String {
return "KmVersionRequirement(kind=$kind, level=$level, version=$version, errorCode=$errorCode, message=$message)"
}
}
/**
@@ -983,4 +983,20 @@ public enum class KmVersionRequirementVersionKind {
* Indicates that certain API version is required.
*/
API_VERSION,
/**
* Represents a version requirement not successfully parsed from the metadata.
*
* The old metadata format (from Kotlin 1.3 and earlier) did not have enough information for correct parsing of version requirements in some cases,
* so a stub of this kind is inserted instead.
*
* [KmVersionRequirement] with this kind always has [KmVersionRequirementLevel.HIDDEN] level, `256.256.256` [KmVersionRequirement.version],
* and `null` [KmVersionRequirement.errorCode] and [KmVersionRequirement.message].
*
* Version requirements of this kind are being ignored by writers (i.e., are not written back).
*
* See the following issues for details: [KT-60870](https://youtrack.jetbrains.com/issue/KT-60870), [KT-25120](https://youtrack.jetbrains.com/issue/KT-25120)
*/
UNKNOWN
;
}
@@ -24,6 +24,7 @@ public class ReadContext(
public val strings: NameResolver,
public val types: TypeTable,
@get:IgnoreInApiDump internal val versionRequirements: VersionRequirementTable,
internal val ignoreUnknownVersionRequirements: Boolean,
private val parent: ReadContext? = null,
internal val contextExtensions: List<ReadContextExtension> = emptyList()
) {
@@ -41,7 +42,7 @@ public class ReadContext(
typeParameterNameToId[name] ?: parent?.getTypeParameterId(name)
internal fun withTypeParameters(typeParameters: List<ProtoBuf.TypeParameter>): ReadContext =
ReadContext(strings, types, versionRequirements, this, contextExtensions).apply {
ReadContext(strings, types, versionRequirements, ignoreUnknownVersionRequirements, this, contextExtensions).apply {
for (typeParameter in typeParameters) {
typeParameterNameToId[typeParameter.name] = typeParameter.id
}
@@ -51,6 +52,7 @@ public class ReadContext(
@OptIn(ExperimentalContextReceivers::class)
public fun ProtoBuf.Class.toKmClass(
strings: NameResolver,
ignoreUnknownVersionRequirements: Boolean = false,
contextExtensions: List<ReadContextExtension> = emptyList(),
): KmClass {
val v = KmClass()
@@ -58,6 +60,7 @@ public fun ProtoBuf.Class.toKmClass(
strings,
TypeTable(typeTable),
VersionRequirementTable.create(versionRequirementTable),
ignoreUnknownVersionRequirements,
contextExtensions = contextExtensions
).withTypeParameters(typeParameterList)
@@ -105,6 +108,7 @@ private fun ProtoBuf.Class.loadInlineClassUnderlyingType(c: ReadContext): ProtoB
public fun ProtoBuf.Package.toKmPackage(
strings: NameResolver,
ignoreUnknownVersionRequirements: Boolean = false,
contextExtensions: List<ReadContextExtension> = emptyList(),
): KmPackage {
val v = KmPackage()
@@ -112,6 +116,7 @@ public fun ProtoBuf.Package.toKmPackage(
strings,
TypeTable(typeTable),
VersionRequirementTable.create(versionRequirementTable),
ignoreUnknownVersionRequirements,
contextExtensions = contextExtensions
)
@@ -131,11 +136,12 @@ public fun ProtoBuf.PackageFragment.toKmModuleFragment(
strings,
TypeTable(ProtoBuf.TypeTable.newBuilder().build()),
VersionRequirementTable.EMPTY,
false, // toKmModuleFragment is used for klib only
contextExtensions = contextExtensions
)
v.pkg = `package`.toKmPackage(strings, contextExtensions)
class_List.mapTo(v.classes) { it.toKmClass(strings, contextExtensions) }
v.pkg = `package`.toKmPackage(strings, false, contextExtensions)
class_List.mapTo(v.classes) { it.toKmClass(strings, false, contextExtensions) }
c.extensions.forEach { it.readModuleFragmentExtensions(v, this, c) }
@@ -153,9 +159,9 @@ private fun KmDeclarationContainer.visitDeclarations(
protoTypeAliases.mapTo(typeAliases) { it.toKmTypeAlias(c) }
}
public fun ProtoBuf.Function.toKmLambda(strings: NameResolver): KmLambda {
public fun ProtoBuf.Function.toKmLambda(strings: NameResolver, ignoreUnknownVersionRequirements: Boolean = false): KmLambda {
val v = KmLambda()
val c = ReadContext(strings, TypeTable(typeTable), VersionRequirementTable.EMPTY)
val c = ReadContext(strings, TypeTable(typeTable), VersionRequirementTable.EMPTY, ignoreUnknownVersionRequirements)
v.function = this.toKmFunction(c)
return v
}
@@ -303,26 +309,27 @@ private fun ProtoBuf.Type.toKmType(c: ReadContext): KmType {
private fun readVersionRequirement(id: Int, c: ReadContext): KmVersionRequirement {
val v = KmVersionRequirement()
val message = VersionRequirement.create(id, c.strings, c.versionRequirements)
?: throw InconsistentKotlinMetadataException("No VersionRequirement with the given id in the table")
if (message == null && !c.ignoreUnknownVersionRequirements) throw InconsistentKotlinMetadataException("No VersionRequirement with the given id in the table")
val kind = when (message.kind) {
val kind = when (message?.kind) {
ProtoBuf.VersionRequirement.VersionKind.LANGUAGE_VERSION -> KmVersionRequirementVersionKind.LANGUAGE_VERSION
ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION -> KmVersionRequirementVersionKind.COMPILER_VERSION
ProtoBuf.VersionRequirement.VersionKind.API_VERSION -> KmVersionRequirementVersionKind.API_VERSION
null -> KmVersionRequirementVersionKind.UNKNOWN
}
val level = when (message.level) {
val level = when (message?.level) {
DeprecationLevel.WARNING -> KmVersionRequirementLevel.WARNING
DeprecationLevel.ERROR -> KmVersionRequirementLevel.ERROR
DeprecationLevel.HIDDEN -> KmVersionRequirementLevel.HIDDEN
DeprecationLevel.HIDDEN, null -> KmVersionRequirementLevel.HIDDEN
}
v.kind = kind
v.level = level
v.errorCode = message.errorCode
v.message = message.message
v.errorCode = message?.errorCode
v.message = message?.message
val (major, minor, patch) = message.version
val (major, minor, patch) = message?.version ?: VersionRequirement.Version.INFINITY
v.version = KmVersion(major, minor, patch)
return v
}
@@ -110,7 +110,7 @@ private fun WriteContext.writeConstructor(kmConstructor: KmConstructor): ProtoBu
kmConstructor.valueParameters.forEach {
t.addValueParameter(writeValueParameter(it).build())
}
kmConstructor.versionRequirements.forEach { t.addVersionRequirement(writeVersionRequirement(it)) }
t.addAllVersionRequirement(kmConstructor.versionRequirements.mapNotNull(::writeVersionRequirement))
extensions.forEach {
it.writeConstructorExtensions(kmConstructor, t, this)
}
@@ -129,7 +129,7 @@ private fun WriteContext.writeFunction(kmFunction: KmFunction): ProtoBuf.Functio
t.addAllContextReceiverType(kmFunction.contextReceiverTypes.map { writeType(it).build() })
t.addAllValueParameter(kmFunction.valueParameters.map { writeValueParameter(it).build() })
t.returnType = writeType(kmFunction.returnType).build()
t.addAllVersionRequirement(kmFunction.versionRequirements.map(::writeVersionRequirement))
t.addAllVersionRequirement(kmFunction.versionRequirements.mapNotNull(::writeVersionRequirement))
@OptIn(ExperimentalContracts::class)
kmFunction.contract?.let { t.contract = writeContract(it) }
@@ -156,7 +156,7 @@ public fun WriteContext.writeProperty(kmProperty: KmProperty): ProtoBuf.Property
t.addAllContextReceiverType(kmProperty.contextReceiverTypes.map { writeType(it).build() })
kmProperty.setterParameter?.let { t.setterValueParameter = writeValueParameter(it).build() }
t.returnType = writeType(kmProperty.returnType).build()
t.addAllVersionRequirement(kmProperty.versionRequirements.map { writeVersionRequirement(it) })
t.addAllVersionRequirement(kmProperty.versionRequirements.mapNotNull { writeVersionRequirement(it) })
extensions.forEach { it.writePropertyExtensions(kmProperty, t, this) }
@@ -192,7 +192,7 @@ private fun WriteContext.writeTypeAlias(
t.underlyingType = writeType(typeAlias.underlyingType).build()
t.expandedType = writeType(typeAlias.expandedType).build()
t.addAllAnnotation(typeAlias.annotations.map { it.writeAnnotation(strings).build() })
t.addAllVersionRequirement(typeAlias.versionRequirements.map(::writeVersionRequirement))
t.addAllVersionRequirement(typeAlias.versionRequirements.mapNotNull(::writeVersionRequirement))
extensions.forEach { it.writeTypeAliasExtensions(typeAlias, t, this) }
if (typeAlias.flags != ProtoBuf.TypeAlias.getDefaultInstance().flags) {
@@ -202,7 +202,7 @@ private fun WriteContext.writeTypeAlias(
return t
}
private fun WriteContext.writeVersionRequirement(kmVersionRequirement: KmVersionRequirement): Int {
private fun WriteContext.writeVersionRequirement(kmVersionRequirement: KmVersionRequirement): Int? {
val kind = kmVersionRequirement.kind
val level = kmVersionRequirement.level
val errorCode = kmVersionRequirement.errorCode
@@ -212,6 +212,7 @@ private fun WriteContext.writeVersionRequirement(kmVersionRequirement: KmVersion
KmVersionRequirementVersionKind.LANGUAGE_VERSION -> ProtoBuf.VersionRequirement.VersionKind.LANGUAGE_VERSION
KmVersionRequirementVersionKind.COMPILER_VERSION -> ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION
KmVersionRequirementVersionKind.API_VERSION -> ProtoBuf.VersionRequirement.VersionKind.API_VERSION
KmVersionRequirementVersionKind.UNKNOWN -> return null
}
if (versionKind != defaultInstanceForType.versionKind) {
this.versionKind = versionKind
@@ -329,7 +330,7 @@ public open class ClassWriter(stringTable: StringTable, contextExtensions: List<
@OptIn(ExperimentalContextReceivers::class)
t.addAllContextReceiverType(kmClass.contextReceiverTypes.map { c.writeType(it).build() })
t.addAllVersionRequirement(kmClass.versionRequirements.map { c.writeVersionRequirement(it) })
t.addAllVersionRequirement(kmClass.versionRequirements.mapNotNull { c.writeVersionRequirement(it) })
c.extensions.forEach { it.writeClassExtensions(kmClass, t, c) }
@@ -322,6 +322,7 @@ private fun printVersionRequirement(versionRequirement: KmVersionRequirement): S
KmVersionRequirementVersionKind.LANGUAGE_VERSION -> "language version"
KmVersionRequirementVersionKind.COMPILER_VERSION -> "compiler version"
KmVersionRequirementVersionKind.API_VERSION -> "API version"
KmVersionRequirementVersionKind.UNKNOWN -> "unknown requirement"
}
).append(" ").append(version)