Split JVM binary version into two: metadata and bytecode interface

Currently all code only uses the first one (JvmMetadataVersion), later the
bytecode interface version (JvmBytecodeBinaryVersion) will be used only in
codegen and reflection to avoid compiling against or calling methods with
unsupported conventions like default method naming and signature,
getters/setters naming etc.
This commit is contained in:
Alexander Udalov
2015-12-28 20:01:00 +03:00
parent fc88a0186f
commit bd47e9d47b
13 changed files with 136 additions and 79 deletions
@@ -18,6 +18,10 @@ package org.jetbrains.kotlin.load.java
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
/**
* The version of conventions used in bytecode of generated .class files, such as default method naming & signatures,
* internal member name mangling specifics, property getter/setter names, etc.
*/
class JvmBytecodeBinaryVersion protected constructor(
major: Int, minor: Int, patch: Int, rest: List<Int>
) : BinaryVersion(major, minor, patch, rest) {
@@ -70,8 +70,8 @@ fun LazyJavaResolverContext.resolveKotlinBinaryClass(kotlinClass: KotlinJvmBinar
val header = kotlinClass.classHeader
return when {
!header.version.isCompatible() -> {
components.errorReporter.reportIncompatibleAbiVersion(kotlinClass.classId, kotlinClass.location, header.version)
!header.metadataVersion.isCompatible() -> {
components.errorReporter.reportIncompatibleAbiVersion(kotlinClass.classId, kotlinClass.location, header.metadataVersion)
KotlinClassLookupResult.NotFound
}
header.kind == KotlinClassHeader.Kind.CLASS -> {
@@ -115,8 +115,8 @@ public final class DeserializedDescriptorResolver {
@Nullable
public String[] readData(@NotNull KotlinJvmBinaryClass kotlinClass, @NotNull Set<KotlinClassHeader.Kind> expectedKinds) {
KotlinClassHeader header = kotlinClass.getClassHeader();
if (!header.getVersion().isCompatible()) {
errorReporter.reportIncompatibleAbiVersion(kotlinClass.getClassId(), kotlinClass.getLocation(), header.getVersion());
if (!header.getMetadataVersion().isCompatible()) {
errorReporter.reportIncompatibleAbiVersion(kotlinClass.getClassId(), kotlinClass.getLocation(), header.getMetadataVersion());
}
else if (expectedKinds.contains(header.getKind())) {
return header.getAnnotationData();
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.load.kotlin
import org.jetbrains.kotlin.serialization.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 (descriptors.proto) as well as JVM extensions (jvm_descriptors.proto).
*/
class JvmMetadataVersion protected constructor(
major: Int, minor: Int, patch: Int, rest: List<Int>
) : BinaryVersion(major, minor, patch, rest) {
override fun isCompatible() = this.isCompatibleTo(INSTANCE)
companion object {
@JvmField
val INSTANCE = create(1, 0, 2)
@JvmField
val INVALID_VERSION = JvmMetadataVersion.create(IntArray(0))
@JvmStatic
fun create(version: IntArray) = create(version, ::JvmMetadataVersion)
@JvmStatic
fun create(major: Int, minor: Int, patch: Int) = create(major, minor, patch, ::JvmMetadataVersion)
}
}
@@ -16,11 +16,13 @@
package org.jetbrains.kotlin.load.kotlin.header
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
class KotlinClassHeader(
val kind: KotlinClassHeader.Kind,
val version: BinaryVersion,
val metadataVersion: JvmMetadataVersion,
val bytecodeVersion: JvmBytecodeBinaryVersion,
val annotationData: Array<String>?,
val strings: Array<String>?,
val filePartClassNames: Array<String>?,
@@ -36,10 +38,5 @@ class KotlinClassHeader(
SYNTHETIC_CLASS
}
override fun toString() = "$kind " + (if (isLocalClass) "(local) " else "") + "version=$version"
override fun toString() = "$kind " + (if (isLocalClass) "(local) " else "") + "version=$metadataVersion"
}
fun KotlinClassHeader.isCompatibleClassKind(): Boolean = version.isCompatible() && kind == KotlinClassHeader.Kind.CLASS
fun KotlinClassHeader.isCompatibleFileFacadeKind(): Boolean = version.isCompatible() && kind == KotlinClassHeader.Kind.FILE_FACADE
fun KotlinClassHeader.isCompatibleMultifileClassKind(): Boolean = version.isCompatible() && kind == KotlinClassHeader.Kind.MULTIFILE_CLASS
fun KotlinClassHeader.isCompatibleMultifileClassPartKind(): Boolean = version.isCompatible() && kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
@@ -20,10 +20,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.SourceElement;
import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion;
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion;
import java.util.ArrayList;
import java.util.HashMap;
@@ -45,7 +45,8 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
HEADER_KINDS.put(ClassId.topLevel(KOTLIN_SYNTHETIC_CLASS), SYNTHETIC_CLASS);
}
private BinaryVersion version = JvmBytecodeBinaryVersion.INVALID_VERSION;
private JvmMetadataVersion metadataVersion = null;
private JvmBytecodeBinaryVersion bytecodeVersion = null;
private String multifileClassName = null;
private String[] filePartClassNames = null;
private String[] annotationData = null;
@@ -60,7 +61,7 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
return null;
}
if (!version.isCompatible()) {
if (metadataVersion == null || !metadataVersion.isCompatible()) {
annotationData = null;
}
else if (shouldHaveData() && annotationData == null) {
@@ -70,7 +71,10 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
}
return new KotlinClassHeader(
headerKind, version, annotationData, strings, filePartClassNames, multifileClassName, isInterfaceDefaultImpls, isLocalClass
headerKind,
metadataVersion != null ? metadataVersion : JvmMetadataVersion.INVALID_VERSION,
bytecodeVersion != null ? bytecodeVersion : JvmBytecodeBinaryVersion.INVALID_VERSION,
annotationData, strings, filePartClassNames, multifileClassName, isInterfaceDefaultImpls, isLocalClass
);
}
@@ -118,7 +122,14 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor
String string = name.asString();
if (VERSION_FIELD_NAME.equals(string)) {
version = value instanceof int[] ? JvmBytecodeBinaryVersion.create((int[]) value) : JvmBytecodeBinaryVersion.INVALID_VERSION;
if (value instanceof int[]) {
metadataVersion = JvmMetadataVersion.create((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) {
bytecodeVersion = JvmBytecodeBinaryVersion.create((int[]) value);
}
}
}
else if (MULTIFILE_CLASS_NAME_FIELD_NAME.equals(string)) {
multifileClassName = value instanceof String ? (String) value : null;
@@ -54,7 +54,7 @@ fun isKotlinWithCompatibleAbiVersion(file: VirtualFile): Boolean {
if (!isKotlinJvmCompiledFile(file)) return false
val kotlinClass = IDEKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(file)
return kotlinClass != null && kotlinClass.classHeader.version.isCompatible()
return kotlinClass != null && kotlinClass.classHeader.metadataVersion.isCompatible()
}
/**
@@ -29,9 +29,7 @@ import org.jetbrains.kotlin.idea.decompiler.textBuilder.ResolverForDecompiler
import org.jetbrains.kotlin.idea.decompiler.textBuilder.buildDecompiledText
import org.jetbrains.kotlin.idea.decompiler.textBuilder.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleFileFacadeKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleMultifileClassKind
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.types.flexibility
import org.jetbrains.kotlin.types.isFlexible
@@ -79,30 +77,33 @@ fun buildDecompiledTextForClassFile(
classFile: VirtualFile,
resolver: ResolverForDecompiler = DeserializerForClassfileDecompiler(classFile)
): DecompiledText {
val kotlinClassHeaderInfo = IDEKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(classFile)
assert(kotlinClassHeaderInfo != null) { "Decompiled data factory shouldn't be called on an unsupported file: " + classFile }
val classId = kotlinClassHeaderInfo!!.classId
val classHeader = kotlinClassHeaderInfo.classHeader
val packageFqName = classId.packageFqName
val (classHeader, classId) = IDEKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(classFile)
?: error("Decompiled data factory shouldn't be called on an unsupported file: " + classFile)
return when {
!classHeader.version.isCompatible() -> {
DecompiledText(
INCOMPATIBLE_ABI_VERSION_COMMENT
.replace(CURRENT_ABI_VERSION_MARKER, JvmBytecodeBinaryVersion.INSTANCE.toString())
.replace(FILE_ABI_VERSION_MARKER, classHeader.version.toString()),
mapOf())
}
classHeader.isCompatibleFileFacadeKind() ->
buildDecompiledText(packageFqName, ArrayList(resolver.resolveDeclarationsInFacade(classId.asSingleFqName())), decompilerRendererForClassFiles)
classHeader.isCompatibleClassKind() ->
buildDecompiledText(packageFqName, listOfNotNull(resolver.resolveTopLevelClass(classId)), decompilerRendererForClassFiles)
classHeader.isCompatibleMultifileClassKind() -> {
if (!classHeader.metadataVersion.isCompatible()) {
return DecompiledText(
INCOMPATIBLE_ABI_VERSION_COMMENT
.replace(CURRENT_ABI_VERSION_MARKER, JvmBytecodeBinaryVersion.INSTANCE.toString())
.replace(FILE_ABI_VERSION_MARKER, classHeader.metadataVersion.toString()),
mapOf()
)
}
return when (classHeader.kind) {
KotlinClassHeader.Kind.FILE_FACADE ->
buildDecompiledText(classId.packageFqName, ArrayList(resolver.resolveDeclarationsInFacade(classId.asSingleFqName())),
decompilerRendererForClassFiles)
KotlinClassHeader.Kind.CLASS ->
buildDecompiledText(classId.packageFqName, listOfNotNull(resolver.resolveTopLevelClass(classId)),
decompilerRendererForClassFiles)
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
val partClasses = findMultifileClassParts(classFile, classId, classHeader)
val partMembers = partClasses.flatMap { partClass -> resolver.resolveDeclarationsInFacade(partClass.classId.asSingleFqName()) }
buildDecompiledText(packageFqName, partMembers, decompilerRendererForClassFiles)
val partMembers = partClasses.flatMap { partClass ->
resolver.resolveDeclarationsInFacade(partClass.classId.asSingleFqName())
}
buildDecompiledText(classId.packageFqName, partMembers, decompilerRendererForClassFiles)
}
else ->
throw UnsupportedOperationException("Unknown header kind: ${classHeader.kind} ${classHeader.version.isCompatible()}")
throw UnsupportedOperationException("Unknown header kind: $classHeader, class $classId")
}
}
@@ -30,9 +30,7 @@ import org.jetbrains.kotlin.idea.decompiler.textBuilder.LoggingErrorReporter
import org.jetbrains.kotlin.load.kotlin.AbstractBinaryClassAnnotationAndConstantLoader
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleFileFacadeKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleMultifileClassKind
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
@@ -62,12 +60,12 @@ open class KotlinClsStubBuilder : ClsStubBuilder() {
val header = kotlinClassHeaderInfo.classHeader
val classId = kotlinClassHeaderInfo.classId
val packageFqName = classId.packageFqName
if (!header.version.isCompatible()) {
if (!header.metadataVersion.isCompatible()) {
return createIncompatibleAbiVersionFileStub()
}
val components = createStubBuilderComponents(file, packageFqName)
if (header.isCompatibleMultifileClassKind()) {
if (header.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS) {
val partFiles = findMultifileClassParts(file, classId, header)
return createMultifileClassStub(header, partFiles, classId.asSingleFqName(), components)
}
@@ -82,14 +80,14 @@ open class KotlinClsStubBuilder : ClsStubBuilder() {
LOG.error("String table not found in file ${file.name}")
return null
}
return when {
header.isCompatibleClassKind() -> {
return when (header.kind) {
KotlinClassHeader.Kind.CLASS -> {
if (header.isLocalClass) return null
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(annotationData, strings)
val context = components.createContext(nameResolver, packageFqName, TypeTable(classProto.typeTable))
createTopLevelClassStub(classId, classProto, context)
}
header.isCompatibleFileFacadeKind() -> {
KotlinClassHeader.Kind.FILE_FACADE -> {
val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(annotationData, strings)
val context = components.createContext(nameResolver, packageFqName, TypeTable(packageProto.typeTable))
createFileFacadeStub(packageProto, classId.asSingleFqName(), context)
@@ -79,7 +79,7 @@ object KotlinClassFileIndex : KotlinFileIndexBase<KotlinClassFileIndex>(KotlinCl
private val INDEXER = indexer() { fileContent ->
val headerInfo = IDEKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(fileContent.file, fileContent.content)
if (headerInfo != null && headerInfo.classHeader.version.isCompatible()) headerInfo.classId.asSingleFqName() else null
if (headerInfo != null && headerInfo.classHeader.metadataVersion.isCompatible()) headerInfo.classId.asSingleFqName() else null
}
}
@@ -30,10 +30,7 @@ import org.jetbrains.kotlin.jps.build.GeneratedJvmClass
import org.jetbrains.kotlin.jps.build.KotlinBuilder
import org.jetbrains.kotlin.jps.incremental.storage.*
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleFileFacadeKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleMultifileClassKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleMultifileClassPartKind
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.name.FqName
@@ -147,8 +144,12 @@ open class IncrementalCacheImpl(
}
val header = kotlinClass.classHeader
val changesInfo = when {
header.isCompatibleFileFacadeKind() -> {
if (header.isLocalClass) {
return CompilationResult.NO_CHANGES
}
val changesInfo = when (header.kind) {
KotlinClassHeader.Kind.FILE_FACADE -> {
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
@@ -156,7 +157,7 @@ open class IncrementalCacheImpl(
constantsMap.process(kotlinClass) +
additionalProcessChangedClass(kotlinClass, isPackage = true)
}
header.isCompatibleMultifileClassKind() -> {
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
val partNames = kotlinClass.classHeader.filePartClassNames?.toList()
?: throw AssertionError("Multifile class has no parts: ${kotlinClass.className}")
multifileClassFacadeMap.add(className, partNames)
@@ -165,7 +166,7 @@ open class IncrementalCacheImpl(
constantsMap.process(kotlinClass) +
additionalProcessChangedClass(kotlinClass, isPackage = true)
}
header.isCompatibleMultifileClassPartKind() -> {
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
multifileClassPartMap.add(className.internalName, header.multifileClassName!!)
@@ -174,7 +175,7 @@ open class IncrementalCacheImpl(
constantsMap.process(kotlinClass) +
additionalProcessChangedClass(kotlinClass, isPackage = true)
}
header.isCompatibleClassKind() && !header.isLocalClass -> {
KotlinClassHeader.Kind.CLASS -> {
addToClassStorage(kotlinClass)
protoMap.process(kotlinClass, isPackage = false) +
@@ -22,9 +22,7 @@ import com.google.common.io.Files
import com.google.protobuf.ExtensionRegistry
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.jps.incremental.LocalFileKotlinClass
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleFileFacadeKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleMultifileClassPartKind
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.serialization.DebugProtoBuf
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
import org.jetbrains.kotlin.serialization.jvm.DebugJvmProtoBuf
@@ -141,14 +139,17 @@ fun classFileToString(classFile: File): String {
out.write("\n------ string table types proto -----\n${DebugJvmProtoBuf.StringTableTypes.parseDelimitedFrom(input)}")
when {
classHeader!!.isCompatibleFileFacadeKind() ->
out.write("\n------ file facade proto -----\n${DebugProtoBuf.Package.parseFrom(input, getExtensionRegistry())}")
classHeader.isCompatibleClassKind() ->
out.write("\n------ class proto -----\n${DebugProtoBuf.Class.parseFrom(input, getExtensionRegistry())}")
classHeader.isCompatibleMultifileClassPartKind() ->
out.write("\n------ multi-file part proto -----\n${DebugProtoBuf.Package.parseFrom(input, getExtensionRegistry())}")
if (!classHeader!!.metadataVersion.isCompatible()) {
error("Incompatible class ($classHeader): $classFile")
}
when (classHeader.kind) {
KotlinClassHeader.Kind.FILE_FACADE ->
out.write("\n------ file facade proto -----\n${DebugProtoBuf.Package.parseFrom(input, getExtensionRegistry())}")
KotlinClassHeader.Kind.CLASS ->
out.write("\n------ class proto -----\n${DebugProtoBuf.Class.parseFrom(input, getExtensionRegistry())}")
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART ->
out.write("\n------ multi-file part proto -----\n${DebugProtoBuf.Package.parseFrom(input, getExtensionRegistry())}")
else -> throw IllegalStateException()
}
}
@@ -20,9 +20,7 @@ import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.UsefulTestCase
import com.intellij.util.SmartList
import org.jetbrains.kotlin.jps.incremental.storage.ProtoMapValue
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleClassKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleFileFacadeKind
import org.jetbrains.kotlin.load.kotlin.header.isCompatibleMultifileClassPartKind
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtil
@@ -93,19 +91,22 @@ abstract class AbstractProtoComparisonTest : UsefulTestCase() {
val oldProtoBytes = BitEncoding.decodeBytes(oldClassHeader.annotationData!!)
val newProtoBytes = BitEncoding.decodeBytes(newClassHeader.annotationData!!)
assert(oldClassHeader.metadataVersion.isCompatible()) { "Incompatible class ($oldClassHeader): $oldClassFile" }
assert(newClassHeader.metadataVersion.isCompatible()) { "Incompatible class ($newClassHeader): $newClassFile" }
val oldProto = ProtoMapValue(
oldClassHeader.isCompatibleFileFacadeKind() || oldClassHeader.isCompatibleMultifileClassPartKind(),
oldClassHeader.kind == KotlinClassHeader.Kind.FILE_FACADE ||
oldClassHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART,
oldProtoBytes, oldClassHeader.strings!!
)
val newProto = ProtoMapValue(
newClassHeader.isCompatibleFileFacadeKind() || newClassHeader.isCompatibleMultifileClassPartKind(),
newClassHeader.kind == KotlinClassHeader.Kind.FILE_FACADE ||
newClassHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART,
newProtoBytes, newClassHeader.strings!!
)
val diff = when {
newClassHeader.isCompatibleClassKind() ||
newClassHeader.isCompatibleFileFacadeKind() ||
newClassHeader.isCompatibleMultifileClassPartKind() ->
val diff = when (newClassHeader.kind) {
KotlinClassHeader.Kind.CLASS, KotlinClassHeader.Kind.FILE_FACADE, KotlinClassHeader.Kind.MULTIFILE_CLASS_PART ->
difference(oldProto, newProto)
else -> {
println("ignore ${oldLocalFileKotlinClass.classId}")