Fix: Use more precise KLIB compatibility check (by metadata version)

Issue #KT-34811
This commit is contained in:
Dmitriy Dolovov
2019-12-12 14:49:38 +03:00
parent 34e1ab4e7a
commit deb4025987
5 changed files with 55 additions and 39 deletions
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.library.KLIB_PROPERTY_METADATA_VERSION
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
class KlibMetadataVersion(major: Int, minor: Int, patch: Int) : BinaryVersion(major, minor, patch) { class KlibMetadataVersion(vararg numbers: Int) : BinaryVersion(*numbers) {
override fun isCompatible(): Boolean = isCompatibleTo(INSTANCE) override fun isCompatible(): Boolean = isCompatibleTo(INSTANCE)
@@ -18,19 +18,13 @@ class KlibMetadataVersion(major: Int, minor: Int, patch: Int) : BinaryVersion(ma
val INSTANCE = KlibMetadataVersion(1, 0, 0) val INSTANCE = KlibMetadataVersion(1, 0, 0)
@JvmField @JvmField
val INVALID_VERSION = KlibMetadataVersion(-1, -1, -1) val INVALID_VERSION = KlibMetadataVersion()
} }
} }
fun KlibMetadataVersion(vararg values: Int): KlibMetadataVersion { val KotlinLibrary.metadataVersion: KlibMetadataVersion?
if (values.size != 3) error("Metadata version should be in major.minor.patch format: $values")
return KlibMetadataVersion(values[0], values[1], values[2])
}
val KotlinLibrary.metadataVersion: KlibMetadataVersion
get() { get() {
val versionString = manifestProperties.getProperty(KLIB_PROPERTY_METADATA_VERSION) val versionString = manifestProperties.getProperty(KLIB_PROPERTY_METADATA_VERSION) ?: return null
val versionIntArray = BinaryVersion.parseVersionArray(versionString) val versionIntArray = BinaryVersion.parseVersionArray(versionString) ?: return null
?: error("Could not parse metadata version: $versionString")
return KlibMetadataVersion(*versionIntArray) return KlibMetadataVersion(*versionIntArray)
} }
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider import org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.kotlin.library.KotlinAbiVersion
/** TODO: merge [KotlinNativeABICompatibilityChecker] in the future with [UnsupportedAbiVersionNotificationPanelProvider], KT-34525 */ /** TODO: merge [KotlinNativeABICompatibilityChecker] in the future with [UnsupportedAbiVersionNotificationPanelProvider], KT-34525 */
class KotlinNativeABICompatibilityChecker(private val project: Project) : ProjectComponent, Disposable { class KotlinNativeABICompatibilityChecker(private val project: Project) : ProjectComponent, Disposable {
@@ -84,7 +83,7 @@ class KotlinNativeABICompatibilityChecker(private val project: Project) : Projec
private fun getLibrariesToNotifyAbout(): Map<String, NativeLibraryInfo> = synchronized(this) { private fun getLibrariesToNotifyAbout(): Map<String, NativeLibraryInfo> = synchronized(this) {
val incompatibleLibraries = getModuleInfosFromIdeaModel(project).asSequence() val incompatibleLibraries = getModuleInfosFromIdeaModel(project).asSequence()
.filterIsInstance<NativeLibraryInfo>() .filterIsInstance<NativeLibraryInfo>()
.filter { it.safeAbiVersion != KotlinAbiVersion.CURRENT } .filter { !it.metadataInfo.isCompatible }
.associateBy { it.libraryRoot } .associateBy { it.libraryRoot }
val newEntries = if (cachedIncompatibleLibraries.isNotEmpty()) val newEntries = if (cachedIncompatibleLibraries.isNotEmpty())
@@ -104,23 +103,23 @@ class KotlinNativeABICompatibilityChecker(private val project: Project) : Projec
val librariesByGroups = HashMap<Pair<LibraryGroup, Boolean>, MutableList<Pair<String, String>>>() val librariesByGroups = HashMap<Pair<LibraryGroup, Boolean>, MutableList<Pair<String, String>>>()
librariesToNotify.forEach { (libraryRoot, libraryInfo) -> librariesToNotify.forEach { (libraryRoot, libraryInfo) ->
val isOldAbi = (libraryInfo.safeAbiVersion?.version ?: 0) < KotlinAbiVersion.CURRENT.version val isOldMetadata = (libraryInfo.metadataInfo as? NativeLibraryInfo.MetadataInfo.Incompatible)?.isOlder ?: true
val (libraryName, libraryGroup) = parseIDELibraryName(libraryInfo) val (libraryName, libraryGroup) = parseIDELibraryName(libraryInfo)
librariesByGroups.computeIfAbsent(libraryGroup to isOldAbi) { mutableListOf() } += libraryName to libraryRoot librariesByGroups.computeIfAbsent(libraryGroup to isOldMetadata) { mutableListOf() } += libraryName to libraryRoot
} }
return librariesByGroups.keys.sortedWith( return librariesByGroups.keys.sortedWith(
compareBy( compareBy(
{ (libraryGroup, _) -> libraryGroup }, { (libraryGroup, _) -> libraryGroup },
{ (_, isOldAbi) -> isOldAbi } { (_, isOldMetadata) -> isOldMetadata }
) )
).map { key -> ).map { key ->
val (libraryGroup, isOldAbi) = key val (libraryGroup, isOldMetadata) = key
val libraries = val libraries =
librariesByGroups.getValue(key).sortedWith(compareBy(LIBRARY_NAME_COMPARATOR) { (libraryName, _) -> libraryName }) librariesByGroups.getValue(key).sortedWith(compareBy(LIBRARY_NAME_COMPARATOR) { (libraryName, _) -> libraryName })
val compilerVersionText = if (isOldAbi) "an older" else "a newer" val compilerVersionText = if (isOldMetadata) "an older" else "a newer"
val message = when (libraryGroup) { val message = when (libraryGroup) {
is LibraryGroup.FromDistribution -> { is LibraryGroup.FromDistribution -> {
@@ -16,6 +16,8 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.PlatformAnalysisParameters import org.jetbrains.kotlin.analyzer.PlatformAnalysisParameters
import org.jetbrains.kotlin.analyzer.ResolverForModuleFactory import org.jetbrains.kotlin.analyzer.ResolverForModuleFactory
import org.jetbrains.kotlin.analyzer.getCapability import org.jetbrains.kotlin.analyzer.getCapability
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.metadataVersion
import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.functions.functionInterfacePackageFragmentProvider import org.jetbrains.kotlin.builtins.functions.functionInterfacePackageFragmentProvider
@@ -27,10 +29,10 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.KlibModuleOrigin import org.jetbrains.kotlin.descriptors.konan.KlibModuleOrigin
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.Companion.safeAbiVersion import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.Companion.safeMetadataVersion
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.Companion.isCompatible import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.MetadataInfo.Compatible
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.MetadataInfo.Incompatible
import org.jetbrains.kotlin.ide.konan.analyzer.NativeResolverForModuleFactory import org.jetbrains.kotlin.ide.konan.analyzer.NativeResolverForModuleFactory
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.LibraryInfo import org.jetbrains.kotlin.idea.caches.project.LibraryInfo
import org.jetbrains.kotlin.idea.caches.project.SdkInfo import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.project.lazyClosure import org.jetbrains.kotlin.idea.caches.project.lazyClosure
@@ -57,7 +59,7 @@ fun KotlinLibrary.createPackageFragmentProvider(
moduleDescriptor: ModuleDescriptor moduleDescriptor: ModuleDescriptor
): PackageFragmentProvider? { ): PackageFragmentProvider? {
if (!safeAbiVersion.isCompatible) return null if (safeMetadataVersion?.isCompatible() != true) return null
val libraryProto = CachingIdeKonanLibraryMetadataLoader.loadModuleHeader(this) val libraryProto = CachingIdeKonanLibraryMetadataLoader.loadModuleHeader(this)
@@ -187,14 +189,33 @@ private fun createKotlinNativeBuiltIns(moduleInfo: ModuleInfo, projectContext: P
private fun ModuleInfo.findNativeStdlib(): NativeLibraryInfo? = private fun ModuleInfo.findNativeStdlib(): NativeLibraryInfo? =
dependencies().lazyClosure { it.dependencies() } dependencies().lazyClosure { it.dependencies() }
.filterIsInstance<NativeLibraryInfo>() .filterIsInstance<NativeLibraryInfo>()
.firstOrNull { it.isStdlib && it.safeAbiVersion.isCompatible } .firstOrNull { it.isStdlib && it.metadataInfo.isCompatible }
class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: String) : LibraryInfo(project, library) { class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: String) : LibraryInfo(project, library) {
sealed class MetadataInfo {
abstract val isCompatible: Boolean
object Compatible : MetadataInfo() {
override val isCompatible get() = true
}
class Incompatible(val isOlder: Boolean) : MetadataInfo() {
override val isCompatible get() = false
}
}
private val nativeLibrary = createKotlinLibrary(File(libraryRoot)) private val nativeLibrary = createKotlinLibrary(File(libraryRoot))
val isStdlib get() = libraryRoot.endsWith(KONAN_STDLIB_NAME) val isStdlib get() = libraryRoot.endsWith(KONAN_STDLIB_NAME)
val safeAbiVersion get() = nativeLibrary.safeAbiVersion val metadataInfo by lazy {
val metadataVersion = nativeLibrary.safeMetadataVersion
when {
metadataVersion == null -> Incompatible(true) // too old KLIB format, even doesn't have metadata version
!metadataVersion.isCompatible() -> Incompatible(!metadataVersion.isAtLeast(KlibMetadataVersion.INSTANCE))
else -> Compatible
}
}
override fun getLibraryRoots() = listOf(libraryRoot) override fun getLibraryRoots() = listOf(libraryRoot)
@@ -215,8 +236,7 @@ class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: Str
companion object { companion object {
val NATIVE_LIBRARY_CAPABILITY = ModuleDescriptor.Capability<KotlinLibrary>("KotlinNativeLibrary") val NATIVE_LIBRARY_CAPABILITY = ModuleDescriptor.Capability<KotlinLibrary>("KotlinNativeLibrary")
internal val KotlinLibrary.safeAbiVersion get() = this.readSafe(null) { versions.abiVersion } internal val KotlinLibrary.safeMetadataVersion get() = this.readSafe(null) { metadataVersion }
internal val KotlinAbiVersion?.isCompatible get() = this == KotlinAbiVersion.CURRENT
private fun <T> KotlinLibrary.readSafe(defaultValue: T, action: KotlinLibrary.() -> T) = try { private fun <T> KotlinLibrary.readSafe(defaultValue: T, action: KotlinLibrary.() -> T) = try {
action() action()
@@ -9,11 +9,13 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.BaseComponent import com.intellij.openapi.components.BaseComponent
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ContainerUtil.createConcurrentWeakValueMap import com.intellij.util.containers.ContainerUtil.createConcurrentWeakValueMap
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.library.* import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.library.metadata.parseModuleHeader import org.jetbrains.kotlin.library.metadata.parseModuleHeader
import org.jetbrains.kotlin.library.metadata.parsePackageFragment import org.jetbrains.kotlin.library.metadata.parsePackageFragment
import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@@ -39,7 +41,7 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
private val packageFragmentCache = createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>() private val packageFragmentCache = createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>()
private val moduleHeaderCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>() private val moduleHeaderCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>()
private val libraryVersioningCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KotlinLibraryVersioning>>() private val libraryMetadataVersionCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataVersion>>()
fun getCachedPackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? { fun getCachedPackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
check(packageFragmentFile.extension == KLIB_METADATA_FILE_EXTENSION) { check(packageFragmentFile.extension == KLIB_METADATA_FILE_EXTENSION) {
@@ -61,18 +63,18 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
}.value }.value
} }
private fun isAbiCompatible(libraryRoot: VirtualFile): Boolean { private fun isMetadataCompatible(libraryRoot: VirtualFile): Boolean {
val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false
val versioning = libraryVersioningCache.computeIfAbsent(CacheKey(manifestFile)) { val metadataVersion = libraryMetadataVersionCache.computeIfAbsent(CacheKey(manifestFile)) {
CacheValue(computeLibraryVersioning(manifestFile)) CacheValue(computeLibraryMetadataVersion(manifestFile))
}.value }.value ?: return false
return versioning?.abiVersion == KotlinAbiVersion.CURRENT return metadataVersion.isCompatible()
} }
private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? { private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
if (!isAbiCompatible(packageFragmentFile.parent.parent.parent)) if (!isMetadataCompatible(packageFragmentFile.parent.parent.parent))
return null return null
return try { return try {
@@ -83,7 +85,7 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
} }
private fun computeModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? { private fun computeModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? {
if (!isAbiCompatible(moduleHeaderFile.parent.parent)) if (!isMetadataCompatible(moduleHeaderFile.parent.parent))
return null return null
return try { return try {
@@ -93,8 +95,9 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
} }
} }
private fun computeLibraryVersioning(manifestFile: VirtualFile): KotlinLibraryVersioning? = try { private fun computeLibraryMetadataVersion(manifestFile: VirtualFile): KlibMetadataVersion? = try {
Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning() val versioning = Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning()
versioning.metadataVersion?.let(BinaryVersion.Companion::parseVersionArray)?.let(::KlibMetadataVersion)
} catch (_: IOException) { } catch (_: IOException) {
// ignore and cache null value // ignore and cache null value
null null
@@ -179,10 +179,10 @@ private fun loadLibrary(location: File): KotlinLibrary {
printErrorAndExit("library does not have metadata version specified in manifest: $location") printErrorAndExit("library does not have metadata version specified in manifest: $location")
val metadataVersion = library.metadataVersion val metadataVersion = library.metadataVersion
if (!metadataVersion.isCompatible()) if (metadataVersion?.isCompatible() != true)
printErrorAndExit( printErrorAndExit(
""" """
library has incompatible metadata version $metadataVersion: $location, library has incompatible metadata version ${metadataVersion ?: "\"unknown\""}: $location,
please make sure that all libraries passed to commonizer compatible metadata version ${KlibMetadataVersion.INSTANCE} please make sure that all libraries passed to commonizer compatible metadata version ${KlibMetadataVersion.INSTANCE}
""".trimIndent() """.trimIndent()
) )