[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
@@ -10,6 +10,7 @@ dependencies {
api(project(":kotlin-util-klib-metadata"))
api(project(":compiler:util"))
implementation(project(":compiler:psi"))
implementation(project(":compiler:frontend.common-psi"))
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
compileOnly(commonDependency("org.jetbrains.intellij.deps.fastutil:intellij-deps-fastutil"))
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2023 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.common.diagnostics
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.util.IdSignature
internal data class ConflictingKlibSignaturesData(val signature: IdSignature, val declarations: Collection<IrDeclaration>)
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2023 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.common.diagnostics
import org.jetbrains.kotlin.ir.IrDiagnosticReporter
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.linkage.SignatureClashDetector
import org.jetbrains.kotlin.ir.util.IdSignature
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.parentClassOrNull
class IdSignatureClashDetector : SignatureClashDetector<IdSignature, IrDeclaration>() {
override fun reportSignatureConflict(
signature: IdSignature,
declarations: Collection<IrDeclaration>,
diagnosticReporter: IrDiagnosticReporter
) {
reportSignatureClashTo(
diagnosticReporter,
SerializationErrors.CONFLICTING_KLIB_SIGNATURES_ERROR,
declarations,
ConflictingKlibSignaturesData(signature, declarations),
reportOnIfSynthetic = { it.parentClassOrNull ?: it.file },
)
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2023 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.common.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.diagnostics.SerializationDiagnosticRenderers.CONFLICTING_KLIB_SIGNATURES_DATA
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.error1
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers
import org.jetbrains.kotlin.diagnostics.rendering.Renderer
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.MemberComparator
internal object SerializationErrors {
val CONFLICTING_KLIB_SIGNATURES_ERROR by error1<PsiElement, ConflictingKlibSignaturesData>()
init {
RootDiagnosticRendererFactory.registerFactory(KtDefaultSerializationErrorMessages)
}
}
internal object KtDefaultSerializationErrorMessages : BaseDiagnosticRendererFactory() {
override val MAP = KtDiagnosticFactoryToRendererMap("KT").also { map ->
map.put(
SerializationErrors.CONFLICTING_KLIB_SIGNATURES_ERROR,
"Platform declaration clash: {0}",
CONFLICTING_KLIB_SIGNATURES_DATA,
)
}
}
internal object SerializationDiagnosticRenderers {
val CONFLICTING_KLIB_SIGNATURES_DATA =
CommonRenderers.renderConflictingSignatureData<DeclarationDescriptor, ConflictingKlibSignaturesData>(
signatureKind = "KLIB",
sortUsing = MemberComparator.INSTANCE,
declarationRenderer = Renderer {
DescriptorRenderer.WITHOUT_MODIFIERS.render(it)
},
renderSignature = { append(it.signature.render()) },
declarations = { it.declarations.map(IrDeclaration::toIrBasedDescriptor) },
)
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.common.serialization
import org.jetbrains.kotlin.backend.common.diagnostics.IdSignatureClashDetector
import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureFactory
import org.jetbrains.kotlin.backend.common.serialization.signature.PublicIdSignatureComputer
import org.jetbrains.kotlin.ir.IrBuiltIns
@@ -15,37 +16,24 @@ import org.jetbrains.kotlin.ir.util.IdSignature
import org.jetbrains.kotlin.ir.util.KotlinMangler
import org.jetbrains.kotlin.ir.util.render
interface IdSignatureClashTracker {
fun commit(declaration: IrDeclaration, signature: IdSignature)
companion object {
val DEFAULT_TRACKER = object : IdSignatureClashTracker {
override fun commit(declaration: IrDeclaration, signature: IdSignature) {}
}
}
}
abstract class GlobalDeclarationTable(
private val mangler: KotlinMangler.IrMangler,
private val clashTracker: IdSignatureClashTracker
) {
abstract class GlobalDeclarationTable(private val mangler: KotlinMangler.IrMangler) {
val publicIdSignatureComputer = PublicIdSignatureComputer(mangler)
internal val clashDetector = IdSignatureClashDetector()
protected val table = hashMapOf<IrDeclaration, IdSignature>()
constructor(mangler: KotlinMangler.IrMangler) : this(mangler, IdSignatureClashTracker.DEFAULT_TRACKER)
protected fun loadKnownBuiltins(builtIns: IrBuiltIns) {
builtIns.knownBuiltins.forEach {
val symbol = (it as IrSymbolOwner).symbol
table[it] = symbol.signature!!.also { id -> clashTracker.commit(it, id) }
table[it] = symbol.signature!!.also { id -> clashDetector.trackDeclaration(it, id) }
}
}
open fun computeSignatureByDeclaration(declaration: IrDeclaration, compatibleMode: Boolean): IdSignature {
return table.getOrPut(declaration) {
publicIdSignatureComputer.composePublicIdSignature(declaration, compatibleMode).also { clashTracker.commit(declaration, it) }
publicIdSignatureComputer.composePublicIdSignature(declaration, compatibleMode).also {
clashDetector.trackDeclaration(declaration, it)
}
}
}
@@ -6,14 +6,23 @@
package org.jetbrains.kotlin.backend.common.serialization
import org.jetbrains.kotlin.builtins.FunctionInterfacePackageFragment
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.ir.IrDiagnosticReporter
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.library.SerializedIrFile
import org.jetbrains.kotlin.library.SerializedIrModule
abstract class IrModuleSerializer<F : IrFileSerializer>(protected val messageLogger: IrMessageLogger, protected val compatibilityMode: CompatibilityMode, protected val normalizeAbsolutePaths: Boolean, protected val sourceBaseDirs: Collection<String>) {
abstract fun createSerializerForFile(file: IrFile): F
abstract class IrModuleSerializer<Serializer : IrFileSerializer>(
protected val diagnosticReporter: IrDiagnosticReporter,
protected val messageLogger: IrMessageLogger,
protected val compatibilityMode: CompatibilityMode,
protected val normalizeAbsolutePaths: Boolean,
protected val sourceBaseDirs: Collection<String>,
private val shouldCheckSignaturesOnUniqueness: Boolean,
) {
abstract fun createSerializerForFile(file: IrFile): Serializer
/**
* Allows to skip [file] during serialization.
@@ -23,6 +32,8 @@ abstract class IrModuleSerializer<F : IrFileSerializer>(protected val messageLog
protected open fun backendSpecificFileFilter(file: IrFile): Boolean =
true
protected abstract val globalDeclarationTable: GlobalDeclarationTable
private fun serializeIrFile(file: IrFile): SerializedIrFile {
val fileSerializer = createSerializerForFile(file)
return fileSerializer.serializeIrFile(file)
@@ -33,6 +44,9 @@ abstract class IrModuleSerializer<F : IrFileSerializer>(protected val messageLog
.filter { it.packageFragmentDescriptor !is FunctionInterfacePackageFragment }
.filter(this::backendSpecificFileFilter)
.map(this::serializeIrFile)
if (shouldCheckSignaturesOnUniqueness) {
globalDeclarationTable.clashDetector.reportErrorsTo(diagnosticReporter)
}
return SerializedIrModule(serializedFiles)
}
}