Rewrite interpretation of enum entries to avoid IR data corruption

There was a bug here if during enum creation there will be an
exception. Then function `cleanEnumSuperCall` will never be called
and ir will be corrupted.
This commit is contained in:
Ivan Kylchik
2022-07-22 19:00:10 +02:00
committed by teamcity
parent 008a5f02e5
commit b9e2173288
3 changed files with 37 additions and 28 deletions
@@ -37,7 +37,7 @@ internal fun unfoldInstruction(element: IrElement?, environment: IrInterpreterEn
is IrConstructor -> unfoldConstructor(element, callStack)
is IrCall -> unfoldValueParameters(element, environment)
is IrConstructorCall -> unfoldValueParameters(element, environment)
is IrEnumConstructorCall -> unfoldValueParameters(element, environment)
is IrEnumConstructorCall -> unfoldEnumConstructorCall(element, environment)
is IrDelegatingConstructorCall -> unfoldValueParameters(element, environment)
is IrInstanceInitializerCall -> unfoldInstanceInitializerCall(element, callStack)
is IrField -> unfoldField(element, callStack)
@@ -49,6 +49,7 @@ internal fun unfoldInstruction(element: IrElement?, environment: IrInterpreterEn
is IrGetValue -> unfoldGetValue(element, environment)
is IrGetObjectValue -> unfoldGetObjectValue(element, environment)
is IrGetEnumValue -> unfoldGetEnumValue(element, environment)
is IrEnumEntry -> unfoldEnumEntry(element, environment)
is IrConst<*> -> callStack.pushSimpleInstruction(element)
is IrVariable -> unfoldVariable(element, callStack)
is IrSetValue -> unfoldSetValue(element, callStack)
@@ -177,6 +178,21 @@ private fun unfoldValueParameters(expression: IrFunctionAccessExpression, enviro
}
}
private fun unfoldEnumConstructorCall(element: IrEnumConstructorCall, environment: IrInterpreterEnvironment) {
val parentName = element.symbol.owner.parentClassOrNull?.fqName
if (parentName == "kotlin.Enum") {
// must create a copy here to avoid original data corruption
val constructorCallCopy = element.shallowCopy()
val enumObject = environment.callStack.loadState(element.getThisReceiver())
environment.irBuiltIns.enumClass.owner.declarations.filterIsInstance<IrProperty>().forEachIndexed { index, it ->
val field = enumObject.getField(it.symbol) as Primitive<*>
constructorCallCopy.putValueArgument(index, field.value.toIrConst(field.type))
}
return unfoldValueParameters(constructorCallCopy, environment)
}
unfoldValueParameters(element, environment)
}
private fun unfoldInstanceInitializerCall(instanceInitializerCall: IrInstanceInitializerCall, callStack: CallStack) {
val irClass = instanceInitializerCall.classSymbol.owner
val toInitialize = irClass.declarations.filter { it is IrProperty || it is IrAnonymousInitializer }
@@ -264,6 +280,13 @@ private fun unfoldGetEnumValue(expression: IrGetEnumValue, environment: IrInterp
}
}
@Suppress("UNUSED_PARAMETER")
private fun unfoldEnumEntry(enumEntry: IrEnumEntry, environment: IrInterpreterEnvironment) {
// a little hak and misconception here; we are not creating simple instructions from this and just do the cleaning after interpretation
environment.callStack.popState()
environment.callStack.dropSubFrame()
}
private fun unfoldVariable(variable: IrVariable, callStack: CallStack) {
when (variable.initializer) {
null -> callStack.storeState(variable.symbol, null)
@@ -396,28 +396,24 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
?: throw InterpreterError("Initializer at enum entry ${enumEntry.fqName} is null")
val enumConstructorCall = enumInitializer as? IrEnumConstructorCall
?: (enumInitializer as IrBlock).statements.filterIsInstance<IrEnumConstructorCall>().single()
val enumSuperCall = enumConstructorCall.getSuperEnumCall()
// TODO must call this function even after exception has occurred or else data will be corrupted
val cleanEnumSuperCall = fun() {
enumSuperCall.apply { (0 until this.valueArgumentsCount).forEach { putValueArgument(it, null) } } // restore to null
callStack.popState() // result of constructor must be dropped, because next instruction will be `IrGetEnumValue`
callStack.dropSubFrame()
}
if (enumEntries.isNotEmpty()) {
val valueArguments = listOf(
enumEntry.name.asString().toIrConst(irBuiltIns.stringType),
enumEntries.indexOf(enumEntry).toIrConst(irBuiltIns.intType)
)
valueArguments.forEachIndexed { index, irConst -> enumSuperCall.putValueArgument(index, irConst) }
}
val enumClassObject = Common(enumEntry.correspondingClass ?: enumClass)
environment.mapOfEnums[enumEntry.symbol] = enumClassObject
if (enumEntries.isNotEmpty()) {
// these fields will be evaluated during interpretation of constructor call
// but we need them right away to create correct call to Enum's constructor
val valueArguments = listOf(
Primitive(enumEntry.name.asString(), irBuiltIns.stringType),
Primitive(enumEntries.indexOf(enumEntry), irBuiltIns.intType)
)
irBuiltIns.enumClass.owner.declarations.filterIsInstance<IrProperty>().zip(valueArguments).forEach { (property, argument) ->
enumClassObject.setField(property.symbol, argument)
}
}
callStack.newSubFrame(enumEntry)
callStack.pushInstruction(CustomInstruction(cleanEnumSuperCall))
callStack.pushCompoundInstruction(enumEntry) // not really a compound instruction
callStack.pushCompoundInstruction(enumInitializer)
callStack.storeState(enumConstructorCall.getThisReceiver(), enumClassObject)
}
@@ -285,16 +285,6 @@ internal fun IrInterpreterEnvironment.loadReifiedTypeArguments(expression: IrFun
}
}
internal fun IrFunctionAccessExpression.getSuperEnumCall(): IrEnumConstructorCall {
val name = this.symbol.owner.parentClassOrNull?.fqName
if (this is IrEnumConstructorCall && name == "kotlin.Enum") return this
return when (val delegatingCall = this.symbol.owner.body?.statements?.get(0)) {
is IrFunctionAccessExpression -> delegatingCall.getSuperEnumCall()
is IrTypeOperatorCall -> (delegatingCall.argument as IrFunctionAccessExpression).getSuperEnumCall()
else -> TODO("$delegatingCall is unexpected")
}
}
internal fun IrFunction.hasFunInterfaceParent(): Boolean {
return this.parentClassOrNull?.isFun == true
}