Implement correct type checks and casts taking into account erased types

This commit is contained in:
Ivan Kylchik
2020-06-09 14:38:56 +03:00
parent 7a19906705
commit d90aba60cc
9 changed files with 106 additions and 28 deletions
@@ -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 -> {
@@ -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) {
@@ -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()
@@ -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
}
}
@@ -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)"
@@ -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) {
@@ -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" }
@@ -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 {
@@ -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!!)
}