[KLIB] Support partially linked klibs in Ir linker

Now it's possible to compile against klib even if in there are
references to declarations from other klibs which are no longer existed

 - add switch flag to turn on/off that mode
 - pass switch flag from JS CLI to JS Linker
This commit is contained in:
Roman Artemev
2021-11-16 13:52:22 +03:00
committed by TeamCityServer
parent 650b04b4dd
commit 53a65f818f
5 changed files with 444 additions and 15 deletions
@@ -81,7 +81,7 @@ class FakeOverrideBuilder(
// TODO: The declaration table is needed for the signaturer.
// private val fakeOverrideDeclarationTable = FakeOverrideDeclarationTable(mangler, signatureSerializerFactory)
private val fakeOverrideCandidates = mutableMapOf<IrClass, CompatibilityMode>()
val fakeOverrideCandidates = mutableMapOf<IrClass, CompatibilityMode>()
fun enqueueClass(clazz: IrClass, signature: IdSignature, compatibilityMode: CompatibilityMode) {
fakeOverrideDeclarationTable.assumeDeclarationSignature(clazz, signature)
fakeOverrideCandidates[clazz] = compatibilityMode
@@ -14,12 +14,17 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.builders.TranslationPluginContext
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.linkage.IrDeserializer
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.IrErrorType
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.name.Name
@@ -31,6 +36,7 @@ abstract class KotlinIrLinker(
val builtIns: IrBuiltIns,
val symbolTable: SymbolTable,
private val exportedDependencies: List<ModuleDescriptor>,
private val allowUnboundSymbols: Boolean = false
) : IrDeserializer, FileLocalAwareLinker {
// Kotlin-MPP related data. Consider some refactoring
@@ -57,12 +63,26 @@ abstract class KotlinIrLinker(
idSignature: IdSignature,
moduleDeserializer: IrModuleDeserializer
): IrModuleDeserializer {
throw SignatureIdNotFoundInModuleWithDependencies(
idSignature = idSignature,
problemModuleDeserializer = moduleDeserializer,
allModuleDeserializers = deserializersForModules.values,
userVisibleIrModulesSupport = userVisibleIrModulesSupport
).raiseIssue(messageLogger)
if (allowUnboundSymbols) {
return object : IrModuleDeserializer(null, KotlinAbiVersion.CURRENT) {
override fun contains(idSig: IdSignature): Boolean = false
override fun deserializeIrSymbol(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): IrSymbol {
return referenceDeserializedSymbol(symbolTable, null, symbolKind, idSig)
}
override val moduleFragment: IrModuleFragment
get() = TODO("Not yet implemented")
override val moduleDependencies: Collection<IrModuleDeserializer> get() = emptyList()
}
} else {
throw SignatureIdNotFoundInModuleWithDependencies(
idSignature = idSignature,
problemModuleDeserializer = moduleDeserializer,
allModuleDeserializers = deserializersForModules.values,
userVisibleIrModulesSupport = userVisibleIrModulesSupport
).raiseIssue(messageLogger)
}
}
fun resolveModuleDeserializer(module: ModuleDescriptor, idSignature: IdSignature?): IrModuleDeserializer {
@@ -145,7 +165,9 @@ abstract class KotlinIrLinker(
?: tryResolveCustomDeclaration(symbol)
?: return null
} catch (e: IrSymbolTypeMismatchException) {
throw SymbolTypeMismatch(e, deserializersForModules.values, userVisibleIrModulesSupport).raiseIssue(messageLogger)
if (!allowUnboundSymbols) {
throw SymbolTypeMismatch(e, deserializersForModules.values, userVisibleIrModulesSupport).raiseIssue(messageLogger)
}
}
}
@@ -192,11 +214,96 @@ abstract class KotlinIrLinker(
deserializersForModules.values.forEach { it.init() }
}
private fun markUnlinkedClassifiers(): Set<IrClassifierSymbol> {
if (!allowUnboundSymbols) return emptySet()
val entries = fakeOverrideBuilder.fakeOverrideCandidates
val result = mutableSetOf<IrClassifierSymbol>()
fun IrType.isUnlinked(visited: MutableSet<IrClassifierSymbol>): Boolean {
val simpleType = this as? IrSimpleType ?: return this !is IrErrorType
val classifier = simpleType.classifier
if (!classifier.isBound) {
return true
}
if (classifier in result) {
return true
}
if (!visited.add(classifier)) return false
val superTypes = when (val decl = classifier.owner) {
is IrClass -> decl.superTypes
is IrTypeParameter -> decl.superTypes
else -> emptyList()
}
if (superTypes.any { it.isUnlinked(visited) }) {
result.add(classifier)
return true
}
for (ta in simpleType.arguments) {
if (ta is IrTypeProjection) {
val projected = ta.type
if (projected.isUnlinked(visited)) {
result.add(classifier)
return true
}
}
}
return false
}
fun IrClass.isUnlinked(visited: MutableSet<IrClassifierSymbol>): Boolean {
if (symbol in result) return true
for (s in superTypes) {
if (s.isUnlinked(visited)) {
result.add(symbol)
return true
}
}
return false
}
val toRemove = mutableListOf<IrClass>()
for (e in entries) {
val klass = e.key
if (klass.isUnlinked(mutableSetOf())) {
toRemove.add(klass)
}
}
toRemove.forEach { entries.remove(it) }
return result
}
private fun applyToModules(transformer: IrElementTransformerVoid) {
deserializersForModules.values.forEach { it.moduleFragment.transformChildrenVoid(transformer) }
}
override fun postProcess() {
finalizeExpectActualLinker()
val unlinkedClassifiers = markUnlinkedClassifiers()
fakeOverrideBuilder.provideFakeOverrides()
triedToDeserializeDeclarationForSymbol.clear()
if (allowUnboundSymbols) {
val t = UnlinkedDeclarationsProcessor(builtIns, unlinkedClassifiers, messageLogger)
t.addLinkageErrorIntoUnlinkedClasses()
applyToModules(t.signatureTransformer())
applyToModules(t.usageTransformer())
}
// TODO: fix IrPluginContext to make it not produce additional external reference
// symbolTable.noUnboundLeft("unbound after fake overrides:")
}
@@ -0,0 +1,317 @@
/*
* Copyright 2010-2021 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.serialization
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl
import org.jetbrains.kotlin.ir.types.IrErrorType
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.impl.IrErrorTypeImpl
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.ir.util.fileOrNull
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.types.Variance
internal class UnlinkedDeclarationsProcessor(
private val builtIns: IrBuiltIns,
private val unlinkedClassifiers: Set<IrClassifierSymbol>,
private val messageLogger: IrMessageLogger
) {
companion object {
val errorOrigin = object : IrStatementOriginImpl("LINKAGE ERROR") {}
}
fun addLinkageErrorIntoUnlinkedClasses() {
for (u in unlinkedClassifiers) {
if (u is IrClassSymbol) {
val klass = u.owner
val anonInitializer = klass.declarations.firstOrNull { it is IrAnonymousInitializer } as IrAnonymousInitializer? ?: run {
builtIns.irFactory.createAnonymousInitializer(
klass.startOffset,
klass.endOffset,
IrDeclarationOrigin.DEFINED,
IrAnonymousInitializerSymbolImpl()
).also {
it.body = builtIns.irFactory.createBlockBody(klass.startOffset, klass.endOffset)
it.parent = klass
klass.declarations.add(it)
}
}
anonInitializer.body.statements.clear()
klass.reportWarning("Class", klass.fqNameForIrSerialization.asString())
anonInitializer.body.statements.add(klass.throwLinkageError(klass.symbol.signature?.render()))
klass.superTypes = klass.superTypes.filter { !it.isUnlinked() }
}
}
}
private val errorType = IrErrorTypeImpl(null, emptyList(), Variance.INVARIANT)
private fun IrDeclaration.location(): IrMessageLogger.Location? = fileOrNull?.location(startOffset)
private fun IrFile.location(offset: Int): IrMessageLogger.Location {
val module = module.name
val fileEntry = fileEntry
val fileName = fileEntry.name
val lineNumber = fileEntry.getLineNumber(offset) + 1 // since humans count from 1, not 0
val columnNumber = fileEntry.getColumnNumber(offset) + 1
// unsure whether should module name be added here
return IrMessageLogger.Location("$module @ $fileName", lineNumber, columnNumber)
}
private fun IrDeclaration.reportWarning(kind: String, fqn: String) {
reportWarning("$kind declaration $fqn contains unlinked symbols", location())
}
private fun reportWarning(message: String, location: IrMessageLogger.Location?) {
messageLogger.report(IrMessageLogger.Severity.WARNING, message, location)
}
fun signatureTransformer(): IrElementTransformerVoid = SignatureTransformer()
private inner class SignatureTransformer : IrElementTransformerVoid() {
override fun visitFunction(declaration: IrFunction): IrStatement {
var linked = true
fun IrValueParameter.fixType() {
if (type.isUnlinked()) {
linked = false
type = errorType
defaultValue = null
}
varargElementType?.let {
if (it.isUnlinked()) {
varargElementType = errorType
}
}
}
declaration.dispatchReceiverParameter?.fixType()
declaration.extensionReceiverParameter?.fixType()
declaration.valueParameters.forEach { it.fixType() }
if (declaration.returnType.isUnlinked()) {
linked = false
declaration.returnType = errorType
}
declaration.typeParameters.forEach {
if (it.superTypes.any { s -> s.isUnlinked() }) {
linked = false
it.superTypes = listOf(errorType)
}
}
if (linked) {
declaration.transformChildrenVoid()
} else {
declaration.reportWarning("Function", declaration.fqNameForIrSerialization.asString())
declaration.body?.let { body ->
val bb = (body as IrBlockBody)
bb.statements.clear()
bb.statements.add(declaration.throwLinkageError("Unlinked type in function signature"))
}
}
return declaration
}
override fun visitField(declaration: IrField): IrStatement {
if (declaration.type.isUnlinked()) {
val fqn = declaration.correspondingPropertySymbol?.owner?.fqNameWhenAvailable ?: declaration.fqNameForIrSerialization
val kind = if (declaration.correspondingPropertySymbol != null) "Property" else "Field"
declaration.reportWarning(kind, fqn.asString())
declaration.type = errorType
declaration.initializer = null
} else {
declaration.transformChildrenVoid()
}
return declaration
}
}
private fun IrSymbol.isUnlinked(): Boolean {
if (!isBound) return true
when (this) {
is IrClassifierSymbol -> this.isUnlinked()
is IrPropertySymbol -> {
owner.getter?.let { if (it.symbol.isUnlinked()) return true }
owner.setter?.let { if (it.symbol.isUnlinked()) return true }
owner.backingField?.let { return it.symbol.isUnlinked() }
}
is IrFunctionSymbol -> return isUnlinked()
}
return false
}
private fun IrClassifierSymbol.isUnlinked(): Boolean = !isBound || this in unlinkedClassifiers
private fun IrType.isUnlinked(): Boolean {
val simpleType = this as? IrSimpleType ?: return false
if (simpleType.classifier.isUnlinked()) return true
return simpleType.arguments.any { it is IrTypeProjection && it.type.isUnlinked() }
}
private fun IrFieldSymbol.isUnlinked(): Boolean {
return owner.type is IrErrorType
}
private fun IrFunctionSymbol.isUnlinked(): Boolean {
val function = owner
if (function.returnType is IrErrorType) return true
if (function.dispatchReceiverParameter?.type is IrErrorType) return true
if (function.extensionReceiverParameter?.type is IrErrorType) return true
if (function.valueParameters.any { it.type is IrErrorType }) return true
if (function.typeParameters.any { tp -> tp.superTypes.any { st -> st is IrErrorType } }) return true
return false
}
private fun IrElement.throwLinkageError(message: String?): IrCall {
return IrCallImpl(startOffset, endOffset, builtIns.nothingType, builtIns.linkageErrorSymbol, 0, 1, errorOrigin).also { call ->
val messageLiteral = "Linkage error of symbol: ${message ?: ""}"
call.putValueArgument(0, IrConstImpl.string(startOffset, endOffset, builtIns.stringType, messageLiteral))
}
}
fun usageTransformer(): IrElementTransformerVoid = UsageTransformer()
private inner class UsageTransformer : IrElementTransformerVoid() {
private var currentFile: IrFile? = null
override fun visitFile(declaration: IrFile): IrFile {
currentFile = declaration
return super.visitFile(declaration).also { currentFile = null }
}
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
expression.transformChildrenVoid()
val checkType = expression.typeOperandClassifier
if (!checkType.isUnlinked()) return expression
reportWarning(
"TypeOperator contains unlinked symbol ${checkType.signature?.render() ?: ""}",
currentFile?.location(expression.startOffset)
)
val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType)
composite.statements.add(expression.argument)
composite.statements.add(expression.throwLinkageError(expression.typeOperandClassifier.signature?.render()))
return composite
}
override fun visitExpression(expression: IrExpression): IrExpression {
if (expression.type.isUnlinked() || expression.type is IrErrorType) {
reportWarning("Expression type contains unlinked symbol", currentFile?.location(expression.startOffset))
return expression.throwLinkageError("Unlinked type of expression")
}
return super.visitExpression(expression)
}
override fun visitMemberAccess(expression: IrMemberAccessExpression<*>): IrExpression {
expression.transformChildrenVoid()
val symbol = expression.symbol
if (!symbol.isUnlinked()) return expression
reportWarning(
"Accessing declaration contains unlinked symbol ${symbol.signature?.render() ?: ""}",
currentFile?.location(expression.startOffset)
)
val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin)
expression.dispatchReceiver?.let { composite.statements.add(it) }
expression.extensionReceiver?.let { composite.statements.add(it) }
for (i in 0 until expression.valueArgumentsCount) {
val arg = expression.getValueArgument(i)
arg?.let { composite.statements.add(it) }
}
val message =
if (!symbol.isBound) "Unlinked symbol: ${symbol.signature?.render()}" else "Unlinked type in signature of ${symbol.signature?.render()}"
composite.statements.add(expression.throwLinkageError(message))
return composite
}
override fun visitFieldAccess(expression: IrFieldAccessExpression): IrExpression {
expression.transformChildrenVoid()
val symbol = expression.symbol
if (!symbol.isUnlinked()) {
return expression
}
reportWarning(
"Accessing field contains unlinked symbol ${symbol.signature?.render() ?: ""}",
currentFile?.location(expression.startOffset)
)
val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin)
expression.receiver?.let { composite.statements.add(it) }
if (expression is IrSetField) {
composite.statements.add(expression.value)
}
val message = if (!symbol.isBound) "Unlinked symbol: ${symbol.signature?.render()}" else "Field type is unlinked"
composite.statements.add(expression.throwLinkageError(message))
return composite
}
override fun visitClassReference(expression: IrClassReference): IrExpression {
if (expression.symbol.isUnlinked()) {
val signRender = expression.symbol.signature?.render()
reportWarning(
"Accessing class contains unlinked symbol ${signRender ?: ""}",
currentFile?.location(expression.startOffset)
)
return expression.throwLinkageError(signRender)
}
return expression
}
override fun visitClass(declaration: IrClass): IrStatement {
declaration.transformChildrenVoid()
fun <S : IrSymbol> IrOverridableDeclaration<S>.filterOverriddens() {
overriddenSymbols = overriddenSymbols.filter { it.isBound }
}
for (member in declaration.declarations) {
when (member) {
is IrSimpleFunction -> member.filterOverriddens()
is IrProperty -> {
member.filterOverriddens()
member.getter?.filterOverriddens()
member.setter?.filterOverriddens()
}
}
}
return declaration
}
}
}
@@ -412,6 +412,8 @@ fun getIrModuleInfoForKlib(
val typeTranslator = TypeTranslatorImpl(symbolTable, configuration.languageVersionSettings, moduleDescriptor)
val irBuiltIns = IrBuiltInsOverDescriptors(moduleDescriptor.builtIns, typeTranslator, symbolTable)
val allowUnboundSymbols = configuration[JSConfigurationKeys.PARTIAL_LINKAGE] ?: false
val irLinker =
JsIrLinker(
null,
@@ -421,7 +423,8 @@ fun getIrModuleInfoForKlib(
null,
null,
loweredIcData,
friendModules
friendModules,
allowUnboundSymbols
)
val deserializedModuleFragmentsToLib = deserializeDependencies(sortedDependencies, irLinker, mainModuleLib, filesToLoad, mapping)
@@ -479,7 +482,8 @@ fun getIrModuleInfoForSourceFiles(
feContext,
null,
loweredIcData,
friendModules
friendModules,
allowUnboundSymbols
)
val deserializedModuleFragmentsToLib = deserializeDependencies(allSortedDependencies, irLinker, null,null, mapping)
val deserializedModuleFragments = deserializedModuleFragmentsToLib.keys.toList()
@@ -30,8 +30,9 @@ class JsIrLinker(
override val translationPluginContext: TranslationPluginContext?,
private val icData: ICData? = null,
private val loweredIcData: Map<ModuleDescriptor, SerializedIcData> = emptyMap(),
friendModules: Map<String, Collection<String>> = emptyMap()
) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, emptyList()) {
friendModules: Map<String, Collection<String>> = emptyMap(),
allowUnboundSymbols: Boolean = false
) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, emptyList(), allowUnboundSymbols) {
override val fakeOverrideBuilder = FakeOverrideBuilder(this, symbolTable, JsManglerIr, IrTypeSystemContextImpl(builtIns), friendModules)