diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt index 13c01d1d892..52236afee5b 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt @@ -5,13 +5,16 @@ package org.jetbrains.kotlin.backend.common.interpreter -import kotlinx.coroutines.* +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.coroutines.yield import org.jetbrains.kotlin.backend.common.interpreter.builtins.* import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterException import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterMethodNotFoundException import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterTimeOutException import org.jetbrains.kotlin.backend.common.interpreter.intrinsics.IntrinsicEvaluator -import org.jetbrains.kotlin.backend.common.interpreter.stack.* +import org.jetbrains.kotlin.backend.common.interpreter.stack.StackImpl +import org.jetbrains.kotlin.backend.common.interpreter.stack.Variable import org.jetbrains.kotlin.backend.common.interpreter.state.* import org.jetbrains.kotlin.builtins.UnsignedTypes import org.jetbrains.kotlin.ir.IrElement @@ -54,7 +57,7 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map irBuiltIns.stringType is Float -> irBuiltIns.floatType is Double -> irBuiltIns.doubleType - null -> irBuiltIns.nothingType + null -> irBuiltIns.nothingNType else -> when (defaultType.classifierOrNull?.owner) { is IrTypeParameter -> stack.getVariable(defaultType.classifierOrFail).state.irClass.defaultType else -> defaultType @@ -239,7 +242,7 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map listOfNotNull(irFunction.getExtensionReceiver()) else -> listOf() } - val valueParametersDescriptors = receiverAsFirstArgument + irFunction.valueParameters.map { it.symbol } + val valueParametersSymbols = receiverAsFirstArgument + irFunction.valueParameters.map { it.symbol } val valueArguments = (0 until expression.valueArgumentsCount).map { expression.getValueArgument(it) } val defaultValues = expression.symbol.owner.valueParameters.map { it.defaultValue?.expression } @@ -249,7 +252,13 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map().toPrimitiveStateArray(expression.getVarargType(i)!!)) // if vararg is empty - with(Variable(valueParametersDescriptors[i], stack.popReturnValue())) { + if (stack.peekReturnValue().isNull() && !valueParametersSymbols[i].isNullable()) { + val method = irFunction.getCapitalizedFileName() + "." + irFunction.fqNameWhenAvailable + val parameter = valueParametersSymbols[i].owner.name + throw IllegalArgumentException("Parameter specified as non-null is null: method $method, parameter $parameter") + } + + with(Variable(valueParametersSymbols[i], stack.popReturnValue())) { stack.addVar(this) //must add value argument in current stack because it can be used later as default argument pool.add(this) } @@ -263,19 +272,18 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map valueArguments.add(Variable(it, receiver)) } } irFunction.getExtensionReceiver()?.let { extensionReceiver?.let { receiver -> valueArguments.add(Variable(it, receiver)) } } @@ -302,7 +310,7 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map irFunction.trySubstituteFunctionBody() ?: calculateBuiltIns(irFunction) else -> irFunction.interpret() } - } + }.check { return it }.implicitCastIfNeeded(expression.type, irFunction.returnType, stack) } private suspend fun IrFunction.trySubstituteFunctionBody(): ExecutionResult? { @@ -351,15 +359,16 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map stack.popReturnValue() IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> { - if (!stack.peekReturnValue().irClass.let { it.isSubclassOf(typeOperandClass) || it.defaultType.isNothing() }) { + if (!isErased && !stack.peekReturnValue().isSubtypeOf(typeOperand)) { val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable - throw ClassCastException("$convertibleClassName cannot be cast to ${typeOperandClass.fqNameWhenAvailable}") + throw ClassCastException("$convertibleClassName cannot be cast to ${typeOperand.getFqName(withNullableSymbol = true)}") } } IrTypeOperator.SAFE_CAST -> { - if (!stack.peekReturnValue().irClass.let { it.isSubclassOf(typeOperandClass) || it.defaultType.isNothing() }) { + if (!isErased && !stack.peekReturnValue().isSubtypeOf(typeOperand)) { stack.popReturnValue() - stack.pushReturnValue(null.toState(irBuiltIns.nothingType)) + stack.pushReturnValue(null.toState(irBuiltIns.nothingNType)) } } IrTypeOperator.INSTANCEOF -> { - val isInstance = stack.popReturnValue().irClass.let { it.isSubclassOf(typeOperandClass) || it.defaultType.isNothing() } + val isInstance = isErased || stack.peekReturnValue().isSubtypeOf(typeOperand) stack.pushReturnValue(isInstance.toState(irBuiltIns.nothingType)) } IrTypeOperator.NOT_INSTANCEOF -> { - val isInstance = stack.popReturnValue().irClass.let { it.isSubclassOf(typeOperandClass) || it.defaultType.isNothing() } + val isInstance = isErased || stack.peekReturnValue().isSubtypeOf(typeOperand) stack.pushReturnValue((!isInstance).toState(irBuiltIns.nothingType)) } IrTypeOperator.IMPLICIT_NOTNULL -> { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Label.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Label.kt index acdd2831690..f68f0746f67 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Label.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Label.kt @@ -5,6 +5,9 @@ package org.jetbrains.kotlin.backend.common.interpreter +import org.jetbrains.kotlin.backend.common.interpreter.stack.Stack +import org.jetbrains.kotlin.backend.common.interpreter.state.Primitive +import org.jetbrains.kotlin.backend.common.interpreter.state.isSubtypeOf import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyFunction @@ -12,6 +15,11 @@ import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrReturnableBlock import org.jetbrains.kotlin.ir.expressions.IrWhen import org.jetbrains.kotlin.ir.expressions.IrWhileLoop +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classifierOrFail +import org.jetbrains.kotlin.ir.types.classifierOrNull +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable enum class ReturnLabel { NEXT, RETURN, BREAK_LOOP, BREAK_WHEN, CONTINUE, EXCEPTION @@ -28,6 +36,21 @@ inline fun ExecutionResult.check(toCheckLabel: ReturnLabel = ReturnLabel.NEXT, r return this } +fun ExecutionResult.implicitCastIfNeeded(expectedType: IrType, actualType: IrType, stack: Stack): ExecutionResult { + if (actualType.classifierOrNull !is IrTypeParameterSymbol) return this + + if (expectedType.classifierOrFail is IrTypeParameterSymbol) return this + + val actualState = stack.peekReturnValue() + if (actualState is Primitive<*> && actualState.value == null) return this // this is handled as NullPointerException + + if (!actualState.isSubtypeOf(expectedType)) { + val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable + throw ClassCastException("$convertibleClassName cannot be cast to ${expectedType.getFqName(withNullableSymbol = true)}") + } + return this +} + open class ExecutionResultWithoutInfoAboutOwner(override val returnLabel: ReturnLabel) : ExecutionResult { override suspend fun getNextLabel(irElement: IrElement, interpret: suspend IrElement.() -> ExecutionResult): ExecutionResult { return when (returnLabel) { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt index 32e6f3d0924..b842d815eeb 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt @@ -16,17 +16,18 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.utils.addToStdlib.safeAs -fun IrFunction.getDispatchReceiver(): IrSymbol? { +fun IrFunction.getDispatchReceiver(): IrValueParameterSymbol? { return this.dispatchReceiverParameter?.symbol } -fun IrFunction.getExtensionReceiver(): IrSymbol? { +fun IrFunction.getExtensionReceiver(): IrValueParameterSymbol? { return this.extensionReceiverParameter?.symbol } @@ -210,4 +211,19 @@ fun State?.getCorrectReceiverByFunction(irFunction: IrFunction): State? { val original: Complex? = this.getOriginal() val other = irFunction.parentClassOrNull?.thisReceiver ?: return this return generateSequence(original) { it.superClass }.firstOrNull { it.irClass.thisReceiver == other } ?: this -} \ No newline at end of file +} + +fun State.checkNullability(irType: IrType?): State { + if (irType !is IrSimpleType) return this + if (this.isNull() && !irType.hasQuestionMark) { + throw NullPointerException() + } + return this +} + +fun IrValueParameterSymbol.isNullable(): Boolean { + val type = this.owner.type as? IrSimpleType ?: return false + return type.isNullable() +} + +fun IrFunction.getCapitalizedFileName() = this.file.name.replace(".kt", "Kt").capitalize() \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt index 5321a5d85e0..f35252e7731 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt @@ -31,7 +31,8 @@ object EmptyArray : IntrinsicBase() { override suspend fun evaluate( irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult ): ExecutionResult { - stack.pushReturnValue(emptyArray().toState(irFunction.returnType)) + val typeArguments = irFunction.typeParameters.map { stack.getVariable(it.symbol) } + stack.pushReturnValue(emptyArray().toState(irFunction.returnType).apply { addTypeArguments(typeArguments) }) return Next } } @@ -46,7 +47,8 @@ object ArrayOf : IntrinsicBase() { irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult ): ExecutionResult { val array = irFunction.getArgsForMethodInvocation(stack.getAll()).toTypedArray() - stack.pushReturnValue(array.toState(irFunction.returnType)) + val typeArguments = irFunction.typeParameters.map { stack.getVariable(it.symbol) } + stack.pushReturnValue(array.toState(irFunction.returnType).apply { addTypeArguments(typeArguments) }) return Next } } @@ -62,7 +64,8 @@ object ArrayOfNulls : IntrinsicBase() { ): ExecutionResult { val size = stack.getVariable(irFunction.valueParameters.first().symbol).state.asInt() val array = arrayOfNulls(size) - stack.pushReturnValue(array.toState(irFunction.returnType)) + val typeArguments = irFunction.typeParameters.map { stack.getVariable(it.symbol) } + stack.pushReturnValue(array.toState(irFunction.returnType).apply { addTypeArguments(typeArguments) }) return Next } } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/Stack.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/Stack.kt index e48b85b7aaa..2efd27e505e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/Stack.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/Stack.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.common.interpreter.stack import org.jetbrains.kotlin.backend.common.interpreter.ExecutionResult import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterException +import org.jetbrains.kotlin.backend.common.interpreter.getCapitalizedFileName import org.jetbrains.kotlin.backend.common.interpreter.state.State import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.name @@ -62,7 +63,7 @@ class StackImpl : Stack { override fun setCurrentFrameName(irFunction: IrFunction) { val fileName = irFunction.file.name - val fileNameCapitalized = fileName.replace(".kt", "Kt").capitalize() + val fileNameCapitalized = irFunction.getCapitalizedFileName() val lineNum = irFunction.fileEntry.getLineNumber(irFunction.startOffset) + 1 if (getCurrentFrame().frameEntryPoint == null) getCurrentFrame().frameEntryPoint = "at $fileNameCapitalized.${irFunction.fqNameWhenAvailable}($fileName:$lineNum)" diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Complex.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Complex.kt index a241eb6f9ab..08229dd7b34 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Complex.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Complex.kt @@ -22,7 +22,7 @@ abstract class Complex(override val irClass: IrClass, override val fields: Mutab var superClass: Complex? = null var subClass: Complex? = null val interfaces: MutableList = mutableListOf() // filled lazily, as needed - val typeArguments: MutableList = mutableListOf() + override val typeArguments: MutableList = mutableListOf() var outerClass: Variable? = null fun setSuperClassInstance(superClass: Complex) { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Lambda.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Lambda.kt index f1381202683..3d81c3a38b9 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Lambda.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Lambda.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.cast class Lambda(val irFunction: IrFunction, override val irClass: IrClass) : State { override val fields: MutableList = mutableListOf() + override val typeArguments: MutableList = mutableListOf() private val invokeSymbol = irClass.declarations .single { it.nameForIrSerialization.asString() == "invoke" } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Primitive.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Primitive.kt index 8cf6121158a..a149a269875 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Primitive.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Primitive.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.util.isFakeOverride class Primitive(var value: T, val type: IrType) : State { override val fields: MutableList = mutableListOf() + override val typeArguments: MutableList = mutableListOf() override val irClass: IrClass = type.classOrNull!!.owner override fun getState(symbol: IrSymbol): State { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/State.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/State.kt index 6ae82cb25db..9ed8e394c23 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/State.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/State.kt @@ -10,10 +10,13 @@ import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.defaultType interface State { val fields: MutableList val irClass: IrClass + val typeArguments: MutableList fun getState(symbol: IrSymbol): State? { return fields.firstOrNull { it.symbol == symbol }?.state @@ -26,11 +29,30 @@ interface State { } } + fun addTypeArguments(typeArguments: List) { + this.typeArguments.addAll(typeArguments) + } + fun getIrFunctionByIrCall(expression: IrCall): IrFunction? } +fun State.isNull() = this is Primitive<*> && this.value == null + fun State.asInt() = (this as Primitive<*>).value as Int fun State.asBoolean() = (this as Primitive<*>).value as Boolean fun State.asString() = (this as Primitive<*>).value.toString() -fun State.asBooleanOrNull() = (this as? Primitive<*>)?.value as? Boolean \ No newline at end of file +fun State.asBooleanOrNull() = (this as? Primitive<*>)?.value as? Boolean + +fun State.isSubtypeOf(other: IrType): Boolean { + if (this is Primitive<*> && this.value == null) return other.isNullable() + + if (this is Primitive<*> && this.type.isArray() && other.isArray()) { + val thisClass = this.typeArguments.single().state.irClass.symbol + val otherArgument = (other as IrSimpleType).arguments.single() + if (otherArgument is IrStarProjection) return true + return thisClass.isSubtypeOfClass(otherArgument.typeOrNull!!.classOrNull!!) + } + + return this.irClass.defaultType.isSubtypeOfClass(other.classOrNull!!) +} \ No newline at end of file