[klib] Implement diagnostics for clashing KLIB signatures

Now, we detect clashing signatures during serialization to KLIB and
report a compiler error if two or more declarations have the same
`IdSignature`

For example, for the following code:
```kotlin
@Deprecated("", level = DeprecationLevel.HIDDEN)
fun foo(): String = ""

fun foo(): Int = 0
```

the compiler will produce this diagnostic:
```
e: main.kt:1:1 Platform declaration clash: The following declarations
       have the same KLIB signature (/foo|foo(){}[0]):
    fun foo(): String defined in root package
    fun foo(): Int defined in root package
e: main.kt:4:1 Platform declaration clash: The following declarations
       have the same KLIB signature (/foo|foo(){}[0]):
    fun foo(): String defined in root package
    fun foo(): Int defined in root package
```

Note that we report this diagnostic during serialization and not earlier
(e.g., in fir2ir) for more robustness, so ensure that we check
exactly the signatures that will be written to a KLIB.
If we later introduce some annotation for customizing a declaration's
signature (e.g., for preserving binary compatibility), this
diagnostic will continue to work as expected.

^KT-63670 Fixed
This commit is contained in:
Sergej Jaskiewicz
2023-12-15 20:05:02 +01:00
committed by Space Team
parent e0cb145c6b
commit eda30ff704
27 changed files with 366 additions and 98 deletions
@@ -631,7 +631,7 @@ fun serializeModuleIntoKlib(
val compatibilityMode = CompatibilityMode(abiVersion)
val sourceBaseDirs = configuration[CommonConfigurationKeys.KLIB_RELATIVE_PATH_BASES] ?: emptyList()
val absolutePathNormalization = configuration[CommonConfigurationKeys.KLIB_NORMALIZE_ABSOLUTE_PATH] ?: false
val signatureClashChecks = configuration[CommonConfigurationKeys.PRODUCE_KLIB_SIGNATURES_CLASH_CHECKS] ?: false
val signatureClashChecks = configuration[CommonConfigurationKeys.PRODUCE_KLIB_SIGNATURES_CLASH_CHECKS] ?: true
val moduleExportedNames = moduleFragment.collectExportedNames()
@@ -643,6 +643,7 @@ fun serializeModuleIntoKlib(
val serializedIr =
JsIrModuleSerializer(
irDiagnosticReporter,
messageLogger,
moduleFragment.irBuiltins,
compatibilityMode,
@@ -650,8 +651,7 @@ fun serializeModuleIntoKlib(
sourceBaseDirs = sourceBaseDirs,
configuration.languageVersionSettings,
signatureClashChecks,
jsIrFileMetadataFactory = { JsIrFileMetadata(moduleExportedNames[it]?.values?.toSmartList() ?: emptyList()) }
).serializedIrModule(moduleFragment)
) { JsIrFileMetadata(moduleExportedNames[it]?.values?.toSmartList() ?: emptyList()) }.serializedIrModule(moduleFragment)
val moduleDescriptor = moduleFragment.descriptor
@@ -6,36 +6,9 @@
package org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir
import org.jetbrains.kotlin.backend.common.serialization.GlobalDeclarationTable
import org.jetbrains.kotlin.backend.common.serialization.IdSignatureClashTracker
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.util.IdSignature
import org.jetbrains.kotlin.ir.util.render
class JsUniqIdClashTracker() : IdSignatureClashTracker {
private val committedIdSignatures = mutableMapOf<IdSignature, IrDeclaration>()
override fun commit(declaration: IrDeclaration, signature: IdSignature) {
if (!signature.isPubliclyVisible) return // don't track local ids
if (signature in committedIdSignatures) {
val clashedDeclaration = committedIdSignatures[signature]!!
val parent = declaration.parent
val clashedParent = clashedDeclaration.parent
if (declaration !is IrTypeParameter || parent !is IrSimpleFunction || clashedParent !is IrSimpleFunction || parent.correspondingPropertySymbol !== clashedParent.correspondingPropertySymbol) {
// TODO: handle clashes properly
error("IdSignature clash: $signature; Existed declaration ${clashedDeclaration.render()} clashed with new ${declaration.render()}")
}
}
committedIdSignatures[signature] = declaration
}
}
class JsGlobalDeclarationTable(builtIns: IrBuiltIns, tracker: IdSignatureClashTracker = JsUniqIdClashTracker()) :
GlobalDeclarationTable(JsManglerIr, tracker) {
class JsGlobalDeclarationTable(builtIns: IrBuiltIns) : GlobalDeclarationTable(JsManglerIr) {
init {
loadKnownBuiltins(builtIns)
}
@@ -7,14 +7,16 @@ package org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir
import org.jetbrains.kotlin.backend.common.serialization.CompatibilityMode
import org.jetbrains.kotlin.backend.common.serialization.DeclarationTable
import org.jetbrains.kotlin.backend.common.serialization.IdSignatureClashTracker
import org.jetbrains.kotlin.backend.common.serialization.IrModuleSerializer
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.IrDiagnosticReporter
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.util.IrMessageLogger
class JsIrModuleSerializer(
diagnosticReporter: IrDiagnosticReporter,
messageLogger: IrMessageLogger,
irBuiltIns: IrBuiltIns,
compatibilityMode: CompatibilityMode,
@@ -22,13 +24,17 @@ class JsIrModuleSerializer(
sourceBaseDirs: Collection<String>,
private val languageVersionSettings: LanguageVersionSettings,
shouldCheckSignaturesOnUniqueness: Boolean = true,
private val jsIrFileMetadataFactory: JsIrFileMetadataFactory = JsIrFileEmptyMetadataFactory
) : IrModuleSerializer<JsIrFileSerializer>(messageLogger, compatibilityMode, normalizeAbsolutePaths, sourceBaseDirs) {
private val jsIrFileMetadataFactory: JsIrFileMetadataFactory = JsIrFileEmptyMetadataFactory,
) : IrModuleSerializer<JsIrFileSerializer>(
diagnosticReporter,
messageLogger,
compatibilityMode,
normalizeAbsolutePaths,
sourceBaseDirs,
shouldCheckSignaturesOnUniqueness,
) {
private val globalDeclarationTable = JsGlobalDeclarationTable(
irBuiltIns,
if (shouldCheckSignaturesOnUniqueness) JsUniqIdClashTracker() else IdSignatureClashTracker.DEFAULT_TRACKER
)
override val globalDeclarationTable = JsGlobalDeclarationTable(irBuiltIns)
override fun createSerializerForFile(file: IrFile): JsIrFileSerializer =
JsIrFileSerializer(