From e0c4a1c6f195e8aca09e5d1f6b920a4c1af02e33 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 12 Jul 2019 16:38:48 +0900 Subject: [PATCH] Debugger: Improve error diagnostics in FUS --- .../evaluate/KotlinEvaluatorBuilder.kt | 54 +++++++++++++------ .../compilation/CodeFragmentCompiler.kt | 9 +--- .../CodeFragmentParameterAnalyzer.kt | 7 ++- .../evaluate/evaluate/EvaluationStatus.kt | 14 +++++ .../evaluate/KotlinDebuggerEvaluator.kt | 1 + 5 files changed, 61 insertions(+), 24 deletions(-) diff --git a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt index 338605c5b53..f7aad2a74b5 100644 --- a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt @@ -93,7 +93,7 @@ object KotlinEvaluatorBuilder : EvaluatorBuilder { } } -class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: SourcePosition?) : Evaluator { +class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePosition: SourcePosition?) : Evaluator { override fun evaluate(context: EvaluationContextImpl): Any? { if (codeFragment.text.isEmpty()) { return context.debugProcess.virtualMachineProxy.mirrorOfVoid() @@ -124,6 +124,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour private fun evaluateWithStatus(context: EvaluationContextImpl, status: EvaluationStatus): Any? { runReadAction { if (DumbService.getInstance(codeFragment.project).isDumb) { + status.error(EvaluationError.DumbMode) evaluationException("Code fragment evaluation is not available in the dumb mode") } } @@ -190,20 +191,20 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour val result = if (classLoaderRef != null) { try { status.usedEvaluator(EvaluationStatus.EvaluatorType.Bytecode) - return evaluateWithCompilation(context, compiledData, classLoaderRef) + return evaluateWithCompilation(context, compiledData, classLoaderRef, status) } catch (e: Throwable) { status.compilingEvaluatorFailed() LOG.warn("Compiling evaluator failed", e) status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j) - evaluateWithEval4J(context, compiledData, classLoaderRef) + evaluateWithEval4J(context, compiledData, classLoaderRef, status) } } else { status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j) - evaluateWithEval4J(context, compiledData, classLoaderRef) + evaluateWithEval4J(context, compiledData, classLoaderRef, status) } - return result.toJdiValue(context) + return result.toJdiValue(context, status) } private fun compileCodeFragment(context: ExecutionContext, status: EvaluationStatus): CompiledDataDescriptor { @@ -223,7 +224,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour val moduleDescriptor = analysisResult.moduleDescriptor try { - val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor) + val result = CodeFragmentCompiler(context, status).compile(codeFragment, bindingContext, moduleDescriptor) return createCompiledDataDescriptor(result, sourcePosition) } catch (e: Throwable) { status.error(EvaluationError.BackendException) @@ -265,6 +266,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour try { AnalyzingUtils.checkForSyntacticErrors(codeFragment) } catch (e: IllegalArgumentException) { + status.error(EvaluationError.ErrorElementOccurred) evaluationException(e.message ?: e.toString()) } @@ -296,9 +298,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour private fun evaluateWithCompilation( context: ExecutionContext, compiledData: CompiledDataDescriptor, - classLoader: ClassLoaderReference + classLoader: ClassLoaderReference, + status: EvaluationStatus ): Value? { - return runEvaluation(context, compiledData, classLoader) { args -> + return runEvaluation(context, compiledData, classLoader, status) { args -> val mainClassType = context.findClass(GENERATED_CLASS_NAME, classLoader) as? ClassType ?: error("Can not find class \"$GENERATED_CLASS_NAME\"") val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME } @@ -310,13 +313,14 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour private fun evaluateWithEval4J( context: ExecutionContext, compiledData: CompiledDataDescriptor, - classLoader: ClassLoaderReference? + classLoader: ClassLoaderReference?, + status: EvaluationStatus ): InterpreterResult { val mainClassBytecode = compiledData.mainClass.bytes val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) } val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME } - return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args -> + return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader, status) { args -> val vm = context.vm.virtualMachine val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isSuspended } ?: error("Can not find a thread to run evaluation on") @@ -330,6 +334,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour context: ExecutionContext, compiledData: CompiledDataDescriptor, classLoader: ClassLoaderReference?, + status: EvaluationStatus, block: (List) -> T ): T { // Preload additional classes @@ -343,7 +348,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } val variableFinder = VariableFinder(context) - val args = calculateMainMethodCallArguments(variableFinder, compiledData) + val args = calculateMainMethodCallArguments(variableFinder, compiledData, status) val result = block(args) @@ -367,7 +372,11 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } } - private fun calculateMainMethodCallArguments(variableFinder: VariableFinder, compiledData: CompiledDataDescriptor): List { + private fun calculateMainMethodCallArguments( + variableFinder: VariableFinder, + compiledData: CompiledDataDescriptor, + status: EvaluationStatus + ): List { val asmValueParameters = compiledData.mainMethodSignature.parameterTypes val valueParameters = compiledData.parameters require(asmValueParameters.size == valueParameters.size) @@ -387,14 +396,20 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } if (parameter.kind == CodeFragmentParameter.Kind.COROUTINE_CONTEXT) { + status.error(EvaluationError.CoroutineContextUnavailable) evaluationException("'coroutineContext' is not available") } else if (parameter in compiledData.crossingBounds) { + status.error(EvaluationError.ParameterNotCaptured) evaluationException("'$name' is not captured") } else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) { + status.error(EvaluationError.BackingFieldNotFound) evaluationException("Cannot find the backing field '${parameter.name}'") } else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) { + status.error(EvaluationError.InsideDefaultMethod) evaluationException("Parameter evaluation is not supported for '\$default' methods") } else { + status.error(EvaluationError.CannotFindVariable) + throw VariableFinder.variableNotFound(variableFinder.context, buildString { append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className) }) @@ -413,20 +428,27 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER) - private fun InterpreterResult.toJdiValue(context: ExecutionContext): Value? { + private fun InterpreterResult.toJdiValue(context: ExecutionContext, status: EvaluationStatus): Value? { val jdiValue = when (this) { is ValueReturned -> result is ExceptionThrown -> { when { - this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE -> + this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE -> { + status.error(EvaluationError.Eval4JExceptionFromEvaluatedCode) evaluationException(InvocationException(this.exception.value as ObjectReference)) + } this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE -> throw exception.value as Throwable - else -> + else -> { + status.error(EvaluationError.Eval4JUnknownException) evaluationException(exception.toString()) + } } } - is AbnormalTermination -> evaluationException(message) + is AbnormalTermination -> { + status.error(EvaluationError.Eval4JAbnormalTermination) + evaluationException(message) + } else -> throw IllegalStateException("Unknown result value produced by eval4j") } diff --git a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt index c01e67f7570..f933ef5c1a9 100644 --- a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt +++ b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt @@ -6,10 +6,8 @@ package org.jetbrains.kotlin.idea.debugger.evaluate.compilation import org.jetbrains.kotlin.backend.common.output.OutputFile -import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.CodeFragmentCodegen.Companion.getSharedTypeIfApplicable -import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.CompilerConfiguration @@ -43,9 +41,8 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.SimpleType import org.jetbrains.kotlin.utils.Printer -import org.jetbrains.org.objectweb.asm.Type -class CodeFragmentCompiler(private val executionContext: ExecutionContext) { +class CodeFragmentCompiler(private val executionContext: ExecutionContext, private val status: EvaluationStatus) { data class CompilationResult( val classes: List, val parameterInfo: CodeFragmentParameterInfo, @@ -80,7 +77,7 @@ class CodeFragmentCompiler(private val executionContext: ExecutionContext) { bindingContext, listOf(codeFragment), compilerConfiguration ).build() - val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext).analyze() + val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext, status).analyze() val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment( codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME), parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator @@ -207,8 +204,6 @@ class CodeFragmentCompiler(private val executionContext: ExecutionContext) { } } - - private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() { override fun getContributedFunctions(name: Name, location: LookupLocation): Collection { return if (name == methodDescriptor.name) { diff --git a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt index abfe96367b6..9a7c7a818ac 100644 --- a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt +++ b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt @@ -14,6 +14,8 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor +import org.jetbrains.kotlin.idea.debugger.evaluate.EvaluationError +import org.jetbrains.kotlin.idea.debugger.evaluate.EvaluationStatus import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.* import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME @@ -49,7 +51,8 @@ class CodeFragmentParameterInfo( class CodeFragmentParameterAnalyzer( private val context: ExecutionContext, private val codeFragment: KtCodeFragment, - private val bindingContext: BindingContext + private val bindingContext: BindingContext, + private val evaluationStatus: EvaluationStatus ) { private val parameters = LinkedHashMap() private val crossingBounds = mutableSetOf() @@ -190,6 +193,7 @@ class CodeFragmentParameterAnalyzer( if (resolvedCall != null) { val descriptor = resolvedCall.resultingDescriptor if (descriptor is FunctionDescriptor && descriptor.isSuspend) { + evaluationStatus.error(EvaluationError.SuspendCall) throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'suspend' calls is not supported") } } @@ -275,6 +279,7 @@ class CodeFragmentParameterAnalyzer( private fun processSimpleNameExpression(target: DeclarationDescriptor, expression: KtSimpleNameExpression): Smart? { if (target is ValueParameterDescriptor && target.isCrossinline) { + evaluationStatus.error(EvaluationError.CrossInlineLambda) throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported") } diff --git a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/evaluate/EvaluationStatus.kt b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/evaluate/EvaluationStatus.kt index 74c9a906d30..294b39af792 100644 --- a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/evaluate/EvaluationStatus.kt +++ b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/evaluate/EvaluationStatus.kt @@ -52,6 +52,7 @@ class EvaluationStatus { enum class EvaluationError { DebuggerNotAttached, + DumbMode, NoFrameProxy, ThreadNotAvailable, ThreadNotSuspended, @@ -61,7 +62,20 @@ enum class EvaluationError { EvaluateException, SpecialException, GenericException, + CannotFindVariable, + CoroutineContextUnavailable, + ParameterNotCaptured, + InsideDefaultMethod, + BackingFieldNotFound, + SuspendCall, + CrossInlineLambda, + + Eval4JExceptionFromEvaluatedCode, + Eval4JAbnormalTermination, + Eval4JUnknownException, + + ErrorElementOccurred, FrontendException, BackendException, ErrorsInCode diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerEvaluator.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerEvaluator.kt index 4fe79f808b3..c61f060b179 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerEvaluator.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerEvaluator.kt @@ -67,6 +67,7 @@ class KotlinDebuggerEvaluator( return null } + @Suppress("unused") enum class EvaluationType(val clazz: Class<*>?) { WATCH(WatchNodeImpl::class.java), WINDOW(EvaluatingExpressionRootNode::class.java),