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:
+24
-1
@@ -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)
|
||||
|
||||
+13
-17
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user