From 1536bca1bb87412cdb7b00c8a5f893ea79a262a7 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Wed, 17 May 2023 17:38:21 +0200 Subject: [PATCH] [PL] Handle illegal fun interface SAM conversions ^KT-53967 --- .../ir/linkage/partial/PartialLinkageCase.kt | 11 ++ .../partial/PartialLinkageErrorMessages.kt | 31 ++++- .../partial/PartiallyLinkedIrTreePatcher.kt | 39 ++++++- .../klibABI/classTransformations/lib1/l1.kt | 17 ++- .../klibABI/classTransformations/lib1/l1.kt.1 | 17 ++- .../klibABI/classTransformations/lib2/l2.kt | 109 +++++++++++++++++- .../klibABI/classTransformations/main/m.kt | 21 +++- 7 files changed, 225 insertions(+), 20 deletions(-) diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/linkage/partial/PartialLinkageCase.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/linkage/partial/PartialLinkageCase.kt index 69fd2603fa0..9e60622298d 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/linkage/partial/PartialLinkageCase.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/linkage/partial/PartialLinkageCase.kt @@ -103,6 +103,17 @@ sealed interface PartialLinkageCase { val functionValueParameterCount: Int ) : PartialLinkageCase + /** + * SAM-conversion to a function interface that effectively has more than one abstract function or at least one abstract property. + * + * Applicable to: Expressions. + */ + class InvalidSamConversion( + val expression: IrTypeOperatorCall, + val abstractFunctionSymbols: Set, + val abstractPropertySymbol: IrPropertySymbol? + ) : PartialLinkageCase + /** * An [IrCall] of suspendable function at the place where no coroutine context is available. * diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartialLinkageErrorMessages.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartialLinkageErrorMessages.kt index 1f199e8ac87..f58daac1273 100644 --- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartialLinkageErrorMessages.kt +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartialLinkageErrorMessages.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.linkage.partial.ExploredClassifier.Unusable import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageCase import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageCase.* import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.types.classifierOrFail import org.jetbrains.kotlin.ir.util.IdSignature.* import org.jetbrains.kotlin.ir.util.isAnonymousObject import org.jetbrains.kotlin.ir.util.parentAsClass @@ -54,6 +55,10 @@ internal fun PartialLinkageCase.renderLinkageError(): String = buildString { ) } + is InvalidSamConversion -> expression(expression) { + invalidSamConversion(expression, abstractFunctionSymbols, abstractPropertySymbol) + } + is SuspendableFunctionCallWithoutCoroutineContext -> expression(expression) { suspendableCallWithoutCoroutine() } @@ -143,6 +148,7 @@ private enum class ExpressionKind(val prefix: String?, val postfix: String?) { WRITING("Can not write value to", null), GETTING_INSTANCE("Can not get instance of", null), TYPE_OPERATOR("Type operator expression", "can not be evaluated"), + SAM_CONVERSION("Single abstract method (SAM) conversion expression", "can not be evaluated"), ANONYMOUS_OBJECT_LITERAL("Anonymous object literal", "can not be evaluated"), OTHER_EXPRESSION("Expression", "can not be evaluated") } @@ -169,7 +175,12 @@ private val IrExpression.expression: Expression else -> Expression(REFERENCE, OTHER_DECLARATION) } is IrInstanceInitializerCall -> Expression(CALLING_INSTANCE_INITIALIZER, classSymbol.declarationKind) - is IrTypeOperatorCall -> Expression(TYPE_OPERATOR, null) + is IrTypeOperatorCall -> { + if (operator == IrTypeOperator.SAM_CONVERSION) + Expression(SAM_CONVERSION, null) + else + Expression(TYPE_OPERATOR, null) + } else -> { if (this is IrBlock && origin == IrStatementOrigin.OBJECT_LITERAL) Expression(ANONYMOUS_OBJECT_LITERAL, null) @@ -266,6 +277,11 @@ private fun Appendable.declarationKindName(symbol: IrSymbol, capitalized: Boolea return append(" ").declarationName(symbol) } +private fun Appendable.sortedDeclarationsName(symbols: Set): Appendable { + symbols.map { symbol -> buildString inner@ { this@inner.declarationName(symbol) } }.sorted().joinTo(this) + return this +} + private fun Appendable.declarationNameIsKind(symbol: IrSymbol): Appendable = declarationName(symbol).append(" is ").declarationKind(symbol, capitalized = false) @@ -515,6 +531,19 @@ private fun Appendable.memberAccessExpressionArgumentsMismatch( .append(functionValueParameterCount.toString()).append(")") } +private fun Appendable.invalidSamConversion( + expression: IrTypeOperatorCall, + abstractFunctionSymbols: Set, + abstractPropertySymbol: IrPropertySymbol?, +): Appendable { + declarationKindName(expression.typeOperand.classifierOrFail, capitalized = true) + return when { + abstractPropertySymbol != null -> append(" has abstract ").declarationKindName(abstractPropertySymbol, capitalized = false) + abstractFunctionSymbols.isEmpty() -> append(" does not have an abstract function") + else -> append(" has more than one abstract function: ").sortedDeclarationsName(abstractFunctionSymbols) + } +} + private fun Appendable.suspendableCallWithoutCoroutine(): Appendable = append("Suspend function can be called only from a coroutine or another suspend function") diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartiallyLinkedIrTreePatcher.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartiallyLinkedIrTreePatcher.kt index a79f99772a4..fdccbea6928 100644 --- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartiallyLinkedIrTreePatcher.kt +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/linkage/partial/PartiallyLinkedIrTreePatcher.kt @@ -24,9 +24,7 @@ import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageCase.* import org.jetbrains.kotlin.ir.overrides.isEffectivelyPrivate import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl -import org.jetbrains.kotlin.ir.types.IrSimpleType -import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.isSubtypeOfClass +import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid @@ -35,6 +33,7 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addToStdlib.ifTrue import org.jetbrains.kotlin.utils.compact +import org.jetbrains.kotlin.utils.newHashSetWithExpectedSize import java.util.* import kotlin.properties.Delegates import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageUtils.File as PLFile @@ -481,6 +480,7 @@ internal class PartiallyLinkedIrTreePatcher( override fun visitTypeOperator(expression: IrTypeOperatorCall) = expression.maybeThrowLinkageError { (typeOperand !== type).ifTrue { checkExpressionType(typeOperand) } + ?: checkSamConversion() } override fun visitDeclarationReference(expression: IrDeclarationReference) = expression.maybeThrowLinkageError { @@ -813,6 +813,39 @@ internal class PartiallyLinkedIrTreePatcher( null } + private fun IrTypeOperatorCall.checkSamConversion(): PartialLinkageCase? { + if (operator != IrTypeOperator.SAM_CONVERSION) return null + + val funInterface: IrClass = typeOperand.classOrNull?.owner ?: return null + + val abstractFunctionSymbols = newHashSetWithExpectedSize(funInterface.declarations.size) + funInterface.declarations.forEach { member -> + when (member) { + is IrSimpleFunction -> { + if (member.modality == Modality.ABSTRACT) + abstractFunctionSymbols += member.symbol + } + is IrProperty -> { + if (member.modality == Modality.ABSTRACT) + return InvalidSamConversion( + expression = this, + abstractFunctionSymbols = emptySet(), + abstractPropertySymbol = member.symbol + ) + } + } + } + + return if (abstractFunctionSymbols.size != 1) + InvalidSamConversion( + expression = this, + abstractFunctionSymbols = abstractFunctionSymbols, + abstractPropertySymbol = null + ) + else + null + } + private fun IrConstructorCall.checkNotAbstractClass(): PartialLinkageCase? { val createdClass = symbol.owner.parentAsClass return if (createdClass.modality == Modality.ABSTRACT || createdClass.modality == Modality.SEALED) diff --git a/compiler/testData/klibABI/classTransformations/lib1/l1.kt b/compiler/testData/klibABI/classTransformations/lib1/l1.kt index f44431a1eef..a8af2a02391 100644 --- a/compiler/testData/klibABI/classTransformations/lib1/l1.kt +++ b/compiler/testData/klibABI/classTransformations/lib1/l1.kt @@ -133,10 +133,6 @@ class ClassToValue(val x: Int) data class DataToClass(val x: Int, val y: Int) -fun interface FunctionalInterfaceToInterface { - fun work() -} - class ClassToAbstractClass { var name: String = "Alice" fun getGreeting() = "Hello, $name!" @@ -150,3 +146,16 @@ object PublicTopLevelLib1 { class ClassThatBecomesPrivate enum class EnumClassThatBecomesPrivate { ENTRY } } + +interface XAnswer { fun answer(): Int } +interface XAnswerDefault { fun answer(): Int /*= 42*/ } +interface XFunction1 { /*fun function1(): Int*/ } +interface XFunction1Default { /*fun function1(): Int = 42*/ } +interface XFunction2 { /*fun function2(): Int*/ } +interface XFunction2Default { /*fun function2(): Int = -42*/ } +interface XProperty1 { /*val property1: Int*/ } +interface XProperty1Default { /*val property1: Int get() = 42*/ } +interface XProperty2 { /*val property2: Int*/ } +interface XProperty2Default { /*val property2: Int get() = 42*/ } + +fun interface FunctionalInterfaceToInterface : XAnswer diff --git a/compiler/testData/klibABI/classTransformations/lib1/l1.kt.1 b/compiler/testData/klibABI/classTransformations/lib1/l1.kt.1 index 2828d5757b0..c82b2aa00c4 100644 --- a/compiler/testData/klibABI/classTransformations/lib1/l1.kt.1 +++ b/compiler/testData/klibABI/classTransformations/lib1/l1.kt.1 @@ -133,10 +133,6 @@ value class ClassToValue(val x: Int) /*data*/ class DataToClass(val x: Int, val y: Int) -/*fun*/ interface FunctionalInterfaceToInterface { - fun work() -} - abstract class ClassToAbstractClass { abstract var name: String fun getGreeting() = "Hello, $name!" @@ -150,3 +146,16 @@ object PublicTopLevelLib1 { private class ClassThatBecomesPrivate private enum class EnumClassThatBecomesPrivate { ENTRY } } + +interface XAnswer { fun answer(): Int } +interface XAnswerDefault { fun answer(): Int /*= 42*/ } +interface XFunction1 { fun function1(): Int } +interface XFunction1Default { fun function1(): Int = 42 } +interface XFunction2 { fun function2(): Int } +interface XFunction2Default { fun function2(): Int = -42 } +interface XProperty1 { val property1: Int } +interface XProperty1Default { val property1: Int get() = 42 } +interface XProperty2 { val property2: Int } +interface XProperty2Default { val property2: Int get() = 42 } + +/*fun*/ interface FunctionalInterfaceToInterface : XAnswer diff --git a/compiler/testData/klibABI/classTransformations/lib2/l2.kt b/compiler/testData/klibABI/classTransformations/lib2/l2.kt index acfba6fe90b..da8ed49c6ef 100644 --- a/compiler/testData/klibABI/classTransformations/lib2/l2.kt +++ b/compiler/testData/klibABI/classTransformations/lib2/l2.kt @@ -1,3 +1,4 @@ + fun getClassToEnumFoo(): ClassToEnum.Foo = ClassToEnum.Foo() inline fun getClassToEnumFooInline(): ClassToEnum.Foo = ClassToEnum.Foo() fun getClassToEnumFooAsAny(): Any = ClassToEnum.Foo() @@ -378,12 +379,6 @@ fun getSumFromDataClass(): Int { return x + y } -fun getFunctionalInterfaceToInterface(): FunctionalInterfaceToInterface { - val worker = FunctionalInterfaceToInterface { /* do some work */ } - worker.work() - return worker -} - fun instantiationOfAbstractClass() { // Accessing uninitialized members of abstract class is an UB. We shall not allow instantiating // abstract classes except for from their direct inheritors. @@ -424,3 +419,105 @@ class StableInheritorOfClassThatUsesPrivateTopLevelClass : AbstractIterator' can not be called: Can not instantiate abstract class 'ClassToAbstractClass'")) { instantiationOfAbstractClass() } expectSuccess { StableEnum.FOO.test } expectSuccess { StableEnum.BAR.test } expectSuccess("01234") { testStableInheritorOfClassThatUsesPrivateTopLevelClass() } + + expectSuccess(1) { getFunctionalInterfaceToInterface(1).answer() } + expectSuccess(2) { getFunctionalInterfaceToInterfaceAsObject(2).answer() } + expectSuccess(3) { getFunctionalInterfaceToInterfaceAnswer(3) } + expectSuccess(4) { getFunctionalInterfaceWith0AbstractFunctions(4).answer() } + expectSuccess(5) { getFunctionalInterfaceWith0AbstractFunctionsAsObject(5).answer() } + expectSuccess(6) { getFunctionalInterfaceWith0AbstractFunctionsAnswer(6) } + expectSuccess(7) { getFunctionalInterfaceWith1AbstractFunction(7).answer() } + expectSuccess(8) { getFunctionalInterfaceWith1AbstractFunctionAsObject(8).answer() } + expectSuccess(9) { getFunctionalInterfaceWith1AbstractFunctionAnswer(9) } + expectFailure(linkage("Single abstract method (SAM) conversion expression can not be evaluated: Fun interface 'FunctionalInterfaceWith2AbstractFunctions' has more than one abstract function: 'answer', 'function1'")) { getFunctionalInterfaceWith2AbstractFunctions(10).answer() } + expectSuccess(11) { getFunctionalInterfaceWith2AbstractFunctionsAsObject(11).answer() } + expectFailure(linkage("Single abstract method (SAM) conversion expression can not be evaluated: Fun interface 'FunctionalInterfaceWith2AbstractFunctions' has more than one abstract function: 'answer', 'function1'")) { getFunctionalInterfaceWith2AbstractFunctionsAnswer(12) } + expectFailure(linkage("Single abstract method (SAM) conversion expression can not be evaluated: Fun interface 'FunctionalInterfaceWith3AbstractFunctions' has more than one abstract function: 'answer', 'function1', 'function2'")) { getFunctionalInterfaceWith3AbstractFunctions(13).answer() } + expectSuccess(14) { getFunctionalInterfaceWith3AbstractFunctionsAsObject(14).answer() } + expectFailure(linkage("Single abstract method (SAM) conversion expression can not be evaluated: Fun interface 'FunctionalInterfaceWith3AbstractFunctions' has more than one abstract function: 'answer', 'function1', 'function2'")) { getFunctionalInterfaceWith3AbstractFunctionsAnswer(15) } + expectFailure(linkage("Single abstract method (SAM) conversion expression can not be evaluated: Fun interface 'FunctionalInterfaceWithAbstractProperty' has abstract property 'property1'")) { getFunctionalInterfaceWithAbstractProperty(16).answer() } + expectSuccess(17) { getFunctionalInterfaceWithAbstractPropertyAsObject(17).answer() } + expectFailure(linkage("Single abstract method (SAM) conversion expression can not be evaluated: Fun interface 'FunctionalInterfaceWithAbstractProperty' has abstract property 'property1'")) { getFunctionalInterfaceWithAbstractPropertyAnswer(18) } }