diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt index b5cf27224d8..341d031183c 100644 --- a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt +++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector import org.jetbrains.kotlin.idea.MainFunctionDetector import org.jetbrains.kotlin.ir.IrBuiltIns +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmDescriptorMangler import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmIrLinker import org.jetbrains.kotlin.ir.builders.TranslationPluginContext @@ -44,6 +45,7 @@ import org.jetbrains.kotlin.library.metadata.KlibModuleOrigin import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator +import org.jetbrains.kotlin.psi2ir.descriptors.IrBuiltInsOverDescriptors import org.jetbrains.kotlin.psi2ir.generators.DeclarationStubGeneratorForNotFoundClasses import org.jetbrains.kotlin.psi2ir.generators.DeclarationStubGeneratorImpl import org.jetbrains.kotlin.psi2ir.generators.fragments.EvaluatorFragmentInfo @@ -60,7 +62,7 @@ open class JvmIrCodegenFactory( private val externalSymbolTable: SymbolTable? = null, private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration), private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null, - private val stubSettings: StubSettings = StubSettings(), + private val ideCodegenSettings: IdeCodegenSettings = IdeCodegenSettings(), ) : CodegenFactory { @IDEAPluginsCompatibilityAPI(IDEAPlatforms._221, message = "Please migrate to the other constructor", plugins = "Android Studio") @@ -81,15 +83,17 @@ open class JvmIrCodegenFactory( externalSymbolTable, jvmGeneratorExtensions, evaluatorFragmentInfoForPsi2Ir, - StubSettings(shouldStubAndNotLinkUnboundSymbols = shouldStubAndNotLinkUnboundSymbols), + IdeCodegenSettings(shouldStubAndNotLinkUnboundSymbols = shouldStubAndNotLinkUnboundSymbols), ) /** * @param shouldStubOrphanedExpectSymbols See [stubOrphanedExpectSymbols]. + * @param shouldDeduplicateBuiltInSymbols See [SymbolTableWithBuiltInsDeduplication]. */ - data class StubSettings( + data class IdeCodegenSettings( val shouldStubAndNotLinkUnboundSymbols: Boolean = false, val shouldStubOrphanedExpectSymbols: Boolean = false, + val shouldDeduplicateBuiltInSymbols: Boolean = false, ) data class JvmIrBackendInput( @@ -110,6 +114,7 @@ open class JvmIrCodegenFactory( val notifyCodegenStart: () -> Unit, ) : CodegenFactory.CodegenInput + @OptIn(ObsoleteDescriptorBasedAPI::class) override fun convertToIr(input: CodegenFactory.IrConversionInput): JvmIrBackendInput { val enableIdSignatures = input.configuration.getBoolean(JVMConfigurationKeys.LINK_VIA_SIGNATURES) || @@ -122,7 +127,10 @@ open class JvmIrCodegenFactory( val signaturer = if (enableIdSignatures) JvmIdSignatureDescriptor(mangler) else DisabledIdSignatureDescriptor - val symbolTable = SymbolTable(signaturer, IrFactoryImpl) + val symbolTable = when { + ideCodegenSettings.shouldDeduplicateBuiltInSymbols -> SymbolTableWithBuiltInsDeduplication(signaturer, IrFactoryImpl) + else -> SymbolTable(signaturer, IrFactoryImpl) + } mangler to symbolTable } val messageLogger = input.configuration.irMessageLogger @@ -142,6 +150,13 @@ open class JvmIrCodegenFactory( jvmGeneratorExtensions, fragmentContext = if (evaluatorFragmentInfoForPsi2Ir != null) FragmentContext() else null, ) + + // Built-ins deduplication must be enabled immediately so that there is no chance for duplicate built-in symbols to occur. For + // example, the creation of `IrPluginContextImpl` might already lead to duplicate built-in symbols via `BuiltinSymbolsBase`. + if (symbolTable is SymbolTableWithBuiltInsDeduplication) { + (psi2irContext.irBuiltIns as? IrBuiltInsOverDescriptors)?.let { symbolTable.bindIrBuiltIns(it) } + } + val pluginExtensions = IrGenerationExtension.getInstances(input.project) val stubGenerator = @@ -215,8 +230,7 @@ open class JvmIrCodegenFactory( irLinker.deserializeIrModuleHeader(it, kotlinLibrary, _moduleName = it.name.asString()) } - - val irProviders = if (stubSettings.shouldStubAndNotLinkUnboundSymbols) { + val irProviders = if (ideCodegenSettings.shouldStubAndNotLinkUnboundSymbols) { listOf(stubGenerator) } else { val stubGeneratorForMissingClasses = DeclarationStubGeneratorForNotFoundClasses(stubGenerator) @@ -240,7 +254,7 @@ open class JvmIrCodegenFactory( // We need to compile all files we reference in Klibs irModuleFragment.files.addAll(dependencies.flatMap { it.files }) - if (stubSettings.shouldStubOrphanedExpectSymbols) { + if (ideCodegenSettings.shouldStubOrphanedExpectSymbols) { irModuleFragment.stubOrphanedExpectSymbols(stubGenerator) } diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/SymbolTableWithBuiltInsDeduplication.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/SymbolTableWithBuiltInsDeduplication.kt new file mode 100644 index 00000000000..d248fc4e3e7 --- /dev/null +++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/SymbolTableWithBuiltInsDeduplication.kt @@ -0,0 +1,67 @@ +/* + * 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.jvm + +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.containingPackage +import org.jetbrains.kotlin.ir.IrBuiltIns +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.declarations.IrFactory +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.util.IdSignatureComposer +import org.jetbrains.kotlin.ir.util.SymbolTable +import org.jetbrains.kotlin.psi2ir.descriptors.IrBuiltInsOverDescriptors + +/** + * A [SymbolTable] that de-duplicates built-ins for which multiple descriptor instances exist. + * + * For example, if multiple descriptors exist for the built-in `Boolean` type, the ordinary [SymbolTable] generates multiple + * [IrClassSymbol]s, one for each descriptor instance. If the wrong `Boolean` class symbol is encountered in `JvmSharedVariableManager`, + * an exception will occur (see KTIJ-24335). + * + * This class instead relies on the symbols declared in [IrBuiltIns]. Any descriptors which reference a built-in by name will be resolved + * to that built-in, regardless of whether that descriptor's instance matches with the descriptor of the built-in. + */ +@OptIn(ObsoleteDescriptorBasedAPI::class) +class SymbolTableWithBuiltInsDeduplication( + signaturer: IdSignatureComposer, + irFactory: IrFactory, +) : SymbolTable(signaturer, irFactory) { + /** + * As long as [IrBuiltIns] aren't bound, the symbol table will operate like [SymbolTable], as it must be assumed that built-ins are + * still being created. + */ + private var irBuiltIns: IrBuiltInsOverDescriptors? = null + + fun bindIrBuiltIns(irBuiltIns: IrBuiltInsOverDescriptors) { + if (this.irBuiltIns == null) { + this.irBuiltIns = irBuiltIns + } else { + throw IllegalStateException("`irBuiltIns` have already been bound.") + } + } + + @ObsoleteDescriptorBasedAPI + override fun referenceClass(descriptor: ClassDescriptor): IrClassSymbol { + val irBuiltIns = this.irBuiltIns ?: return super.referenceClass(descriptor) + + // We need to find out whether `descriptor` is possibly a built-in symbol before it's actually retrieved to break recursion as + // `irBuiltIns.findClass` uses `referenceClass` recursively. + val builtInDescriptor = irBuiltIns.findBuiltInClassDescriptor(descriptor) + if (builtInDescriptor != null) { + // We need to delegate to the supertype implementation here to break recursion. `findBuiltInClassDescriptor` will return + // `descriptor` even if `descriptor` was found via `findBuiltInClassDescriptor`. + return super.referenceClass(builtInDescriptor) + } + + return super.referenceClass(descriptor) + } + + private fun IrBuiltInsOverDescriptors.findBuiltInClassDescriptor(descriptor: ClassDescriptor): ClassDescriptor? { + val packageFqName = descriptor.containingPackage() ?: return null + return findClassDescriptor(descriptor.name, packageFqName) + } +} diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/descriptors/IrBuiltInsOverDescriptors.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/descriptors/IrBuiltInsOverDescriptors.kt index 6829b66311b..d0f5e4df069 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/descriptors/IrBuiltInsOverDescriptors.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/descriptors/IrBuiltInsOverDescriptors.kt @@ -21,7 +21,10 @@ import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.declarations.addConstructor import org.jetbrains.kotlin.ir.builders.declarations.buildClass -import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrConstructor +import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment +import org.jetbrains.kotlin.ir.declarations.IrFactory import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl import org.jetbrains.kotlin.ir.descriptors.* import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl @@ -486,10 +489,13 @@ class IrBuiltInsOverDescriptors( ) as? ClassDescriptor)?.let { symbolTable.referenceClass(it) } override fun findClass(name: Name, packageFqName: FqName): IrClassSymbol? = - (builtIns.builtInsModule.getPackage(packageFqName).memberScope.getContributedClassifier( + findClassDescriptor(name, packageFqName)?.let { symbolTable.referenceClass(it) } + + fun findClassDescriptor(name: Name, packageFqName: FqName): ClassDescriptor? = + builtIns.builtInsModule.getPackage(packageFqName).memberScope.getContributedClassifier( name, NoLookupLocation.FROM_BACKEND - ) as? ClassDescriptor)?.let { symbolTable.referenceClass(it) } + ) as? ClassDescriptor override fun findBuiltInClassMemberFunctions(builtInClass: IrClassSymbol, name: Name): Iterable = builtInClass.descriptor.unsubstitutedMemberScope