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:
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
|
||||
import java.util.*
|
||||
|
||||
object KotlinJsMetadataVersionIndex : KotlinMetadataVersionIndexBase<KotlinJsMetadataVersionIndex, JsMetadataVersion>(
|
||||
KotlinJsMetadataVersionIndex::class.java, ::JsMetadataVersion
|
||||
KotlinJsMetadataVersionIndex::class.java, { version, _ -> JsMetadataVersion(*version) }
|
||||
) {
|
||||
override fun getIndexer() = INDEXER
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.fileTypes.StdFileTypes
|
||||
import com.intellij.util.indexing.DataIndexer
|
||||
import com.intellij.util.indexing.FileBasedIndex
|
||||
import com.intellij.util.indexing.FileContent
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
||||
@@ -29,7 +30,8 @@ import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
object KotlinJvmMetadataVersionIndex : KotlinMetadataVersionIndexBase<KotlinJvmMetadataVersionIndex, JvmMetadataVersion>(
|
||||
KotlinJvmMetadataVersionIndex::class.java, ::JvmMetadataVersion
|
||||
KotlinJvmMetadataVersionIndex::class.java,
|
||||
{ version, isStrictSemantics -> JvmMetadataVersion(version, isStrictSemantics = isStrictSemantics!!) }
|
||||
) {
|
||||
override fun getIndexer() = INDEXER
|
||||
|
||||
@@ -37,16 +39,21 @@ object KotlinJvmMetadataVersionIndex : KotlinMetadataVersionIndexBase<KotlinJvmM
|
||||
|
||||
override fun getVersion() = VERSION
|
||||
|
||||
private val VERSION = 4
|
||||
override fun isExtraBooleanNeeded(): Boolean = true
|
||||
|
||||
override fun getExtraBoolean(version: JvmMetadataVersion): Boolean = version.isStrictSemantics
|
||||
|
||||
private const val VERSION = 5
|
||||
|
||||
private val kindsToIndex = setOf(
|
||||
KotlinClassHeader.Kind.CLASS,
|
||||
KotlinClassHeader.Kind.FILE_FACADE,
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS
|
||||
KotlinClassHeader.Kind.CLASS,
|
||||
KotlinClassHeader.Kind.FILE_FACADE,
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS
|
||||
)
|
||||
|
||||
private val INDEXER = DataIndexer<JvmMetadataVersion, Void, FileContent> { inputData: FileContent ->
|
||||
var version: JvmMetadataVersion? = null
|
||||
var versionArray: IntArray? = null
|
||||
var isStrictSemantics = false
|
||||
var annotationPresent = false
|
||||
var kind: KotlinClassHeader.Kind? = null
|
||||
|
||||
@@ -61,11 +68,14 @@ object KotlinJvmMetadataVersionIndex : KotlinMetadataVersionIndexBase<KotlinJvmM
|
||||
override fun visit(name: String, value: Any) {
|
||||
when (name) {
|
||||
METADATA_VERSION_FIELD_NAME -> if (value is IntArray) {
|
||||
version = createBinaryVersion(value)
|
||||
versionArray = value
|
||||
}
|
||||
KIND_FIELD_NAME -> if (value is Int) {
|
||||
kind = KotlinClassHeader.Kind.getById(value)
|
||||
}
|
||||
METADATA_EXTRA_INT_FIELD_NAME -> if (value is Int) {
|
||||
isStrictSemantics = (value and JvmAnnotationNames.METADATA_STRICT_VERSION_SEMANTICS_FLAG) != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,15 +83,17 @@ object KotlinJvmMetadataVersionIndex : KotlinMetadataVersionIndexBase<KotlinJvmM
|
||||
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
|
||||
var version =
|
||||
if (versionArray != null) createBinaryVersion(versionArray!!, isStrictSemantics) else null
|
||||
|
||||
if (kind !in kindsToIndex) {
|
||||
// Do not index metadata version for synthetic classes
|
||||
version = null
|
||||
}
|
||||
else if (annotationPresent && version == null) {
|
||||
} else if (annotationPresent && version == null) {
|
||||
// No version at all because the class is too old, or version is set to something weird
|
||||
version = JvmMetadataVersion.INVALID_VERSION
|
||||
}
|
||||
|
||||
if (version != null) mapOf(version!! to null) else mapOf()
|
||||
if (version != null) mapOf(version to null) else emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.io.DataOutput
|
||||
*/
|
||||
abstract class KotlinMetadataVersionIndexBase<T, V : BinaryVersion>(
|
||||
private val classOfIndex: Class<T>,
|
||||
protected val createBinaryVersion: (IntArray) -> V
|
||||
protected val createBinaryVersion: (IntArray, Boolean?) -> V
|
||||
) : ScalarIndexExtension<V>() {
|
||||
|
||||
override fun getName(): ID<V, Void> = ID.create<V, Void>(classOfIndex.canonicalName)
|
||||
@@ -43,7 +43,9 @@ abstract class KotlinMetadataVersionIndexBase<T, V : BinaryVersion>(
|
||||
|
||||
override fun read(input: DataInput): V {
|
||||
val size = DataInputOutputUtil.readINT(input)
|
||||
return createBinaryVersion((0..size - 1).map { DataInputOutputUtil.readINT(input) }.toIntArray())
|
||||
val versionArray = (0..size - 1).map { DataInputOutputUtil.readINT(input) }.toIntArray()
|
||||
val extraBoolean = if (isExtraBooleanNeeded()) DataInputOutputUtil.readINT(input) == 1 else null
|
||||
return createBinaryVersion(versionArray, extraBoolean)
|
||||
}
|
||||
|
||||
override fun save(output: DataOutput, value: V) {
|
||||
@@ -52,11 +54,17 @@ abstract class KotlinMetadataVersionIndexBase<T, V : BinaryVersion>(
|
||||
for (number in array) {
|
||||
DataInputOutputUtil.writeINT(output, number)
|
||||
}
|
||||
if (isExtraBooleanNeeded()) {
|
||||
DataInputOutputUtil.writeINT(output, if (getExtraBoolean(value)) 1 else 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dependsOnFileContent() = true
|
||||
|
||||
protected open fun isExtraBooleanNeeded(): Boolean = false
|
||||
protected open fun getExtraBoolean(version: V): Boolean = throw UnsupportedOperationException()
|
||||
|
||||
protected val LOG: Logger = Logger.getInstance(classOfIndex)
|
||||
|
||||
protected inline fun tryBlock(inputData: FileContent, body: () -> Unit) {
|
||||
|
||||
Reference in New Issue
Block a user