[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:
committed by
Space Team
parent
e0cb145c6b
commit
eda30ff704
@@ -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"))
|
||||
|
||||
|
||||
+11
@@ -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>)
|
||||
+29
@@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
+52
@@ -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) },
|
||||
)
|
||||
}
|
||||
+7
-19
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user