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.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.* import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.interpreter.state.reflection.* 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.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
internal interface Instruction { internal interface Instruction {
@@ -179,28 +177,33 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
val irFunction = dispatchReceiver?.getIrFunctionByIrCall(call) ?: call.symbol.owner val irFunction = dispatchReceiver?.getIrFunctionByIrCall(call) ?: call.symbol.owner
val args = listOfNotNull(dispatchReceiver.getThisOrSuperReceiver(irFunction), extensionReceiver) + valueArguments 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.newFrame(irFunction)
callStack.addInstruction(SimpleInstruction(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 (dispatchReceiver is StateWithClosure) callStack.loadUpValues(dispatchReceiver)
if (extensionReceiver is StateWithClosure) callStack.loadUpValues(extensionReceiver) if (extensionReceiver is StateWithClosure) callStack.loadUpValues(extensionReceiver)
if (irFunction.isLocal) callStack.copyUpValuesFromPreviousFrame() 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 // TODO: if using KTypeState then it's class must be corresponding
// `call.type` is used in check cast and emptyArray // `call.type` is used in check cast and emptyArray
callStack.addVariable(Variable(irFunction.symbol, KTypeState(call.type, irBuiltIns.anyClass.owner))) callStack.addVariable(Variable(irFunction.symbol, KTypeState(call.type, irBuiltIns.anyClass.owner)))
// 4. store arguments in memory (remap args on actual names) // 6. store reified type parameters
irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> callStack.addVariable(Variable(it, receiver)) } } reifiedTypeArguments.forEach { callStack.addVariable(it) }
irFunction.getExtensionReceiver()?.let { callStack.addVariable(Variable(it, extensionReceiver ?: callStack.getState(it))) }
irFunction.valueParameters.forEachIndexed { i, param -> callStack.addVariable(Variable(param.symbol, valueArguments[i])) }
// 5. store reified type parameters // 7. load outer class object
irFunction.typeParameters.filter { it.isReified }
.forEach { callStack.addVariable(Variable(it.symbol, KTypeState(call.getTypeArgument(it.index)!!, irBuiltIns.anyClass.owner))) }
// 6. load outer class object
if (dispatchReceiver is Complex && irFunction.parentClassOrNull?.isInner == true) dispatchReceiver.loadOuterClassesInto(callStack) if (dispatchReceiver is Complex && irFunction.parentClassOrNull?.isInner == true) dispatchReceiver.loadOuterClassesInto(callStack)
callInterceptor.interceptCall(call, irFunction, args) { callInterceptor.interceptCall(call, irFunction, args) {
@@ -444,25 +447,10 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
} }
private fun interpretTypeOperatorCall(expression: IrTypeOperatorCall) { 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 typeClassifier = expression.typeOperand.classifierOrFail
val isReified = (typeClassifier.owner as? IrTypeParameter)?.isReified == true val isReified = (typeClassifier.owner as? IrTypeParameter)?.isReified == true
val isErased = typeClassifier.owner is IrTypeParameter && !isReified 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() val state = callStack.popState()
when (expression.operator) { when (expression.operator) {
@@ -472,6 +460,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
} }
IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> { IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> {
when { when {
// this first check is performed inside `isSubtypeOf` but repeated here to create separate exception
state.isNull() && !typeOperand.isNullable() -> NullPointerException().handleUserException(environment) state.isNull() && !typeOperand.isNullable() -> NullPointerException().handleUserException(environment)
!isErased && !state.isSubtypeOf(typeOperand) -> { !isErased && !state.isSubtypeOf(typeOperand) -> {
val castedClassName = state.irClass.fqNameWhenAvailable val castedClassName = state.irClass.fqNameWhenAvailable
@@ -487,11 +476,11 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
} }
} }
IrTypeOperator.INSTANCEOF -> { IrTypeOperator.INSTANCEOF -> {
val isInstance = state.isSubtypeOf(typeOperand) || isErased val isInstance = isErased || state.isSubtypeOf(typeOperand)
callStack.pushState(isInstance.toState(irBuiltIns.booleanType)) callStack.pushState(isInstance.toState(irBuiltIns.booleanType))
} }
IrTypeOperator.NOT_INSTANCEOF -> { IrTypeOperator.NOT_INSTANCEOF -> {
val isInstance = state.isSubtypeOf(typeOperand) || isErased val isInstance = isErased || state.isSubtypeOf(typeOperand)
callStack.pushState((!isInstance).toState(irBuiltIns.booleanType)) callStack.pushState((!isInstance).toState(irBuiltIns.booleanType))
} }
IrTypeOperator.IMPLICIT_NOTNULL -> { 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.exceptions.handleUserException
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
import org.jetbrains.kotlin.ir.interpreter.proxy.wrap import org.jetbrains.kotlin.ir.interpreter.proxy.wrap
import org.jetbrains.kotlin.ir.interpreter.state.Primitive import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.state.State import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.interpreter.state.Wrapper
import org.jetbrains.kotlin.ir.interpreter.state.isSubtypeOf
import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState
import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
@@ -247,3 +245,28 @@ internal fun IrValueParameter.getDefaultWithActualParameters(
} }
return expression?.deepCopyWithSymbols(newParent)?.transform(transformer, null) 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 val state = irFunction.valueParameters.getOrNull(1)?.symbol?.let { environment.callStack.getState(it) as StateWithClosure }
environment.callStack.pushState(arrayValue.toPrimitiveStateArray(type.irType)) 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 ExceptionState -> this
is Wrapper -> this.value is Wrapper -> this.value
is Primitive<*> -> when { is Primitive<*> -> when {
this.isNull() -> null
this.type.isArray() || this.type.isPrimitiveArray() -> if (remainArraysAsIs) this else this.value this.type.isArray() || this.type.isPrimitiveArray() -> if (remainArraysAsIs) this else this.value
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 * Prepare state object to be passed in outer world
*/ */
internal fun List<State>.wrap(callInterceptor: CallInterceptor, irFunction: IrFunction, methodType: MethodType? = null): List<Any?> { 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 -> return this.mapIndexed { index, state ->
// don't get arrays from Primitive in case of "set" and "Pair.<init>"; information about type will be lost // 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)) state.wrap(callInterceptor, unwrapArrays, methodType?.parameterType(index))
} }
} }
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.ir.interpreter.state package org.jetbrains.kotlin.ir.interpreter.state
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.*
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.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment
import org.jetbrains.kotlin.ir.interpreter.exceptions.handleUserException 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.asStringOrNull() = (this as Primitive<*>).value as? String
internal fun State.isSubtypeOf(other: IrType): Boolean { 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 Primitive<*> && this.value == null) return other.isNullable()
if (this is ExceptionState) return this.isSubtypeOf(other.classOrNull!!.owner) 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 { fun IrType.arraySubtypeCheck(other: IrType): Boolean {
if (other !is IrSimpleType || this !is IrSimpleType) return false if (other !is IrSimpleType || this !is IrSimpleType) return false
val thisArgument = this.arguments.single().typeOrNull ?: return false val thisArgument = this.arguments.single().typeOrNull ?: return false
val otherArgument = other.arguments.single().typeOrNull ?: return other.arguments.single() is IrStarProjection val otherArgument = other.arguments.single().typeOrNull ?: return other.arguments.single() is IrStarProjection
if (thisArgument.isArray() && otherArgument.isArray()) return thisArgument.arraySubtypeCheck(otherArgument) 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 thisArgument.classOrNull?.isSubtypeOfClass(otherArgument.classOrNull!!) ?: false
} }
return this.type.arraySubtypeCheck(other) return this.type.arraySubtypeCheck(other)