From 40d224d5fefc7d3b833e86f2dbbc6a37acbe9e26 Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Fri, 4 Feb 2022 12:29:09 +0300 Subject: [PATCH] Change error message of `EXCEPTION_IN_CONST_VAL_INITIALIZER` --- .../jvm/diagnostics/JvmBackendErrors.kt | 2 +- .../jvm/lower/ConstEvaluationLowering.kt | 5 ++- .../kotlin/ir/interpreter/IrInterpreter.kt | 4 +-- .../interpreter/IrInterpreterConfiguration.kt | 2 ++ .../interpreter/IrInterpreterEnvironment.kt | 35 ++++++++++++++++--- .../kotlin/ir/interpreter/IrTreeBuildUtils.kt | 25 ------------- .../interpreter/exceptions/ExceptionUtils.kt | 2 +- .../ir/interpreter/state/ExceptionState.kt | 24 +++++++++---- .../exceptionFromInterpreter_ir.diag.txt | 12 ++----- .../exceptions/exceptionFromWrapper.kt | 3 +- 10 files changed, 63 insertions(+), 51 deletions(-) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmBackendErrors.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmBackendErrors.kt index ca113230091..ced0ba58c87 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmBackendErrors.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmBackendErrors.kt @@ -76,6 +76,6 @@ object KtDefaultJvmErrorMessages : BaseDiagnosticRendererFactory() { map.put(JvmBackendErrors.SCRIPT_CAPTURING_ENUM, "Enum class {0} captures the script class instance. Try to use class or anonymous object instead", STRING) map.put(JvmBackendErrors.SCRIPT_CAPTURING_ENUM_ENTRY, "Enum entry {0} captures the script class instance. Try to use class or anonymous object instead", STRING) - map.put(JvmBackendErrors.EXCEPTION_IN_CONST_VAL_INITIALIZER, "An exception occur while evaluating const value initializer: {0}", STRING) + map.put(JvmBackendErrors.EXCEPTION_IN_CONST_VAL_INITIALIZER, "Cannot evaluate constant expression: {0}", STRING) } } diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ConstEvaluationLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ConstEvaluationLowering.kt index fabaf5ceee9..51962e5580b 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ConstEvaluationLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ConstEvaluationLowering.kt @@ -10,6 +10,8 @@ import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.interpreter.IrInterpreter +import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration +import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode import org.jetbrains.kotlin.ir.interpreter.checker.IrConstTransformer import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmBackendErrors @@ -22,7 +24,8 @@ val constEvaluationPhase = makeIrModulePhase( // TODO make context common class ConstEvaluationLowering(val context: JvmBackendContext) : FileLoweringPass { - val interpreter = IrInterpreter(context.irBuiltIns) + val configuration = IrInterpreterConfiguration(printOnlyExceptionMessage = true) + val interpreter = IrInterpreter(IrInterpreterEnvironment(context.irBuiltIns, configuration), emptyMap()) override fun lower(irFile: IrFile) { val transformer = IrConstTransformer(interpreter, irFile, mode = EvaluationMode.ONLY_INTRINSIC_CONST) { element, error -> diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt index 2083eea5ae6..6618b8aa4ca 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt @@ -60,7 +60,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal callStack.popInstruction().handle() } - return callStack.popState().toIrExpression(expression).apply { callStack.dropFrame() } + return environment.stateToIrExpression(callStack.popState(), expression).apply { callStack.dropFrame() } } internal fun withNewCallStack(call: IrCall, init: IrInterpreter.() -> Any?): State { @@ -221,7 +221,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal // this check is used to be sure that object will be created once val objectState = when (constructorCall) { !is IrConstructorCall -> callStack.loadState(constructorCall.getThisReceiver()) - else -> (if (irClass.isSubclassOfThrowable()) ExceptionState(irClass, callStack.getStackTrace()) else Common(irClass)) + else -> (if (irClass.isSubclassOfThrowable()) ExceptionState(irClass, environment) else Common(irClass)) .apply { // must set object's state here, before actual initialization, to avoid cyclic evaluation if (irClass.isObject) environment.mapOfObjects[irClass.symbol] = this diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterConfiguration.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterConfiguration.kt index 8f4a0ad16d4..b3eac0130cd 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterConfiguration.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterConfiguration.kt @@ -17,4 +17,6 @@ class IrInterpreterConfiguration( val maxStack: Int = 10_000, val maxCommands: Int = 1_000_000, val createNonCompileTimeObjects: Boolean = false, + val printOnlyExceptionMessage: Boolean = false, + val collapseStackTraceFromJDK: Boolean = true, ) diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt index b1c41000f89..d1ba717be50 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt @@ -9,20 +9,21 @@ import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy import org.jetbrains.kotlin.ir.interpreter.stack.CallStack import org.jetbrains.kotlin.ir.interpreter.state.Common import org.jetbrains.kotlin.ir.interpreter.state.Complex +import org.jetbrains.kotlin.ir.interpreter.state.ExceptionState import org.jetbrains.kotlin.ir.interpreter.state.Primitive import org.jetbrains.kotlin.ir.interpreter.state.State import org.jetbrains.kotlin.ir.interpreter.state.Wrapper import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol -import org.jetbrains.kotlin.ir.types.IrSimpleType -import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.classOrNull -import org.jetbrains.kotlin.ir.types.typeOrNull +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.isSubclassOf import org.jetbrains.kotlin.ir.util.nameForIrSerialization @@ -116,6 +117,32 @@ class IrInterpreterEnvironment( } } + internal fun stateToIrExpression(state: State, original: IrExpression): IrExpression { + val start = original.startOffset + val end = original.endOffset + val type = original.type.makeNotNull() + return when (state) { + is Primitive<*> -> + when { + state.value == null -> state.value.toIrConst(type, start, end) + type.isPrimitiveType() || type.isString() -> state.value.toIrConst(type, start, end) + else -> original // TODO support for arrays + } + is ExceptionState -> { + val message = if (configuration.printOnlyExceptionMessage) state.getShortDescription() else "\n" + state.getFullDescription() + IrErrorExpressionImpl(original.startOffset, original.endOffset, original.type, message) + } + is Complex -> { + val stateType = state.irClass.defaultType + when { + stateType.isUnsignedType() -> (state.fields.values.single() as Primitive<*>).value.toIrConst(type, start, end) + else -> original + } + } + else -> original // TODO support + } + } + private fun IrClassSymbol.getIrClassOfReflectionFromList(name: String): IrClassSymbol? { val property = this.owner.declarations.singleOrNull { it.nameForIrSerialization.asString() == name } as? IrProperty val list = property?.getter?.returnType as? IrSimpleType diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt index 1e1b4d9fa4e..f000ce3e6ad 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt @@ -65,31 +65,6 @@ fun Any?.toIrConst(irType: IrType, startOffset: Int = SYNTHETIC_OFFSET, endOffse toIrConstOrNull(irType, startOffset, endOffset) ?: throw UnsupportedOperationException("Unsupported const element type ${irType.makeNotNull().render()}") -internal fun State.toIrExpression(expression: IrExpression): IrExpression { - val start = expression.startOffset - val end = expression.endOffset - val type = expression.type.makeNotNull() - return when (this) { - is Primitive<*> -> - when { - this.value == null -> this.value.toIrConst(type, start, end) - type.isPrimitiveType() || type.isString() -> this.value.toIrConst(type, start, end) - else -> expression // TODO support for arrays - } - is ExceptionState -> { - IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, "\n" + this.getFullDescription()) - } - is Complex -> { - val stateType = this.irClass.defaultType - when { - stateType.isUnsignedType() -> (this.fields.values.single() as Primitive<*>).value.toIrConst(type, start, end) - else -> expression - } - } - else -> expression // TODO support - } -} - internal fun IrFunction.createCall(origin: IrStatementOrigin? = null): IrCall { this as IrSimpleFunction return IrCallImpl(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, returnType, symbol, typeParameters.size, valueParameters.size, origin) diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/ExceptionUtils.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/ExceptionUtils.kt index ea6653220a3..44f0317f7d2 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/ExceptionUtils.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/ExceptionUtils.kt @@ -34,6 +34,6 @@ internal fun Throwable.handleUserException(environment: IrInterpreterEnvironment val exceptionName = this::class.java.simpleName val irExceptionClass = environment.irExceptions.firstOrNull { it.name.asString() == exceptionName } ?: environment.irBuiltIns.throwableClass.owner - environment.callStack.pushState(ExceptionState(this, irExceptionClass, environment.callStack.getStackTrace())) + environment.callStack.pushState(ExceptionState(this, irExceptionClass, environment.callStack.getStackTrace(), environment)) environment.callStack.dropFramesUntilTryCatch() } \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt index eafbf39b59c..67b4442ad3e 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.interpreter.state import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment import org.jetbrains.kotlin.ir.interpreter.getOriginalPropertyByName import org.jetbrains.kotlin.ir.interpreter.stack.Field import org.jetbrains.kotlin.ir.interpreter.stack.Fields @@ -39,11 +40,13 @@ internal class ExceptionState private constructor( if (!fields.containsKey(causeProperty.symbol)) setCause(null) } - constructor(irClass: IrClass, stackTrace: List) : this(irClass, mutableMapOf(), stackTrace) + constructor( + irClass: IrClass, environment: IrInterpreterEnvironment + ) : this(irClass, mutableMapOf(), environment.callStack.getStackTrace()) constructor( - exception: Throwable, irClass: IrClass, stackTrace: List - ) : this(irClass, evaluateFields(exception, irClass, stackTrace), stackTrace + evaluateAdditionalStackTrace(exception)) { + exception: Throwable, irClass: IrClass, stackTrace: List, environment: IrInterpreterEnvironment + ) : this(irClass, evaluateFields(exception, irClass, environment), stackTrace + evaluateAdditionalStackTrace(exception, environment)) { setCause(null) // TODO check this fact if (irClass.name.asString() != exception::class.java.simpleName) { // ir class wasn't found in classpath, a stub was passed => need to save java class hierarchy @@ -88,6 +91,10 @@ internal class ExceptionState private constructor( setField(causeProperty.symbol, causeValue ?: Primitive.nullStateOfType(causeProperty.getter!!.returnType)) } + fun getShortDescription(): String { + return message.let { if (it?.isNotEmpty() == true) it else "???" } + } + fun getFullDescription(): String { // TODO remainder of the stack trace with "..." val message = message.let { if (it?.isNotEmpty() == true) ": $it" else "" } @@ -102,18 +109,19 @@ internal class ExceptionState private constructor( override fun toString(): String = message?.let { "$exceptionFqName: $it" } ?: exceptionFqName companion object { - private fun evaluateFields(exception: Throwable, irClass: IrClass, stackTrace: List): Fields { + private fun evaluateFields(exception: Throwable, irClass: IrClass, environment: IrInterpreterEnvironment): Fields { + val stackTrace = environment.callStack.getStackTrace() val messageProperty = irClass.getOriginalPropertyByName("message") val causeProperty = irClass.getOriginalPropertyByName("cause") val messageVar = messageProperty.symbol to Primitive(exception.message, messageProperty.getter!!.returnType) val causeVar = exception.cause?.let { - causeProperty.symbol to ExceptionState(it, irClass, stackTrace + it.stackTrace.reversed().map { "at $it" }) + causeProperty.symbol to ExceptionState(it, irClass, stackTrace + it.stackTrace.reversed().map { "at $it" }, environment) } return causeVar?.let { mutableMapOf(messageVar, it) } ?: mutableMapOf(messageVar) } - private fun evaluateAdditionalStackTrace(e: Throwable): List { + private fun evaluateAdditionalStackTrace(e: Throwable, environment: IrInterpreterEnvironment): List { // TODO do we really need this?... It will point to JVM stdlib val additionalStack = mutableListOf() if (e.stackTrace.any { it.className == "java.lang.invoke.MethodHandle" }) { @@ -137,6 +145,10 @@ internal class ExceptionState private constructor( cause = cause.cause } } + + if (environment.configuration.collapseStackTraceFromJDK && additionalStack.isNotEmpty()) { + return listOf("at ") + } return additionalStack } } diff --git a/compiler/testData/diagnostics/firTestWithJvmBackend/exceptionFromInterpreter_ir.diag.txt b/compiler/testData/diagnostics/firTestWithJvmBackend/exceptionFromInterpreter_ir.diag.txt index 16e4a928ecb..4b1f802bb26 100644 --- a/compiler/testData/diagnostics/firTestWithJvmBackend/exceptionFromInterpreter_ir.diag.txt +++ b/compiler/testData/diagnostics/firTestWithJvmBackend/exceptionFromInterpreter_ir.diag.txt @@ -1,11 +1,5 @@ -/exceptionFromInterpreter_ir.kt:7:26: error: An exception occur while evaluating const value initializer: -Exception java.lang.ArithmeticException: / by zero - at ExceptionFromInterpreter_irKt.(exceptionFromInterpreter_ir.kt:7) +/exceptionFromInterpreter_ir.kt:6:26: error: Cannot evaluate constant expression: / by zero -/exceptionFromInterpreter_ir.kt:8:39: error: An exception occur while evaluating const value initializer: -Exception java.lang.IllegalArgumentException: marginPrefix must be non-blank string. - at ExceptionFromInterpreter_irKt.(exceptionFromInterpreter_ir.kt:8) +/exceptionFromInterpreter_ir.kt:7:39: error: Cannot evaluate constant expression: marginPrefix must be non-blank string. -/exceptionFromInterpreter_ir.kt:12:4: error: An exception occur while evaluating const value initializer: -Exception java.lang.ArithmeticException: / by zero - at ExceptionFromInterpreter_irKt.(exceptionFromInterpreter_ir.kt:12) \ No newline at end of file +/exceptionFromInterpreter_ir.kt:11:4: error: Cannot evaluate constant expression: / by zero \ No newline at end of file diff --git a/compiler/testData/ir/interpreter/exceptions/exceptionFromWrapper.kt b/compiler/testData/ir/interpreter/exceptions/exceptionFromWrapper.kt index 307e0d3f4fb..f7523176648 100644 --- a/compiler/testData/ir/interpreter/exceptions/exceptionFromWrapper.kt +++ b/compiler/testData/ir/interpreter/exceptions/exceptionFromWrapper.kt @@ -6,7 +6,6 @@ fun append(sb: StringBuilder, value: CharSequence, start: Int, end: Int): String const val a = append(StringBuilder("Some string with not zero length"), "!!!", 0, 3) const val b = at ExceptionFromWrapperKt.append(exceptionFromWrapper.kt:3) at ExceptionFromWrapperKt.(exceptionFromWrapper.kt:7)`!>append(StringBuilder("Some string with not zero length"), "!!!", -1, 0)