Support type check for deep nested arrays in interpreter

This commit is contained in:
Ivan Kylchik
2021-06-22 22:39:07 +03:00
committed by TeamCityServer
parent 4ad88679fd
commit b9decc3b30
5 changed files with 59 additions and 44 deletions
@@ -21,10 +21,8 @@ import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.interpreter.state.reflection.*
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
import org.jetbrains.kotlin.ir.util.*
internal interface Instruction {
@@ -179,28 +177,33 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
val irFunction = dispatchReceiver?.getIrFunctionByIrCall(call) ?: call.symbol.owner
val args = listOfNotNull(dispatchReceiver.getThisOrSuperReceiver(irFunction), extensionReceiver) + valueArguments
// 3. evaluate reified type arguments; must do it here, before new frame, because outer type arguments can be loaded at this point
val reifiedTypeArguments = irFunction.typeParameters.filter { it.isReified }
.map {
val reifiedType = call.getTypeArgument(it.index)!!.getTypeIfReified(callStack)
Variable(it.symbol, KTypeState(reifiedType, irBuiltIns.anyClass.owner))
}
callStack.newFrame(irFunction)
callStack.addInstruction(SimpleInstruction(irFunction))
// 3. load up values onto stack; do it at first to set low priority of these variables
// 4. load up values onto stack; do it at first to set low priority of these variables
if (dispatchReceiver is StateWithClosure) callStack.loadUpValues(dispatchReceiver)
if (extensionReceiver is StateWithClosure) callStack.loadUpValues(extensionReceiver)
if (irFunction.isLocal) callStack.copyUpValuesFromPreviousFrame()
// 5. store arguments in memory (remap args on actual names)
irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> callStack.addVariable(Variable(it, receiver)) } }
irFunction.getExtensionReceiver()?.let { callStack.addVariable(Variable(it, extensionReceiver ?: callStack.getState(it))) }
irFunction.valueParameters.forEachIndexed { i, param -> callStack.addVariable(Variable(param.symbol, valueArguments[i])) }
// TODO: if using KTypeState then it's class must be corresponding
// `call.type` is used in check cast and emptyArray
callStack.addVariable(Variable(irFunction.symbol, KTypeState(call.type, irBuiltIns.anyClass.owner)))
// 4. store arguments in memory (remap args on actual names)
irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> callStack.addVariable(Variable(it, receiver)) } }
irFunction.getExtensionReceiver()?.let { callStack.addVariable(Variable(it, extensionReceiver ?: callStack.getState(it))) }
irFunction.valueParameters.forEachIndexed { i, param -> callStack.addVariable(Variable(param.symbol, valueArguments[i])) }
// 6. store reified type parameters
reifiedTypeArguments.forEach { callStack.addVariable(it) }
// 5. store reified type parameters
irFunction.typeParameters.filter { it.isReified }
.forEach { callStack.addVariable(Variable(it.symbol, KTypeState(call.getTypeArgument(it.index)!!, irBuiltIns.anyClass.owner))) }
// 6. load outer class object
// 7. load outer class object
if (dispatchReceiver is Complex && irFunction.parentClassOrNull?.isInner == true) dispatchReceiver.loadOuterClassesInto(callStack)
callInterceptor.interceptCall(call, irFunction, args) {
@@ -444,25 +447,10 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
}
private fun interpretTypeOperatorCall(expression: IrTypeOperatorCall) {
fun IrClassifierSymbol.getTypeFromStack(): IrType {
return (callStack.getState(this) as KTypeState).irType
}
fun IrType.replaceArgumentIfReified(): IrType {
if (this !is IrSimpleType) return this
val argument = this.arguments.singleOrNull()?.typeOrNull?.classifierOrNull
return when {
argument is IrTypeParameterSymbol && argument.owner.isReified -> {
this.buildSimpleType { arguments = listOf(argument.getTypeFromStack() as IrTypeArgument) }
}
else -> this
}
}
val typeClassifier = expression.typeOperand.classifierOrFail
val isReified = (typeClassifier.owner as? IrTypeParameter)?.isReified == true
val isErased = typeClassifier.owner is IrTypeParameter && !isReified
val typeOperand = (if (isReified) typeClassifier.getTypeFromStack() else expression.typeOperand).replaceArgumentIfReified()
val typeOperand = expression.typeOperand.getTypeIfReified(callStack)
val state = callStack.popState()
when (expression.operator) {
@@ -472,6 +460,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
}
IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> {
when {
// this first check is performed inside `isSubtypeOf` but repeated here to create separate exception
state.isNull() && !typeOperand.isNullable() -> NullPointerException().handleUserException(environment)
!isErased && !state.isSubtypeOf(typeOperand) -> {
val castedClassName = state.irClass.fqNameWhenAvailable
@@ -487,11 +476,11 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
}
}
IrTypeOperator.INSTANCEOF -> {
val isInstance = state.isSubtypeOf(typeOperand) || isErased
val isInstance = isErased || state.isSubtypeOf(typeOperand)
callStack.pushState(isInstance.toState(irBuiltIns.booleanType))
}
IrTypeOperator.NOT_INSTANCEOF -> {
val isInstance = state.isSubtypeOf(typeOperand) || isErased
val isInstance = isErased || state.isSubtypeOf(typeOperand)
callStack.pushState((!isInstance).toState(irBuiltIns.booleanType))
}
IrTypeOperator.IMPLICIT_NOTNULL -> {
@@ -13,10 +13,8 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.interpreter.exceptions.handleUserException
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
import org.jetbrains.kotlin.ir.interpreter.proxy.wrap
import org.jetbrains.kotlin.ir.interpreter.state.Primitive
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.interpreter.state.Wrapper
import org.jetbrains.kotlin.ir.interpreter.state.isSubtypeOf
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
@@ -247,3 +245,28 @@ internal fun IrValueParameter.getDefaultWithActualParameters(
}
return expression?.deepCopyWithSymbols(newParent)?.transform(transformer, null)
}
internal fun IrType.getTypeIfReified(callStack: CallStack): IrType {
return this.getTypeIfReified { (callStack.getState(it) as KTypeState).irType }
}
internal fun IrType.getTypeIfReified(getType: (IrClassifierSymbol) -> IrType): IrType {
if (this !is IrSimpleType) return this
val owner = this.classifierOrNull?.owner
if (owner is IrTypeParameter && owner.isReified) {
return (getType(owner.symbol) as IrSimpleType)
.buildSimpleType { hasQuestionMark = this.hasQuestionMark || this@getTypeIfReified.hasQuestionMark }
}
val newArguments = this.arguments.map {
val type = it.typeOrNull ?: return@map it
val typeOwner = type.classifierOrNull?.owner
if (typeOwner is IrTypeParameter && !typeOwner.isReified) return@map it
type.getTypeIfReified(getType) as IrTypeArgument
}
return this.buildSimpleType {
hasQuestionMark = this.hasQuestionMark || this@getTypeIfReified.hasQuestionMark
arguments = newArguments
}
}
@@ -267,8 +267,12 @@ internal object ArrayConstructor : IntrinsicBase() {
}
}
val type = environment.callStack.getState(irFunction.symbol) as KTypeState
environment.callStack.pushState(arrayValue.toPrimitiveStateArray(type.irType))
val state = irFunction.valueParameters.getOrNull(1)?.symbol?.let { environment.callStack.getState(it) as StateWithClosure }
val typeFromMemory = (environment.callStack.getState(irFunction.symbol) as KTypeState).irType
val type = state?.let {
typeFromMemory.getTypeIfReified { typeSymbol -> (it.upValues.single { it.symbol == typeSymbol }.state as KTypeState).irType }
}
environment.callStack.pushState(arrayValue.toPrimitiveStateArray(type ?: typeFromMemory))
}
}
@@ -30,6 +30,7 @@ internal fun State.wrap(callInterceptor: CallInterceptor, remainArraysAsIs: Bool
is ExceptionState -> this
is Wrapper -> this.value
is Primitive<*> -> when {
this.isNull() -> null
this.type.isArray() || this.type.isPrimitiveArray() -> if (remainArraysAsIs) this else this.value
else -> this.value
}
@@ -43,10 +44,10 @@ internal fun State.wrap(callInterceptor: CallInterceptor, remainArraysAsIs: Bool
* Prepare state object to be passed in outer world
*/
internal fun List<State>.wrap(callInterceptor: CallInterceptor, irFunction: IrFunction, methodType: MethodType? = null): List<Any?> {
val name = irFunction.fqNameWhenAvailable?.asString()
val name = irFunction.fqNameWhenAvailable?.asString() ?: ""
return this.mapIndexed { index, state ->
// don't get arrays from Primitive in case of "set" and "Pair.<init>"; information about type will be lost
val unwrapArrays = name?.let { (it == "kotlin.Array.set" && index != 0) || it == "kotlin.Pair.<init>" } ?: false
val unwrapArrays = (name == "kotlin.Array.set" && index != 0) || name == "kotlin.Pair.<init>" || name == "kotlin.internal.ir.CHECK_NOT_NULL"
state.wrap(callInterceptor, unwrapArrays, methodType?.parameterType(index))
}
}
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.ir.interpreter.state
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment
import org.jetbrains.kotlin.ir.interpreter.exceptions.handleUserException
@@ -53,16 +50,17 @@ internal fun State.asBooleanOrNull() = (this as? Primitive<*>)?.value as? Boolea
internal fun State.asStringOrNull() = (this as Primitive<*>).value as? String
internal fun State.isSubtypeOf(other: IrType): Boolean {
if (this.isNull() && other.isNullable()) return true
if (this is Primitive<*> && this.value == null) return other.isNullable()
if (this is ExceptionState) return this.isSubtypeOf(other.classOrNull!!.owner)
if (this is Primitive<*> && this.type.isArray() && other.isArray()) {
if (this is Primitive<*> && (this.type.isArray() || this.type.isNullableArray()) && (other.isArray() || other.isNullableArray())) {
fun IrType.arraySubtypeCheck(other: IrType): Boolean {
if (other !is IrSimpleType || this !is IrSimpleType) return false
val thisArgument = this.arguments.single().typeOrNull ?: return false
val otherArgument = other.arguments.single().typeOrNull ?: return other.arguments.single() is IrStarProjection
if (thisArgument.isArray() && otherArgument.isArray()) return thisArgument.arraySubtypeCheck(otherArgument)
if (otherArgument.classOrNull == null) return false
if (otherArgument.classOrNull == null) return true
return thisArgument.classOrNull?.isSubtypeOfClass(otherArgument.classOrNull!!) ?: false
}
return this.type.arraySubtypeCheck(other)