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:
Alexander Udalov
2018-09-06 21:49:24 +03:00
parent 8c942752e8
commit 1d5d6b5b72
22 changed files with 167 additions and 38 deletions
@@ -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) {