[IR] Enhance error reporting for IR linking issues

^KT-44626

Typical use case:
- There are two KLIB libraries: A and B.
- Library A has two versions: A.v1 (older) and A.v2 (newer).
- A.v2 is ABI-incompatible with A.v1.
- B depends on A and was compiled against A.v1.
- An attempt to build the application with A.v2 and B fails with weird error message. It's unclear for end user what's wrong and what needs to be done to fix the issue.

The fix improves error reporting for the following particular cases:
- A symbol that is gone (KT-41378)
- A class that became a typealias (KT-47285, KT-46697)
- A typealias that became a class (KT-46340)
This commit is contained in:
Dmitriy Dolovov
2021-06-10 11:40:16 +03:00
parent 672b972b38
commit c1fb40a436
15 changed files with 1046 additions and 81 deletions
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.backend.konan
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport
import org.jetbrains.kotlin.backend.konan.serialization.KonanUserVisibleIrModulesSupport
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
@@ -91,6 +93,15 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
val resolvedLibraries get() = resolve.resolvedLibraries
internal val userVisibleIrModulesSupport = KonanUserVisibleIrModulesSupport(
externalDependenciesLoader = UserVisibleIrModulesSupport.ExternalDependenciesLoader.from(
externalDependenciesFile = configuration.get(KonanConfigKeys.EXTERNAL_DEPENDENCIES)?.let(::File),
onMalformedExternalDependencies = { warningMessage ->
configuration.report(CompilerMessageSeverity.STRONG_WARNING, warningMessage)
}),
konanKlibDir = File(distribution.klib)
)
internal val cacheSupport = CacheSupport(configuration, resolvedLibraries, target, produce)
internal val cachedLibraries: CachedLibraries
@@ -206,10 +217,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
File(distribution.defaultNatives(target)).child("exceptionsSupport.bc").absolutePath
internal val nativeLibraries: List<String> =
configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES)
configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES)
internal val includeBinaries: List<String> =
configuration.getList(KonanConfigKeys.INCLUDED_BINARY_FILES)
configuration.getList(KonanConfigKeys.INCLUDED_BINARY_FILES)
internal val languageVersionSettings =
configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS)!!
@@ -101,7 +101,8 @@ internal fun Context.psiToIr(
stubGenerator,
irProviderForCEnumsAndCStructs,
exportedDependencies,
config.cachedLibraries
config.cachedLibraries,
config.userVisibleIrModulesSupport
).also { linker ->
// context.config.librariesWithDependencies could change at each iteration.
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.backend.common.overrides.FakeOverrideBuilder
import org.jetbrains.kotlin.backend.common.overrides.FakeOverrideClassFilter
import org.jetbrains.kotlin.backend.common.serialization.*
import org.jetbrains.kotlin.backend.common.serialization.encodings.BinarySymbolData
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport
import org.jetbrains.kotlin.backend.konan.CachedLibraries
import org.jetbrains.kotlin.backend.konan.descriptors.isInteropLibrary
import org.jetbrains.kotlin.backend.konan.ir.interop.IrProviderForCEnumAndCStructStubs
@@ -72,7 +73,8 @@ internal class KonanIrLinker(
private val stubGenerator: DeclarationStubGenerator,
private val cenumsProvider: IrProviderForCEnumAndCStructStubs,
exportedDependencies: List<ModuleDescriptor>,
private val cachedLibraries: CachedLibraries
private val cachedLibraries: CachedLibraries,
override val userVisibleIrModulesSupport: UserVisibleIrModulesSupport
) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, exportedDependencies) {
companion object {
@@ -204,7 +206,7 @@ internal class KonanIrLinker(
val descriptor = resolveDescriptor(idSig)
val actualModule = descriptor.module
if (actualModule !== moduleDescriptor) {
val moduleDeserializer = deserializersForModules[actualModule.name.asString()] ?: error("No module deserializer for $actualModule")
val moduleDeserializer = resolveModuleDeserializer(actualModule, idSig)
moduleDeserializer.addModuleReachableTopLevel(idSig)
return symbolTable.referenceClassFromLinker(idSig)
}
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan.serialization
import org.jetbrains.kotlin.backend.common.serialization.IrModuleDeserializer
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.KONAN_PLATFORM_LIBS_NAME_PREFIX
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.RequiredUnresolvedLibrary
import org.jetbrains.kotlin.library.unresolvedDependencies
import org.jetbrains.kotlin.utils.ResolvedDependency
import org.jetbrains.kotlin.utils.ResolvedDependencyArtifactPath
import org.jetbrains.kotlin.utils.ResolvedDependencyId
import org.jetbrains.kotlin.utils.ResolvedDependencyVersion
class KonanUserVisibleIrModulesSupport(
externalDependenciesLoader: ExternalDependenciesLoader,
private val konanKlibDir: File
) : UserVisibleIrModulesSupport(externalDependenciesLoader) {
override fun getUserVisibleModules(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
return compressedModules(deserializers)
}
override fun modulesFromDeserializers(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
val moduleToCompilerVersion: MutableMap<ResolvedDependencyId, ResolvedDependencyVersion> = mutableMapOf()
val kotlinNativeBundledLibraries: MutableSet<ResolvedDependencyId> = mutableSetOf()
// Transform deserializers to [ModuleWithUninitializedDependencies]s.
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer ->
val library: KotlinLibrary = deserializer.kotlinLibrary ?: return@mapNotNull null
val compilerVersion: ResolvedDependencyVersion = library.compilerVersion
val isDefaultLibrary = library.isDefaultLibrary
// For default libraries the version is the same as the version of the compiler.
// Note: Empty string means missing (unknown) version.
val libraryVersion: ResolvedDependencyVersion = if (isDefaultLibrary) compilerVersion else ResolvedDependencyVersion.EMPTY
val moduleId = getUserVisibleModuleId(deserializer)
val module = ResolvedDependency(
id = moduleId,
selectedVersion = libraryVersion,
requestedVersionsByIncomingDependencies = mutableMapOf(), // To be initialized in a separate pass below.
artifactPaths = mutableSetOf(ResolvedDependencyArtifactPath(library.libraryFile.absolutePath))
)
moduleToCompilerVersion[moduleId] = compilerVersion
if (isDefaultLibrary) kotlinNativeBundledLibraries += moduleId
// Don't rely on dependencies in IrModuleDeserializer. In Kotlin/Native each module depends on all other modules,
// and this contradicts with the real module dependencies as written in KLIB manifest files.
val outgoingDependencyIds = library.unresolvedDependencies.map { it.moduleId }
moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds)
}.toMap()
// Stamp dependencies.
return modules.mapValues { (moduleId, moduleWithUninitializedDependencies) ->
val (module, outgoingDependencyIds) = moduleWithUninitializedDependencies
outgoingDependencyIds.forEach { outgoingDependencyId ->
val outgoingDependencyModule = modules.getValue(outgoingDependencyId).module
if (outgoingDependencyId in kotlinNativeBundledLibraries) {
outgoingDependencyModule.requestedVersionsByIncomingDependencies[moduleId] = moduleToCompilerVersion.getValue(moduleId)
} else {
outgoingDependencyModule.requestedVersionsByIncomingDependencies[moduleId] = outgoingDependencyModule.selectedVersion
}
}
module
}
}
/**
* This is an optimization to avoid displaying 100+ Kotlin/Native platform libraries to the user.
* Instead, lets compress them into a single row and avoid excessive output.
*/
private fun compressedModules(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
val compressedModules: MutableMap<ResolvedDependencyId, ResolvedDependency> = mergedModules(deserializers)
var platformLibrariesVersion: ResolvedDependencyVersion? = null // Must be the same version to succeed.
val platformLibraries: MutableList<ResolvedDependency> = mutableListOf() // All platform libraries to be patched.
val outgoingDependencyIds: MutableSet<ResolvedDependencyId> = mutableSetOf() // All outgoing dependencies from platform libraries.
for ((moduleId, module) in compressedModules) {
if (moduleId.isKonanPlatformLibrary) {
if (ResolvedDependencyId.SOURCE_CODE_MODULE_ID !in module.requestedVersionsByIncomingDependencies) {
continue
}
platformLibrariesVersion = when (platformLibrariesVersion) {
null, module.selectedVersion -> module.selectedVersion
else -> {
// Multiple versions of platform libs. Give up.
return compressedModules
}
}
platformLibraries += module
} else {
module.requestedVersionsByIncomingDependencies.keys.forEach { incomingDependencyId ->
if (incomingDependencyId.isKonanPlatformLibrary) {
outgoingDependencyIds += moduleId
}
}
}
}
if (platformLibraries.isNotEmpty()) {
platformLibraries.forEach { it.visibleAsFirstLevelDependency = false }
val compressedModuleId = ResolvedDependencyId("$KONAN_PLATFORM_LIBS_NAME_PREFIX* (${platformLibraries.size} libraries)")
val compressedModule = ResolvedDependency(
id = compressedModuleId,
selectedVersion = platformLibrariesVersion!!,
requestedVersionsByIncomingDependencies = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to platformLibrariesVersion),
artifactPaths = mutableSetOf()
)
outgoingDependencyIds.forEach { outgoingDependencyId ->
val outgoingDependency = compressedModules.getValue(outgoingDependencyId)
outgoingDependency.requestedVersionsByIncomingDependencies[compressedModuleId] = compressedModule.selectedVersion
}
compressedModules[compressedModuleId] = compressedModule
}
return compressedModules
}
private val KotlinLibrary.compilerVersion: ResolvedDependencyVersion
get() = ResolvedDependencyVersion(versions.compilerVersion.orEmpty())
// This is much safer check then KotlinLibrary.isDefault, which may return false even for "stdlib" when
// Kotlin/Native compiler is running with "-nostdlib", "-no-endorsed-libs", "-no-default-libs" arguments.
private val KotlinLibrary.isDefaultLibrary: Boolean
get() = libraryFile.startsWith(konanKlibDir)
override val ResolvedDependencyId.isKotlinLibrary: Boolean
get() = uniqueNames.any { uniqueName -> uniqueName == KONAN_STDLIB_NAME || uniqueName.startsWith(KOTLIN_LIBRARY_PREFIX) }
companion object {
private val RequiredUnresolvedLibrary.moduleId: ResolvedDependencyId
get() = ResolvedDependencyId(path) // Yep, it's named "path" but in fact holds unique name of the library.
private val ResolvedDependencyId.isKonanPlatformLibrary: Boolean
get() = uniqueNames.any { it.startsWith(KONAN_PLATFORM_LIBS_NAME_PREFIX) }
}
}