Support type check for deep nested arrays in interpreter
This commit is contained in:
committed by
TeamCityServer
parent
4ad88679fd
commit
b9decc3b30
+19
-30
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-2
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user