Debugger: Improve error diagnostics in FUS
This commit is contained in:
+38
-16
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
+2
-7
@@ -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) {
|
||||
|
||||
+6
-1
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -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
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ class KotlinDebuggerEvaluator(
|
||||
return null
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
enum class EvaluationType(val clazz: Class<*>?) {
|
||||
WATCH(WatchNodeImpl::class.java),
|
||||
WINDOW(EvaluatingExpressionRootNode::class.java),
|
||||
|
||||
Reference in New Issue
Block a user