Debugger: Improve error diagnostics in FUS

This commit is contained in:
Yan Zhulanow
2019-07-12 16:38:48 +09:00
parent a20014a7e6
commit e0c4a1c6f1
5 changed files with 61 additions and 24 deletions
@@ -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? { override fun evaluate(context: EvaluationContextImpl): Any? {
if (codeFragment.text.isEmpty()) { if (codeFragment.text.isEmpty()) {
return context.debugProcess.virtualMachineProxy.mirrorOfVoid() 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? { private fun evaluateWithStatus(context: EvaluationContextImpl, status: EvaluationStatus): Any? {
runReadAction { runReadAction {
if (DumbService.getInstance(codeFragment.project).isDumb) { if (DumbService.getInstance(codeFragment.project).isDumb) {
status.error(EvaluationError.DumbMode)
evaluationException("Code fragment evaluation is not available in the dumb mode") 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) { val result = if (classLoaderRef != null) {
try { try {
status.usedEvaluator(EvaluationStatus.EvaluatorType.Bytecode) status.usedEvaluator(EvaluationStatus.EvaluatorType.Bytecode)
return evaluateWithCompilation(context, compiledData, classLoaderRef) return evaluateWithCompilation(context, compiledData, classLoaderRef, status)
} catch (e: Throwable) { } catch (e: Throwable) {
status.compilingEvaluatorFailed() status.compilingEvaluatorFailed()
LOG.warn("Compiling evaluator failed", e) LOG.warn("Compiling evaluator failed", e)
status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j) status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j)
evaluateWithEval4J(context, compiledData, classLoaderRef) evaluateWithEval4J(context, compiledData, classLoaderRef, status)
} }
} else { } else {
status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j) 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 { private fun compileCodeFragment(context: ExecutionContext, status: EvaluationStatus): CompiledDataDescriptor {
@@ -223,7 +224,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val moduleDescriptor = analysisResult.moduleDescriptor val moduleDescriptor = analysisResult.moduleDescriptor
try { try {
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor) val result = CodeFragmentCompiler(context, status).compile(codeFragment, bindingContext, moduleDescriptor)
return createCompiledDataDescriptor(result, sourcePosition) return createCompiledDataDescriptor(result, sourcePosition)
} catch (e: Throwable) { } catch (e: Throwable) {
status.error(EvaluationError.BackendException) status.error(EvaluationError.BackendException)
@@ -265,6 +266,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
try { try {
AnalyzingUtils.checkForSyntacticErrors(codeFragment) AnalyzingUtils.checkForSyntacticErrors(codeFragment)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
status.error(EvaluationError.ErrorElementOccurred)
evaluationException(e.message ?: e.toString()) evaluationException(e.message ?: e.toString())
} }
@@ -296,9 +298,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
private fun evaluateWithCompilation( private fun evaluateWithCompilation(
context: ExecutionContext, context: ExecutionContext,
compiledData: CompiledDataDescriptor, compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference classLoader: ClassLoaderReference,
status: EvaluationStatus
): Value? { ): Value? {
return runEvaluation(context, compiledData, classLoader) { args -> return runEvaluation(context, compiledData, classLoader, status) { args ->
val mainClassType = context.findClass(GENERATED_CLASS_NAME, classLoader) as? ClassType val mainClassType = context.findClass(GENERATED_CLASS_NAME, classLoader) as? ClassType
?: error("Can not find class \"$GENERATED_CLASS_NAME\"") ?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_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( private fun evaluateWithEval4J(
context: ExecutionContext, context: ExecutionContext,
compiledData: CompiledDataDescriptor, compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference? classLoader: ClassLoaderReference?,
status: EvaluationStatus
): InterpreterResult { ): InterpreterResult {
val mainClassBytecode = compiledData.mainClass.bytes val mainClassBytecode = compiledData.mainClass.bytes
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) } val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) }
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME } 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 vm = context.vm.virtualMachine
val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isSuspended } val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isSuspended }
?: error("Can not find a thread to run evaluation on") ?: error("Can not find a thread to run evaluation on")
@@ -330,6 +334,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
context: ExecutionContext, context: ExecutionContext,
compiledData: CompiledDataDescriptor, compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference?, classLoader: ClassLoaderReference?,
status: EvaluationStatus,
block: (List<Value?>) -> T block: (List<Value?>) -> T
): T { ): T {
// Preload additional classes // Preload additional classes
@@ -343,7 +348,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
val variableFinder = VariableFinder(context) val variableFinder = VariableFinder(context)
val args = calculateMainMethodCallArguments(variableFinder, compiledData) val args = calculateMainMethodCallArguments(variableFinder, compiledData, status)
val result = block(args) val result = block(args)
@@ -367,7 +372,11 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
} }
private fun calculateMainMethodCallArguments(variableFinder: VariableFinder, compiledData: CompiledDataDescriptor): List<Value?> { private fun calculateMainMethodCallArguments(
variableFinder: VariableFinder,
compiledData: CompiledDataDescriptor,
status: EvaluationStatus
): List<Value?> {
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
val valueParameters = compiledData.parameters val valueParameters = compiledData.parameters
require(asmValueParameters.size == valueParameters.size) require(asmValueParameters.size == valueParameters.size)
@@ -387,14 +396,20 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
if (parameter.kind == CodeFragmentParameter.Kind.COROUTINE_CONTEXT) { if (parameter.kind == CodeFragmentParameter.Kind.COROUTINE_CONTEXT) {
status.error(EvaluationError.CoroutineContextUnavailable)
evaluationException("'coroutineContext' is not available") evaluationException("'coroutineContext' is not available")
} else if (parameter in compiledData.crossingBounds) { } else if (parameter in compiledData.crossingBounds) {
status.error(EvaluationError.ParameterNotCaptured)
evaluationException("'$name' is not captured") evaluationException("'$name' is not captured")
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) { } else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
status.error(EvaluationError.BackingFieldNotFound)
evaluationException("Cannot find the backing field '${parameter.name}'") evaluationException("Cannot find the backing field '${parameter.name}'")
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) { } else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
status.error(EvaluationError.InsideDefaultMethod)
evaluationException("Parameter evaluation is not supported for '\$default' methods") evaluationException("Parameter evaluation is not supported for '\$default' methods")
} else { } else {
status.error(EvaluationError.CannotFindVariable)
throw VariableFinder.variableNotFound(variableFinder.context, buildString { throw VariableFinder.variableNotFound(variableFinder.context, buildString {
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className) 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 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) { val jdiValue = when (this) {
is ValueReturned -> result is ValueReturned -> result
is ExceptionThrown -> { is ExceptionThrown -> {
when { 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)) evaluationException(InvocationException(this.exception.value as ObjectReference))
}
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE -> this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
throw exception.value as Throwable throw exception.value as Throwable
else -> else -> {
status.error(EvaluationError.Eval4JUnknownException)
evaluationException(exception.toString()) evaluationException(exception.toString())
}
} }
} }
is AbnormalTermination -> evaluationException(message) is AbnormalTermination -> {
status.error(EvaluationError.Eval4JAbnormalTermination)
evaluationException(message)
}
else -> throw IllegalStateException("Unknown result value produced by eval4j") else -> throw IllegalStateException("Unknown result value produced by eval4j")
} }
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
import org.jetbrains.kotlin.backend.common.output.OutputFile 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.*
import org.jetbrains.kotlin.codegen.CodeFragmentCodegen.Companion.getSharedTypeIfApplicable 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.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.CompilerConfiguration 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.KotlinType
import org.jetbrains.kotlin.types.SimpleType import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.utils.Printer 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( data class CompilationResult(
val classes: List<ClassToLoad>, val classes: List<ClassToLoad>,
val parameterInfo: CodeFragmentParameterInfo, val parameterInfo: CodeFragmentParameterInfo,
@@ -80,7 +77,7 @@ class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
bindingContext, listOf(codeFragment), compilerConfiguration bindingContext, listOf(codeFragment), compilerConfiguration
).build() ).build()
val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext).analyze() val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext, status).analyze()
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment( val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME), codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
@@ -207,8 +204,6 @@ class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
} }
} }
private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() { private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() {
override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> { override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
return if (name == methodDescriptor.name) { return if (name == methodDescriptor.name) {
@@ -14,6 +14,8 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor 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.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.* import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
@@ -49,7 +51,8 @@ class CodeFragmentParameterInfo(
class CodeFragmentParameterAnalyzer( class CodeFragmentParameterAnalyzer(
private val context: ExecutionContext, private val context: ExecutionContext,
private val codeFragment: KtCodeFragment, private val codeFragment: KtCodeFragment,
private val bindingContext: BindingContext private val bindingContext: BindingContext,
private val evaluationStatus: EvaluationStatus
) { ) {
private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>() private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>()
private val crossingBounds = mutableSetOf<Dumb>() private val crossingBounds = mutableSetOf<Dumb>()
@@ -190,6 +193,7 @@ class CodeFragmentParameterAnalyzer(
if (resolvedCall != null) { if (resolvedCall != null) {
val descriptor = resolvedCall.resultingDescriptor val descriptor = resolvedCall.resultingDescriptor
if (descriptor is FunctionDescriptor && descriptor.isSuspend) { if (descriptor is FunctionDescriptor && descriptor.isSuspend) {
evaluationStatus.error(EvaluationError.SuspendCall)
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'suspend' calls is not supported") throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'suspend' calls is not supported")
} }
} }
@@ -275,6 +279,7 @@ class CodeFragmentParameterAnalyzer(
private fun processSimpleNameExpression(target: DeclarationDescriptor, expression: KtSimpleNameExpression): Smart? { private fun processSimpleNameExpression(target: DeclarationDescriptor, expression: KtSimpleNameExpression): Smart? {
if (target is ValueParameterDescriptor && target.isCrossinline) { if (target is ValueParameterDescriptor && target.isCrossinline) {
evaluationStatus.error(EvaluationError.CrossInlineLambda)
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported") throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported")
} }
@@ -52,6 +52,7 @@ class EvaluationStatus {
enum class EvaluationError { enum class EvaluationError {
DebuggerNotAttached, DebuggerNotAttached,
DumbMode,
NoFrameProxy, NoFrameProxy,
ThreadNotAvailable, ThreadNotAvailable,
ThreadNotSuspended, ThreadNotSuspended,
@@ -61,7 +62,20 @@ enum class EvaluationError {
EvaluateException, EvaluateException,
SpecialException, SpecialException,
GenericException, GenericException,
CannotFindVariable,
CoroutineContextUnavailable,
ParameterNotCaptured,
InsideDefaultMethod,
BackingFieldNotFound,
SuspendCall,
CrossInlineLambda,
Eval4JExceptionFromEvaluatedCode,
Eval4JAbnormalTermination,
Eval4JUnknownException,
ErrorElementOccurred,
FrontendException, FrontendException,
BackendException, BackendException,
ErrorsInCode ErrorsInCode
@@ -67,6 +67,7 @@ class KotlinDebuggerEvaluator(
return null return null
} }
@Suppress("unused")
enum class EvaluationType(val clazz: Class<*>?) { enum class EvaluationType(val clazz: Class<*>?) {
WATCH(WatchNodeImpl::class.java), WATCH(WatchNodeImpl::class.java),
WINDOW(EvaluatingExpressionRootNode::class.java), WINDOW(EvaluatingExpressionRootNode::class.java),