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? {
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<Value?>) -> 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<Value?> {
private fun calculateMainMethodCallArguments(
variableFinder: VariableFinder,
compiledData: CompiledDataDescriptor,
status: EvaluationStatus
): List<Value?> {
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")
}
@@ -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<ClassToLoad>,
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<SimpleFunctionDescriptor> {
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.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<DeclarationDescriptor, Smart>()
private val crossingBounds = mutableSetOf<Dumb>()
@@ -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")
}
@@ -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
@@ -67,6 +67,7 @@ class KotlinDebuggerEvaluator(
return null
}
@Suppress("unused")
enum class EvaluationType(val clazz: Class<*>?) {
WATCH(WatchNodeImpl::class.java),
WINDOW(EvaluatingExpressionRootNode::class.java),