Support strict metadata version semantics
Preface: Kotlin 1.3 will be able to read metadata of .class files produced by Kotlin 1.4 (see KT-25972). Also, to simplify implementation and to improve diagnostic messages, we're going to advance JVM metadata version to 1.4.0 in Kotlin 1.4, and would like to keep it in sync with the compiler version thereafter. This presents a problem: in an unlikely event that before releasing 1.4, we find out that the metadata-reading implementation in 1.3 was incorrect, we'd like to be able to fix the bug in that implementation and _forbid_ 1.3 from reading metadata of 1.4. But prior to this commit the only way to do this was to advance the metadata version, in this case to 1.5, and that breaks the metadata/compiler version equivalence we'd like to keep. The solution is to add another boolean flag to the class file, called "strict metadata version semantics", which signifies that if this class file has metadata version 1.X, then it can only be read by the compilers of versions 1.X and greater. This flag effectively disables the smooth migration scenario proposed in KT-25972 (as does increasing metadata version by 2), and will be used only in hopeless situations as in the case described above.
This commit is contained in:
@@ -38,6 +38,7 @@ public final class JvmAnnotationNames {
|
||||
public static final int METADATA_MULTIFILE_PARTS_INHERIT_FLAG = 1 << 0;
|
||||
public static final int METADATA_PRE_RELEASE_FLAG = 1 << 1;
|
||||
public static final int METADATA_SCRIPT_FLAG = 1 << 2;
|
||||
public static final int METADATA_STRICT_VERSION_SEMANTICS_FLAG = 1 << 3;
|
||||
|
||||
public static final Name DEFAULT_ANNOTATION_MEMBER_NAME = Name.identifier("value");
|
||||
|
||||
|
||||
+9
-8
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.load.kotlin.header;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.SourceElement;
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmBytecodeBinaryVersion;
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion;
|
||||
import org.jetbrains.kotlin.name.ClassId;
|
||||
@@ -48,7 +49,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
||||
HEADER_KINDS.put(ClassId.topLevel(new FqName("kotlin.jvm.internal.KotlinSyntheticClass")), SYNTHETIC_CLASS);
|
||||
}
|
||||
|
||||
private JvmMetadataVersion metadataVersion = null;
|
||||
private int[] metadataVersionArray = null;
|
||||
private JvmBytecodeBinaryVersion bytecodeVersion = null;
|
||||
private String extraString = null;
|
||||
private int extraInt = 0;
|
||||
@@ -60,15 +61,15 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
||||
|
||||
@Nullable
|
||||
public KotlinClassHeader createHeader() {
|
||||
if (headerKind == null) {
|
||||
if (headerKind == null || metadataVersionArray == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JvmMetadataVersion metadataVersion =
|
||||
new JvmMetadataVersion(metadataVersionArray, (extraInt & JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG) != 0);
|
||||
|
||||
if (!metadataVersion.isCompatible()) {
|
||||
incompatibleData = data;
|
||||
}
|
||||
|
||||
if (metadataVersion == null || !metadataVersion.isCompatible()) {
|
||||
data = null;
|
||||
}
|
||||
else if (shouldHaveData() && data == null) {
|
||||
@@ -79,7 +80,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
||||
|
||||
return new KotlinClassHeader(
|
||||
headerKind,
|
||||
metadataVersion != null ? metadataVersion : JvmMetadataVersion.INVALID_VERSION,
|
||||
metadataVersion,
|
||||
bytecodeVersion != null ? bytecodeVersion : JvmBytecodeBinaryVersion.INVALID_VERSION,
|
||||
data,
|
||||
incompatibleData,
|
||||
@@ -137,7 +138,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
||||
}
|
||||
else if (METADATA_VERSION_FIELD_NAME.equals(string)) {
|
||||
if (value instanceof int[]) {
|
||||
metadataVersion = new JvmMetadataVersion((int[]) value);
|
||||
metadataVersionArray = (int[]) value;
|
||||
}
|
||||
}
|
||||
else if (BYTECODE_VERSION_FIELD_NAME.equals(string)) {
|
||||
@@ -224,7 +225,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
|
||||
String string = name.asString();
|
||||
if ("version".equals(string)) {
|
||||
if (value instanceof int[]) {
|
||||
metadataVersion = new JvmMetadataVersion((int[]) value);
|
||||
metadataVersionArray = (int[]) value;
|
||||
|
||||
// If there's no bytecode binary version in the class file, we assume it to be equal to the metadata version
|
||||
if (bytecodeVersion == null) {
|
||||
|
||||
+13
-6
@@ -11,12 +11,19 @@ import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
||||
* The version of the metadata serialized by the compiler and deserialized by the compiler and reflection.
|
||||
* This version includes the version of the core protobuf messages (metadata.proto) as well as JVM extensions (jvm_metadata.proto).
|
||||
*/
|
||||
class JvmMetadataVersion(vararg numbers: Int) : BinaryVersion(*numbers) {
|
||||
// In Kotlin 1.4, JVM metadata version is going to be advanced to 1.4.0.
|
||||
// Kotlin 1.3 is able to read metadata of versions up to Kotlin 1.4.
|
||||
// NOTE: 1.0 is a pre-Kotlin-1.0 metadata version, with which the current compiler is incompatible
|
||||
override fun isCompatible() =
|
||||
major == 1 && minor in 1..4
|
||||
class JvmMetadataVersion(versionArray: IntArray, val isStrictSemantics: Boolean) : BinaryVersion(*versionArray) {
|
||||
constructor(vararg numbers: Int) : this(numbers, isStrictSemantics = false)
|
||||
|
||||
override fun isCompatible(): Boolean =
|
||||
// NOTE: 1.0 is a pre-Kotlin-1.0 metadata version, with which the current compiler is incompatible
|
||||
(major != 1 || minor != 0) &&
|
||||
if (isStrictSemantics) {
|
||||
isCompatibleTo(INSTANCE)
|
||||
} else {
|
||||
// In Kotlin 1.4, JVM metadata version is going to be advanced to 1.4.0.
|
||||
// Kotlin 1.3 is able to read metadata of versions up to Kotlin 1.4 (unless the version has strict semantics).
|
||||
major == 1 && minor <= 4
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
|
||||
+11
-5
@@ -31,6 +31,8 @@ class ModuleMapping private constructor(
|
||||
@JvmField
|
||||
val CORRUPTED: ModuleMapping = ModuleMapping(emptyMap(), BinaryModuleData(emptyList()), "CORRUPTED")
|
||||
|
||||
const val STRICT_METADATA_VERSION_SEMANTICS_FLAG = 1 shl 0
|
||||
|
||||
fun loadModuleMapping(
|
||||
bytes: ByteArray?,
|
||||
debugName: String,
|
||||
@@ -50,15 +52,19 @@ class ModuleMapping private constructor(
|
||||
return CORRUPTED
|
||||
}
|
||||
|
||||
val version = JvmMetadataVersion(*versionNumber)
|
||||
if (!skipMetadataVersionCheck && !version.isCompatible()) {
|
||||
reportIncompatibleVersionError(version)
|
||||
val preVersion = JvmMetadataVersion(*versionNumber)
|
||||
if (!skipMetadataVersionCheck && !preVersion.isCompatible()) {
|
||||
reportIncompatibleVersionError(preVersion)
|
||||
return EMPTY
|
||||
}
|
||||
|
||||
// Since Kotlin 1.4, we write integer flags between the version and the proto
|
||||
if (isKotlin1Dot4OrLater(version)) {
|
||||
stream.readInt()
|
||||
val flags = if (isKotlin1Dot4OrLater(preVersion)) stream.readInt() else 0
|
||||
|
||||
val version = JvmMetadataVersion(versionNumber, (flags and STRICT_METADATA_VERSION_SEMANTICS_FLAG) != 0)
|
||||
if (!skipMetadataVersionCheck && !version.isCompatible()) {
|
||||
reportIncompatibleVersionError(version)
|
||||
return EMPTY
|
||||
}
|
||||
|
||||
val moduleProto = JvmModuleProtoBuf.Module.parseFrom(stream) ?: return EMPTY
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package kotlin.reflect.jvm
|
||||
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||
@@ -36,7 +37,8 @@ fun <R> Function<R>.reflect(): KFunction<R>? {
|
||||
val annotation = javaClass.getAnnotation(Metadata::class.java) ?: return null
|
||||
val data = annotation.d1.takeUnless(Array<String>::isEmpty) ?: return null
|
||||
val (nameResolver, proto) = JvmProtoBufUtil.readFunctionDataFrom(data, annotation.d2)
|
||||
val metadataVersion = JvmMetadataVersion(*annotation.mv)
|
||||
val metadataVersion =
|
||||
JvmMetadataVersion(annotation.mv, (annotation.xi and JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG) != 0)
|
||||
|
||||
val descriptor = deserializeToDescriptor(
|
||||
javaClass, proto, nameResolver, TypeTable(proto.typeTable), metadataVersion, MemberDeserializer::loadFunction
|
||||
|
||||
Reference in New Issue
Block a user