From f11ea92601823b8af8a786425d73d3e36bbde5b2 Mon Sep 17 00:00:00 2001 From: Marco Pennekamp Date: Thu, 19 Jan 2023 21:18:59 +0100 Subject: [PATCH] [JVM IR] KTIJ-24335 Deduplicate class symbols of duplicate built-ins - When the IR backend is invoked from the bytecode tool window, multiple descriptors may exist of the same built-in. For example, there may be multiple descriptors for `Boolean`. - This caused issues when the `IrClassifierSymbol` of a type was used as a key for some built-ins map (such as `primitiveRefProviders` in `JvmSharedVariablesManager`). Because while the reference map is built from predeclared built-ins, with duplicate descriptors, multiple `IrClassifierSymbol`s may exist for the same type. Such a duplicated symbol would lead to an exception. - The solution invents a special symbol table which de-duplicates built-ins at the point where `IrClassSymbol`s are created. I tried other approaches: - It's possible to fix the symptoms in `JvmSharedVariablesManager` and `JvmSymbols` by turning the keys of the requisite maps, i.e. `primitiveRefProviders` and `arraysCopyOfFunctions`, from `IrClassifierSymbol` to something like a `Name` or `PrimitiveType`. This however touches the IR backend beyond the IDE and still leaves error potential for all the other possibly duplicated built-ins. - I tried to handle built-ins in `DeclarationStubGenerator` specially, to circumvent the issue of duplicated descriptors, but this is not a solution because the duplicated `IrClassSymbol`s will already be part of the IR at that point. ^KTIJ-24335 fixed --- .../kotlin/backend/jvm/JvmIrCodegenFactory.kt | 28 ++++++-- .../SymbolTableWithBuiltInsDeduplication.kt | 67 +++++++++++++++++++ .../descriptors/IrBuiltInsOverDescriptors.kt | 12 +++- 3 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/SymbolTableWithBuiltInsDeduplication.kt 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