[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
This commit is contained in:
Marco Pennekamp
2023-01-19 21:18:59 +01:00
committed by Space Team
parent ba2e716682
commit f11ea92601
3 changed files with 97 additions and 10 deletions
@@ -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)
}
@@ -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)
}
}
@@ -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<IrSimpleFunctionSymbol> =
builtInClass.descriptor.unsubstitutedMemberScope