From 8ffc58a77d0a103e9aa11bb8518970ce57cf7bb1 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Fri, 18 Oct 2019 14:34:18 +0700 Subject: [PATCH] Fix: Don't index and decompile incompatible .knm files from K/N KLIBs Issue #KT-34159 --- .../ide/konan/KotlinNativePluginUtil.kt | 4 +- .../KotlinNativeLoadingMetadataCache.kt | 83 ++++++++++++++----- .../KotlinNativeMetadataDecompiler.kt | 4 +- .../konan/index/KotlinNativeMetaFileIndex.kt | 5 +- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativePluginUtil.kt b/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativePluginUtil.kt index 1203886a368..f1f012faf1a 100644 --- a/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativePluginUtil.kt +++ b/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativePluginUtil.kt @@ -41,12 +41,12 @@ fun createLoggingErrorReporter(log: Logger) = LoggingErrorReporter(log) internal object CachingIdeKonanLibraryMetadataLoader : PackageAccessHandler { override fun loadModuleHeader(library: KotlinLibrary): KlibMetadataProtoBuf.Header { val virtualFile = getVirtualFile(library, library.moduleHeaderFile) - return cache.getCachedModuleHeader(virtualFile) + return cache.getCachedModuleHeader(virtualFile)!! } override fun loadPackageFragment(library: KotlinLibrary, packageFqName: String, partName: String): ProtoBuf.PackageFragment { val virtualFile = getVirtualFile(library, library.packageFragmentFile(packageFqName, partName)) - return cache.getCachedPackageFragment(virtualFile) + return cache.getCachedPackageFragment(virtualFile)!! } private fun getVirtualFile(library: KotlinLibrary, file: KFile): VirtualFile = diff --git a/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeLoadingMetadataCache.kt b/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeLoadingMetadataCache.kt index daa951915ac..f02321ce6fb 100644 --- a/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeLoadingMetadataCache.kt +++ b/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeLoadingMetadataCache.kt @@ -9,12 +9,13 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.BaseComponent import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.containers.ContainerUtil.createConcurrentWeakValueMap -import org.jetbrains.kotlin.library.KLIB_METADATA_FILE_EXTENSION -import org.jetbrains.kotlin.library.KLIB_MODULE_METADATA_FILE_NAME +import org.jetbrains.kotlin.library.* import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf import org.jetbrains.kotlin.library.metadata.parseModuleHeader import org.jetbrains.kotlin.library.metadata.parsePackageFragment import org.jetbrains.kotlin.metadata.ProtoBuf +import java.io.IOException +import java.util.* class KotlinNativeLoadingMetadataCache : BaseComponent { @@ -33,34 +34,72 @@ class KotlinNativeLoadingMetadataCache : BaseComponent { constructor(virtualFile: VirtualFile) : this(virtualFile.url, virtualFile.modificationStamp) } - private val packageFragmentCache = createConcurrentWeakValueMap() - private val moduleHeaderCache = createConcurrentWeakValueMap() + // ConcurrentWeakValueHashMap does not allow null values. + private class CacheValue(val value: T?) - fun getCachedPackageFragment(virtualFile: VirtualFile): ProtoBuf.PackageFragment = - packageFragmentCache.computeIfAbsent(CacheKey(virtualFile.ensurePackageMetadataFile)) { - virtualFile.createPackageFragmentCacheEntry + private val packageFragmentCache = createConcurrentWeakValueMap>() + private val moduleHeaderCache = createConcurrentWeakValueMap>() + private val libraryVersioningCache = createConcurrentWeakValueMap>() + + fun getCachedPackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? { + check(packageFragmentFile.extension == KLIB_METADATA_FILE_EXTENSION) { + "Not a package metadata file: $packageFragmentFile" } - fun getCachedModuleHeader(virtualFile: VirtualFile): KlibMetadataProtoBuf.Header = - moduleHeaderCache.computeIfAbsent(CacheKey(virtualFile.ensureModuleHeaderFile)) { - virtualFile.createModuleHeaderCacheEntry + return packageFragmentCache.computeIfAbsent(CacheKey(packageFragmentFile)) { + CacheValue(computePackageFragment(packageFragmentFile)) + }.value + } + + fun getCachedModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? { + check(moduleHeaderFile.name == KLIB_MODULE_METADATA_FILE_NAME) { + "Not a module header file: $moduleHeaderFile" } - private val VirtualFile.createPackageFragmentCacheEntry - get() = parsePackageFragment(contentsToByteArray(false)) + return moduleHeaderCache.computeIfAbsent(CacheKey(moduleHeaderFile)) { + CacheValue(computeModuleHeader(moduleHeaderFile)) + }.value + } - private val VirtualFile.createModuleHeaderCacheEntry - get() = parseModuleHeader(contentsToByteArray(false)) + private fun isAbiCompatible(libraryRoot: VirtualFile): Boolean { + val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false - private val VirtualFile.ensurePackageMetadataFile - get() = if (isPackageMetadataFile) this else error("Not a package metadata file: $this") + val versioning = libraryVersioningCache.computeIfAbsent(CacheKey(manifestFile)) { + CacheValue(computeLibraryVersioning(manifestFile)) + }.value - private val VirtualFile.isPackageMetadataFile - get() = extension == KLIB_METADATA_FILE_EXTENSION + return versioning?.abiVersion == KotlinAbiVersion.CURRENT + } - private val VirtualFile.ensureModuleHeaderFile - get() = if (isModuleHeaderFile) this else error("Not a module header file: $this") + private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? { + if (!isAbiCompatible(packageFragmentFile.parent.parent.parent)) + return null - private val VirtualFile.isModuleHeaderFile - get() = name == KLIB_MODULE_METADATA_FILE_NAME + return try { + parsePackageFragment(packageFragmentFile.contentsToByteArray(false)) + } catch (_: IOException) { + null + } + } + + private fun computeModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? { + if (!isAbiCompatible(moduleHeaderFile.parent.parent)) + return null + + return try { + parseModuleHeader(moduleHeaderFile.contentsToByteArray(false)) + } catch (_: IOException) { + null + } + } + + private fun computeLibraryVersioning(manifestFile: VirtualFile): KonanLibraryVersioning? = try { + Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning() + } catch (_: IOException) { + // ignore and cache null value + null + } catch (_: IllegalArgumentException) { + // ignore and cache null value + null + } } diff --git a/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeMetadataDecompiler.kt b/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeMetadataDecompiler.kt index d9637095256..0db360ec810 100644 --- a/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeMetadataDecompiler.kt +++ b/idea/idea-native/src/org/jetbrains/kotlin/ide/konan/decompiler/KotlinNativeMetadataDecompiler.kt @@ -22,8 +22,8 @@ class KotlinNativeMetadataDecompiler : KotlinNativeMetadataDecompilerBase val fragment = KotlinNativeLoadingMetadataCache.getInstance().getCachedPackageFragment(fileContent.file) - FqName(fragment.getExtension(KlibMetadataProtoBuf.fqName)) + if (fragment != null) + FqName(fragment.getExtension(KlibMetadataProtoBuf.fqName)) + else + null } }