Implement correct type checks and casts taking into account erased types
This commit is contained in:
+30
-19
@@ -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<IdSigna
|
||||
is String -> 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<IdSigna
|
||||
true -> 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<IdSigna
|
||||
(valueArguments[i] ?: defaultValues[i])?.interpret()?.check { return@newFrame it }
|
||||
?: stack.pushReturnValue(listOf<Any?>().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<IdSigna
|
||||
// dispatch receiver processing
|
||||
val rawDispatchReceiver = expression.dispatchReceiver
|
||||
rawDispatchReceiver?.interpret()?.check { return it }
|
||||
val dispatchReceiver = rawDispatchReceiver?.let { stack.popReturnValue() }
|
||||
val dispatchReceiver = rawDispatchReceiver?.let { stack.popReturnValue() }?.checkNullability(expression.dispatchReceiver?.type)
|
||||
|
||||
// extension receiver processing
|
||||
val rawExtensionReceiver = expression.extensionReceiver
|
||||
rawExtensionReceiver?.interpret()?.check { return it }
|
||||
val extensionReceiver = rawExtensionReceiver?.let { stack.popReturnValue() }
|
||||
val extensionReceiver = rawExtensionReceiver?.let { stack.popReturnValue() }?.checkNullability(expression.extensionReceiver?.type)
|
||||
|
||||
// get correct ir function
|
||||
val irFunction = dispatchReceiver?.getIrFunctionByIrCall(expression) ?: expression.symbol.owner
|
||||
val functionReceiver = dispatchReceiver.getCorrectReceiverByFunction(irFunction)
|
||||
|
||||
// it is important firstly to add receiver, then arguments; this order is used in builtin method call
|
||||
// TODO what if receiver is null
|
||||
irFunction.getDispatchReceiver()?.let { functionReceiver?.let { receiver -> 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<IdSigna
|
||||
irFunction.body == null -> 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<IdSigna
|
||||
val typeArguments = getTypeArguments(irClass, constructorCall) { stack.getVariable(it).state }
|
||||
if (irClass.hasAnnotation(evaluateIntrinsicAnnotation) || irClass.fqNameWhenAvailable!!.startsWith(Name.identifier("java"))) {
|
||||
return stack.newFrame(initPool = valueArguments) { Wrapper.getConstructorMethod(owner).invokeMethod(owner) }
|
||||
.apply { (stack.peekReturnValue() as? Wrapper)?.typeArguments?.addAll(typeArguments) } // 'as?' because can be exception
|
||||
.apply { stack.peekReturnValue().addTypeArguments(typeArguments) }
|
||||
}
|
||||
|
||||
if (irClass.defaultType.isArray() || irClass.defaultType.isPrimitiveArray()) {
|
||||
// array constructor doesn't have body so must be treated separately
|
||||
return stack.newFrame(initPool = valueArguments) { handleIntrinsicMethods(owner) }
|
||||
.apply { stack.peekReturnValue().addTypeArguments(typeArguments) }
|
||||
}
|
||||
|
||||
val state = Common(irClass).apply { this.typeArguments.addAll(typeArguments) }
|
||||
val state = Common(irClass).apply { this.addTypeArguments(typeArguments) }
|
||||
if (irClass.isLocal) state.fields.addAll(stack.getAll()) // TODO save only necessary declarations
|
||||
if (irClass.isInner) {
|
||||
constructorCall.dispatchReceiver!!.interpret().check { return it }
|
||||
@@ -601,30 +610,32 @@ class IrInterpreter(irModule: IrModuleFragment, private val bodyMap: Map<IdSigna
|
||||
|
||||
private suspend fun interpretTypeOperatorCall(expression: IrTypeOperatorCall): ExecutionResult {
|
||||
val executionResult = expression.argument.interpret().check { return it }
|
||||
val typeOperandDescriptor = expression.typeOperand.classifierOrFail
|
||||
val typeOperandClass = expression.typeOperand.classOrNull?.owner ?: stack.getVariable(typeOperandDescriptor).state.irClass
|
||||
val typeClassifier = expression.typeOperand.classifierOrFail
|
||||
val isReified = (typeClassifier.owner as? IrTypeParameter)?.isReified == true
|
||||
val isErased = typeClassifier.owner is IrTypeParameter && !isReified
|
||||
val typeOperand = if (isReified) stack.getVariable(typeClassifier).state.irClass.defaultType else expression.typeOperand
|
||||
|
||||
when (expression.operator) {
|
||||
// coercion to unit means that return value isn't used
|
||||
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> 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 -> {
|
||||
|
||||
+23
@@ -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) {
|
||||
|
||||
+19
-3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
+6
-3
@@ -31,7 +31,8 @@ object EmptyArray : IntrinsicBase() {
|
||||
override suspend fun evaluate(
|
||||
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
|
||||
): ExecutionResult {
|
||||
stack.pushReturnValue(emptyArray<Any?>().toState(irFunction.returnType))
|
||||
val typeArguments = irFunction.typeParameters.map { stack.getVariable(it.symbol) }
|
||||
stack.pushReturnValue(emptyArray<Any?>().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<Any?>(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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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)"
|
||||
|
||||
+1
-1
@@ -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<Complex> = mutableListOf() // filled lazily, as needed
|
||||
val typeArguments: MutableList<Variable> = mutableListOf()
|
||||
override val typeArguments: MutableList<Variable> = mutableListOf()
|
||||
var outerClass: Variable? = null
|
||||
|
||||
fun setSuperClassInstance(superClass: Complex) {
|
||||
|
||||
+1
@@ -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<Variable> = mutableListOf()
|
||||
override val typeArguments: MutableList<Variable> = mutableListOf()
|
||||
|
||||
private val invokeSymbol = irClass.declarations
|
||||
.single { it.nameForIrSerialization.asString() == "invoke" }
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.util.isFakeOverride
|
||||
|
||||
class Primitive<T>(var value: T, val type: IrType) : State {
|
||||
override val fields: MutableList<Variable> = mutableListOf()
|
||||
override val typeArguments: MutableList<Variable> = mutableListOf()
|
||||
override val irClass: IrClass = type.classOrNull!!.owner
|
||||
|
||||
override fun getState(symbol: IrSymbol): State {
|
||||
|
||||
+23
-1
@@ -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<Variable>
|
||||
val irClass: IrClass
|
||||
val typeArguments: MutableList<Variable>
|
||||
|
||||
fun getState(symbol: IrSymbol): State? {
|
||||
return fields.firstOrNull { it.symbol == symbol }?.state
|
||||
@@ -26,11 +29,30 @@ interface State {
|
||||
}
|
||||
}
|
||||
|
||||
fun addTypeArguments(typeArguments: List<Variable>) {
|
||||
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
|
||||
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!!)
|
||||
}
|
||||
Reference in New Issue
Block a user