[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:
committed by
Space Team
parent
ba2e716682
commit
f11ea92601
+21
-7
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+67
@@ -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)
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user