Fix: Don't index and decompile incompatible .knm files from K/N KLIBs

Issue #KT-34159
This commit is contained in:
Dmitriy Dolovov
2019-10-18 14:34:18 +07:00
parent 9c887c1e79
commit 8ffc58a77d
4 changed files with 69 additions and 27 deletions
@@ -41,12 +41,12 @@ fun createLoggingErrorReporter(log: Logger) = LoggingErrorReporter(log)
internal object CachingIdeKonanLibraryMetadataLoader : PackageAccessHandler { internal object CachingIdeKonanLibraryMetadataLoader : PackageAccessHandler {
override fun loadModuleHeader(library: KotlinLibrary): KlibMetadataProtoBuf.Header { override fun loadModuleHeader(library: KotlinLibrary): KlibMetadataProtoBuf.Header {
val virtualFile = getVirtualFile(library, library.moduleHeaderFile) 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 { override fun loadPackageFragment(library: KotlinLibrary, packageFqName: String, partName: String): ProtoBuf.PackageFragment {
val virtualFile = getVirtualFile(library, library.packageFragmentFile(packageFqName, partName)) val virtualFile = getVirtualFile(library, library.packageFragmentFile(packageFqName, partName))
return cache.getCachedPackageFragment(virtualFile) return cache.getCachedPackageFragment(virtualFile)!!
} }
private fun getVirtualFile(library: KotlinLibrary, file: KFile): VirtualFile = private fun getVirtualFile(library: KotlinLibrary, file: KFile): VirtualFile =
@@ -9,12 +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.library.KLIB_METADATA_FILE_EXTENSION import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.KLIB_MODULE_METADATA_FILE_NAME
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 java.io.IOException
import java.util.*
class KotlinNativeLoadingMetadataCache : BaseComponent { class KotlinNativeLoadingMetadataCache : BaseComponent {
@@ -33,34 +34,72 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
constructor(virtualFile: VirtualFile) : this(virtualFile.url, virtualFile.modificationStamp) constructor(virtualFile: VirtualFile) : this(virtualFile.url, virtualFile.modificationStamp)
} }
private val packageFragmentCache = createConcurrentWeakValueMap<CacheKey, ProtoBuf.PackageFragment>() // ConcurrentWeakValueHashMap does not allow null values.
private val moduleHeaderCache = createConcurrentWeakValueMap<CacheKey, KlibMetadataProtoBuf.Header>() private class CacheValue<T : Any>(val value: T?)
fun getCachedPackageFragment(virtualFile: VirtualFile): ProtoBuf.PackageFragment = private val packageFragmentCache = createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>()
packageFragmentCache.computeIfAbsent(CacheKey(virtualFile.ensurePackageMetadataFile)) { private val moduleHeaderCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>()
virtualFile.createPackageFragmentCacheEntry private val libraryVersioningCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KonanLibraryVersioning>>()
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 = return packageFragmentCache.computeIfAbsent(CacheKey(packageFragmentFile)) {
moduleHeaderCache.computeIfAbsent(CacheKey(virtualFile.ensureModuleHeaderFile)) { CacheValue(computePackageFragment(packageFragmentFile))
virtualFile.createModuleHeaderCacheEntry }.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 return moduleHeaderCache.computeIfAbsent(CacheKey(moduleHeaderFile)) {
get() = parsePackageFragment(contentsToByteArray(false)) CacheValue(computeModuleHeader(moduleHeaderFile))
}.value
}
private val VirtualFile.createModuleHeaderCacheEntry private fun isAbiCompatible(libraryRoot: VirtualFile): Boolean {
get() = parseModuleHeader(contentsToByteArray(false)) val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false
private val VirtualFile.ensurePackageMetadataFile val versioning = libraryVersioningCache.computeIfAbsent(CacheKey(manifestFile)) {
get() = if (isPackageMetadataFile) this else error("Not a package metadata file: $this") CacheValue(computeLibraryVersioning(manifestFile))
}.value
private val VirtualFile.isPackageMetadataFile return versioning?.abiVersion == KotlinAbiVersion.CURRENT
get() = extension == KLIB_METADATA_FILE_EXTENSION }
private val VirtualFile.ensureModuleHeaderFile private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
get() = if (isModuleHeaderFile) this else error("Not a module header file: $this") if (!isAbiCompatible(packageFragmentFile.parent.parent.parent))
return null
private val VirtualFile.isModuleHeaderFile return try {
get() = name == KLIB_MODULE_METADATA_FILE_NAME 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
}
} }
@@ -22,8 +22,8 @@ class KotlinNativeMetadataDecompiler : KotlinNativeMetadataDecompilerBase<Kotlin
) { ) {
override fun doReadFile(file: VirtualFile): FileWithMetadata? { override fun doReadFile(file: VirtualFile): FileWithMetadata? {
val proto = KotlinNativeLoadingMetadataCache.getInstance().getCachedPackageFragment(file) val fragment = KotlinNativeLoadingMetadataCache.getInstance().getCachedPackageFragment(file) ?: return null
return FileWithMetadata.Compatible(proto, KlibMetadataSerializerProtocol) //todo: check version compatibility return FileWithMetadata.Compatible(fragment, KlibMetadataSerializerProtocol) //todo: check version compatibility
} }
} }
@@ -28,6 +28,9 @@ object KotlinNativeMetaFileIndex : KotlinFileIndexBase<KotlinNativeMetaFileIndex
/*todo: check version?!*/ /*todo: check version?!*/
private val INDEXER = indexer { fileContent -> private val INDEXER = indexer { fileContent ->
val fragment = KotlinNativeLoadingMetadataCache.getInstance().getCachedPackageFragment(fileContent.file) val fragment = KotlinNativeLoadingMetadataCache.getInstance().getCachedPackageFragment(fileContent.file)
FqName(fragment.getExtension(KlibMetadataProtoBuf.fqName)) if (fragment != null)
FqName(fragment.getExtension(KlibMetadataProtoBuf.fqName))
else
null
} }
} }