From e3f8aa255a07bf6787d72c2a42f5dd27152294cd Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Thu, 17 Nov 2022 14:08:22 +0100 Subject: [PATCH] Fix `StackOverflow` in interpreter for case with recursive enum init #KT-54884 Fixed --- .../ir/LoweredIrInterpreterTestGenerated.java | 6 ++++++ .../checker/IrCompileTimeChecker.kt | 15 ++++++++++----- .../interpreter/checker/IrConstTransformer.kt | 19 ++++++++++++++----- .../interpreter/enumRecursiveName.kt | 13 +++++++++++++ 4 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 compiler/testData/ir/loweredIr/interpreter/enumRecursiveName.kt diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/ir/LoweredIrInterpreterTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/ir/LoweredIrInterpreterTestGenerated.java index 074cdd09c7f..a1ddc546a4a 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/ir/LoweredIrInterpreterTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/ir/LoweredIrInterpreterTestGenerated.java @@ -76,6 +76,12 @@ public class LoweredIrInterpreterTestGenerated extends AbstractLoweredIrInterpre runTest("compiler/testData/ir/loweredIr/interpreter/enumName.kt"); } + @Test + @TestMetadata("enumRecursiveName.kt") + public void testEnumRecursiveName() throws Exception { + runTest("compiler/testData/ir/loweredIr/interpreter/enumRecursiveName.kt"); + } + @Test @TestMetadata("floatOperations.kt") public void testFloatOperations() throws Exception { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt index 81427969688..cc17c63a9f4 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt @@ -31,8 +31,9 @@ class IrCompileTimeChecker( } private fun IrCall.saveContext(block: () -> R): R { + val oldContext = contextExpression contextExpression = this - return block().apply { contextExpression = null } + return block().apply { contextExpression = oldContext } } override fun visitElement(element: IrElement, data: Nothing?) = false @@ -53,7 +54,7 @@ class IrCompileTimeChecker( private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean { return when { - !visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false + !visitValueArguments(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false else -> expression.symbol.owner.visitBodyIfNeeded() } } @@ -69,7 +70,7 @@ class IrCompileTimeChecker( return expression.saveContext { val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true - if (!visitValueParameters(expression, null)) return@saveContext false + if (!visitValueArguments(expression, null)) return@saveContext false val bodyComputable = owner.visitBodyIfNeeded() return@saveContext dispatchReceiverComputable && extensionReceiverComputable && bodyComputable } @@ -79,7 +80,7 @@ class IrCompileTimeChecker( return declaration.initializer?.accept(this, data) ?: true } - private fun visitValueParameters(expression: IrFunctionAccessExpression, data: Nothing?): Boolean { + private fun visitValueArguments(expression: IrFunctionAccessExpression, data: Nothing?): Boolean { return (0 until expression.valueArgumentsCount) .map { expression.getValueArgument(it) } .none { it?.accept(this, data) == false } @@ -151,7 +152,11 @@ class IrCompileTimeChecker( override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?): Boolean { if (!mode.canEvaluateEnumValue(expression, contextExpression)) return false - return expression.symbol.owner.initializerExpression?.accept(this, data) == true + // we want to avoid recursion in cases like "enum class E(val srt: String) { OK(OK.name) }" + if (visitedStack.contains(expression)) return true + return expression.asVisited { + expression.symbol.owner.initializerExpression?.accept(this, data) == true + } } override fun visitGetValue(expression: IrGetValue, data: Nothing?): Boolean { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt index b529f9cc6ee..b31cf9aa438 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt @@ -7,14 +7,12 @@ package org.jetbrains.kotlin.ir.interpreter.checker import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement -import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase import org.jetbrains.kotlin.ir.declarations.IrField import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl import org.jetbrains.kotlin.ir.interpreter.IrInterpreter import org.jetbrains.kotlin.ir.interpreter.isPrimitiveArray @@ -51,6 +49,17 @@ class IrConstTransformer( return this } + private fun IrExpression.canBeInterpreted(containingDeclaration: IrElement? = null): Boolean { + return try { + this.accept(IrCompileTimeChecker(containingDeclaration, mode = mode), null) + } catch (e: Throwable) { + if (suppressExceptions) { + return false + } + throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e) + } + } + private fun IrExpression.interpret(failAsError: Boolean): IrExpression { val result = try { interpreter.interpret(this, irFile) @@ -65,7 +74,7 @@ class IrConstTransformer( } override fun visitCall(expression: IrCall): IrExpression { - if (expression.accept(IrCompileTimeChecker(mode = mode), null)) { + if (expression.canBeInterpreted()) { return expression.interpret(failAsError = false) } return super.visitCall(expression) @@ -78,7 +87,7 @@ class IrConstTransformer( val expression = initializer?.expression ?: return declaration if (expression is IrConst<*>) return declaration val isConst = declaration.correspondingPropertySymbol?.owner?.isConst == true - if (isConst && expression.accept(IrCompileTimeChecker(declaration, mode), null)) { + if (isConst && expression.canBeInterpreted(declaration)) { initializer.expression = expression.interpret(failAsError = true) } @@ -124,7 +133,7 @@ class IrConstTransformer( } private fun IrExpression.transformSingleArg(expectedType: IrType): IrExpression { - if (this.accept(IrCompileTimeChecker(mode = mode), null)) { + if (this.canBeInterpreted()) { return this.interpret(failAsError = true).convertToConstIfPossible(expectedType) } else if (this is IrConstructorCall) { transformAnnotation(this) diff --git a/compiler/testData/ir/loweredIr/interpreter/enumRecursiveName.kt b/compiler/testData/ir/loweredIr/interpreter/enumRecursiveName.kt new file mode 100644 index 00000000000..0f91d139461 --- /dev/null +++ b/compiler/testData/ir/loweredIr/interpreter/enumRecursiveName.kt @@ -0,0 +1,13 @@ +// TARGET_BACKEND: JVM +// IGNORE_FIR_DIAGNOSTICS +// !DIAGNOSTICS: -UNINITIALIZED_ENUM_ENTRY + +enum class TestEnum(val testNaming: String) { + OK(OK.name), +} + +const val name = TestEnum.OK.name + +fun box(): String { + return name +}