Debugger: Implement error reporting for evaluator
This commit is contained in:
+9
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.DebuggerContext
|
import com.intellij.debugger.DebuggerContext
|
||||||
import com.intellij.debugger.engine.JavaStackFrame
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
import com.intellij.debugger.engine.JavaValue
|
import com.intellij.debugger.engine.JavaValue
|
||||||
@@ -50,6 +51,7 @@ import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
|||||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
import org.jetbrains.kotlin.utils.getSafe
|
import org.jetbrains.kotlin.utils.getSafe
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerEvaluator
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -60,6 +62,13 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
|
|
||||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||||
|
|
||||||
|
private val kotlinEvaluator by lazy {
|
||||||
|
val debugProcess = descriptor.debugProcess as DebugProcessImpl // Cast as in JavaStackFrame
|
||||||
|
KotlinDebuggerEvaluator(debugProcess, this@KotlinStackFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEvaluator() = kotlinEvaluator
|
||||||
|
|
||||||
override fun superBuildVariables(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
override fun superBuildVariables(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
||||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||||
return super.superBuildVariables(evaluationContext, children)
|
return super.superBuildVariables(evaluationContext, children)
|
||||||
|
|||||||
+10
-2
@@ -68,7 +68,7 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
|
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
|
||||||
supplyDebugLabels(codeFragment, context)
|
supplyDebugInformation(item, codeFragment, context)
|
||||||
|
|
||||||
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
||||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||||
@@ -139,10 +139,16 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
return codeFragment
|
return codeFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun supplyDebugLabels(codeFragment: KtCodeFragment, context: PsiElement?) {
|
private fun supplyDebugInformation(item: TextWithImports, codeFragment: KtCodeFragment, context: PsiElement?) {
|
||||||
val project = codeFragment.project
|
val project = codeFragment.project
|
||||||
val debugProcess = getDebugProcess(project, context) ?: return
|
val debugProcess = getDebugProcess(project, context) ?: return
|
||||||
|
|
||||||
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
|
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
|
||||||
|
|
||||||
|
val evaluator = debugProcess.session.xDebugSession?.currentStackFrame?.evaluator
|
||||||
|
if (evaluator is KotlinDebuggerEvaluator) {
|
||||||
|
codeFragment.putUserData(EVALUATION_TYPE, evaluator.getType(item))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDebugProcess(project: Project, context: PsiElement?): DebugProcessImpl? {
|
private fun getDebugProcess(project: Project, context: PsiElement?): DebugProcessImpl? {
|
||||||
@@ -295,6 +301,8 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
@get:TestOnly
|
@get:TestOnly
|
||||||
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
|
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
|
||||||
|
|
||||||
|
val EVALUATION_TYPE: Key<KotlinDebuggerEvaluator.EvaluationType> = Key.create("DEBUG_EVALUATION_TYPE")
|
||||||
|
|
||||||
const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_"
|
const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_"
|
||||||
const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_"
|
const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_"
|
||||||
|
|
||||||
|
|||||||
+86
-39
@@ -22,6 +22,7 @@ import com.intellij.debugger.engine.evaluation.EvaluateException
|
|||||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
import com.intellij.debugger.engine.evaluation.expression.*
|
import com.intellij.debugger.engine.evaluation.expression.*
|
||||||
|
import com.intellij.lang.java.JavaLanguage
|
||||||
import com.intellij.openapi.diagnostic.Attachment
|
import com.intellij.openapi.diagnostic.Attachment
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
@@ -42,6 +43,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
|||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.diagnostics.Severity
|
import org.jetbrains.kotlin.diagnostics.Severity
|
||||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.core.util.attachmentByPsiFile
|
import org.jetbrains.kotlin.idea.core.util.attachmentByPsiFile
|
||||||
import org.jetbrains.kotlin.idea.core.util.mergeAttachments
|
import org.jetbrains.kotlin.idea.core.util.mergeAttachments
|
||||||
import org.jetbrains.kotlin.idea.core.util.runInReadActionWithWriteActionPriorityWithPCE
|
import org.jetbrains.kotlin.idea.core.util.runInReadActionWithWriteActionPriorityWithPCE
|
||||||
@@ -65,6 +67,8 @@ import org.jetbrains.kotlin.resolve.isInlineClassType
|
|||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.org.objectweb.asm.*
|
import org.jetbrains.org.objectweb.asm.*
|
||||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.EvaluationStatus.EvaluationContextLanguage
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.ClassLoadingResult
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal val LOG = Logger.getInstance(KotlinEvaluator::class.java)
|
internal val LOG = Logger.getInstance(KotlinEvaluator::class.java)
|
||||||
@@ -93,35 +97,68 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
return context.debugProcess.virtualMachineProxy.mirrorOfVoid()
|
return context.debugProcess.virtualMachineProxy.mirrorOfVoid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val status = EvaluationStatus()
|
||||||
|
|
||||||
|
val evaluationType = codeFragment.getUserData(KotlinCodeFragmentFactory.EVALUATION_TYPE)
|
||||||
|
if (evaluationType != null) {
|
||||||
|
status.evaluationType(evaluationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
val language = when (codeFragment.language) {
|
||||||
|
KotlinLanguage.INSTANCE -> EvaluationContextLanguage.Kotlin
|
||||||
|
JavaLanguage.INSTANCE -> EvaluationContextLanguage.Java
|
||||||
|
else -> EvaluationContextLanguage.Other
|
||||||
|
}
|
||||||
|
|
||||||
|
status.contextLanguage(language)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return evaluateWithStatus(context, status)
|
||||||
|
} finally {
|
||||||
|
status.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun evaluateWithStatus(context: EvaluationContextImpl, status: EvaluationStatus): Any? {
|
||||||
if (DumbService.getInstance(codeFragment.project).isDumb) {
|
if (DumbService.getInstance(codeFragment.project).isDumb) {
|
||||||
evaluationException("Code fragment evaluation is not available in the dumb mode")
|
evaluationException("Code fragment evaluation is not available in the dumb mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
val frameProxy = context.frameProxy
|
val frameProxy = context.frameProxy ?: run {
|
||||||
?: evaluationException("Cannot evaluate a code fragment: frame proxy is not available")
|
status.error(EvaluationError.NoFrameProxy)
|
||||||
|
evaluationException("Cannot evaluate a code fragment: frame proxy is not available")
|
||||||
|
}
|
||||||
|
|
||||||
val operatingThread = context.suspendContext.thread
|
val operatingThread = context.suspendContext.thread ?: run {
|
||||||
?: evaluationException("Cannot evaluate a code fragment: thread is not available")
|
status.error(EvaluationError.ThreadNotAvailable)
|
||||||
|
evaluationException("Cannot evaluate a code fragment: thread is not available")
|
||||||
|
}
|
||||||
|
|
||||||
if (!operatingThread.isSuspended) {
|
if (!operatingThread.isSuspended) {
|
||||||
|
status.error(EvaluationError.ThreadNotSuspended)
|
||||||
evaluationException("Evaluation is available only for the suspended threads")
|
evaluationException("Evaluation is available only for the suspended threads")
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val executionContext = ExecutionContext(context, frameProxy)
|
val executionContext = ExecutionContext(context, frameProxy)
|
||||||
return evaluateSafe(executionContext)
|
return evaluateSafe(executionContext, status)
|
||||||
} catch (e: EvaluateException) {
|
} catch (e: EvaluateException) {
|
||||||
|
status.error(EvaluationError.EvaluateException)
|
||||||
throw e
|
throw e
|
||||||
} catch (e: ProcessCanceledException) {
|
} catch (e: ProcessCanceledException) {
|
||||||
|
status.error(EvaluationError.ProcessCancelledException)
|
||||||
evaluationException(e)
|
evaluationException(e)
|
||||||
} catch (e: Eval4JInterpretingException) {
|
} catch (e: Eval4JInterpretingException) {
|
||||||
|
status.error(EvaluationError.InterpretingException)
|
||||||
evaluationException(e.cause)
|
evaluationException(e.cause)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val isSpecialException = isSpecialException(e)
|
val isSpecialException = isSpecialException(e)
|
||||||
if (isSpecialException) {
|
if (isSpecialException) {
|
||||||
|
status.error(EvaluationError.SpecialException)
|
||||||
evaluationException(e)
|
evaluationException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status.error(EvaluationError.GenericException)
|
||||||
reportError(codeFragment, sourcePosition, e.message ?: "An exception occurred", e)
|
reportError(codeFragment, sourcePosition, e.message ?: "An exception occurred", e)
|
||||||
|
|
||||||
val cause = if (e.message != null) ": ${e.message}" else ""
|
val cause = if (e.message != null) ": ${e.message}" else ""
|
||||||
@@ -129,38 +166,44 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun evaluateSafe(context: ExecutionContext): Any? {
|
private fun evaluateSafe(context: ExecutionContext, status: EvaluationStatus): Any? {
|
||||||
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
|
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context, status)
|
||||||
|
|
||||||
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
|
val (compiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = false)
|
||||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
|
||||||
|
val classLoadingResult = loadClassesSafely(context, compiledData.classes)
|
||||||
|
val classLoaderRef = (classLoadingResult as? ClassLoadingResult.Success)?.classLoader
|
||||||
|
|
||||||
|
if (classLoadingResult is ClassLoadingResult.Failure) {
|
||||||
|
status.classLoadingFailed()
|
||||||
|
}
|
||||||
|
|
||||||
val result = if (classLoaderRef != null) {
|
val result = if (classLoaderRef != null) {
|
||||||
evaluateWithCompilation(context, compiledData, classLoaderRef)
|
try {
|
||||||
?: evaluateWithEval4J(context, compiledData, classLoaderRef)
|
status.usedEvaluator(EvaluationStatus.EvaluatorType.Bytecode)
|
||||||
|
return evaluateWithCompilation(context, compiledData, classLoaderRef)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
status.compilingEvaluatorFailed()
|
||||||
|
LOG.warn("Compiling evaluator failed", e)
|
||||||
|
|
||||||
|
status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j)
|
||||||
|
evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
status.usedEvaluator(EvaluationStatus.EvaluatorType.Eval4j)
|
||||||
evaluateWithEval4J(context, compiledData, classLoaderRef)
|
evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
return result.toJdiValue(context)
|
||||||
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
|
||||||
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
|
|
||||||
return evaluateWithEval4J(context, recompiledData, classLoaderRef).toJdiValue(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (result) {
|
|
||||||
is InterpreterResult -> result.toJdiValue(context)
|
|
||||||
else -> result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileCodeFragment(context: ExecutionContext): CompiledDataDescriptor {
|
private fun compileCodeFragment(context: ExecutionContext, status: EvaluationStatus): CompiledDataDescriptor {
|
||||||
val debugProcess = context.debugProcess
|
val debugProcess = context.debugProcess
|
||||||
var analysisResult = checkForErrors(codeFragment, debugProcess)
|
var analysisResult = analyze(codeFragment, status, debugProcess)
|
||||||
|
|
||||||
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
||||||
// Repeat analysis with toString() added
|
// Repeat analysis with toString() added
|
||||||
analysisResult = checkForErrors(codeFragment, debugProcess)
|
analysisResult = analyze(codeFragment, status, debugProcess)
|
||||||
}
|
}
|
||||||
|
|
||||||
val (bindingContext) = runReadAction {
|
val (bindingContext) = runReadAction {
|
||||||
@@ -172,8 +215,13 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
|
|
||||||
val moduleDescriptor = analysisResult.moduleDescriptor
|
val moduleDescriptor = analysisResult.moduleDescriptor
|
||||||
|
|
||||||
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor)
|
try {
|
||||||
return createCompiledDataDescriptor(result, sourcePosition)
|
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor)
|
||||||
|
return createCompiledDataDescriptor(result, sourcePosition)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
status.error(EvaluationError.BackendException)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
||||||
@@ -205,7 +253,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
val files: List<KtFile>
|
val files: List<KtFile>
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
|
private fun analyze(codeFragment: KtCodeFragment, status: EvaluationStatus, debugProcess: DebugProcessImpl): ErrorCheckingResult {
|
||||||
return runInReadActionWithWriteActionPriorityWithPCE {
|
return runInReadActionWithWriteActionPriorityWithPCE {
|
||||||
try {
|
try {
|
||||||
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
|
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
|
||||||
@@ -221,6 +269,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
|
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
|
||||||
|
|
||||||
if (analysisResult.isError()) {
|
if (analysisResult.isError()) {
|
||||||
|
status.error(EvaluationError.FrontendException)
|
||||||
evaluationException(analysisResult.error)
|
evaluationException(analysisResult.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +278,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
bindingContext.diagnostics
|
bindingContext.diagnostics
|
||||||
.filter { it.factory !in IGNORED_DIAGNOSTICS }
|
.filter { it.factory !in IGNORED_DIAGNOSTICS }
|
||||||
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
|
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
|
||||||
?.let { evaluationException(DefaultErrorMessages.render(it)) }
|
?.let {
|
||||||
|
status.error(EvaluationError.ErrorsInCode)
|
||||||
|
evaluationException(DefaultErrorMessages.render(it))
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
|
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
|
||||||
}
|
}
|
||||||
@@ -240,17 +292,12 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
compiledData: CompiledDataDescriptor,
|
compiledData: CompiledDataDescriptor,
|
||||||
classLoader: ClassLoaderReference
|
classLoader: ClassLoaderReference
|
||||||
): Value? {
|
): Value? {
|
||||||
return try {
|
return runEvaluation(context, compiledData, classLoader) { args ->
|
||||||
runEvaluation(context, compiledData, classLoader) { 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 }
|
val returnValue = context.invokeMethod(mainClassType, mainMethod, args)
|
||||||
val returnValue = context.invokeMethod(mainClassType, mainMethod, args)
|
EvaluatorValueConverter(context).unref(returnValue)
|
||||||
EvaluatorValueConverter(context).unref(returnValue)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
LOG.error("Unable to evaluate the expression with compilation", e)
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-7
@@ -23,21 +23,32 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
|||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||||
|
|
||||||
fun loadClassesSafely(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
sealed class ClassLoadingResult {
|
||||||
|
class Success(val classLoader: ClassLoaderReference) : ClassLoadingResult()
|
||||||
|
class Failure(val error: Throwable) : ClassLoadingResult()
|
||||||
|
object NotNeeded : ClassLoadingResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadClassesSafely(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoadingResult {
|
||||||
|
if (classes.isEmpty()) {
|
||||||
|
return ClassLoadingResult.NotNeeded
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
loadClasses(context, classes)
|
val cl = loadClasses(context, classes)
|
||||||
|
if (cl != null) {
|
||||||
|
ClassLoadingResult.Success(cl)
|
||||||
|
} else {
|
||||||
|
ClassLoadingResult.NotNeeded
|
||||||
|
}
|
||||||
} catch (e: EvaluateException) {
|
} catch (e: EvaluateException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
LOG.debug("Failed to evaluate expression", e)
|
LOG.debug("Failed to evaluate expression", e)
|
||||||
null
|
ClassLoadingResult.Failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||||
if (classes.isEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClassLoadingAdapter.loadClasses(context, classes)
|
return ClassLoadingAdapter.loadClasses(context, classes)
|
||||||
}
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.statistics.FUSEventGroups
|
||||||
|
import org.jetbrains.kotlin.idea.statistics.KotlinFUSLogger
|
||||||
|
|
||||||
|
class EvaluationStatus {
|
||||||
|
private var error: EvaluationError? = null
|
||||||
|
private var values = mutableMapOf<String, String>()
|
||||||
|
private var evaluationTypeSet: Boolean = false
|
||||||
|
|
||||||
|
fun error(kind: EvaluationError) {
|
||||||
|
if (error == null) {
|
||||||
|
error = kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flag(name: String, value: Boolean) {
|
||||||
|
values[name] = value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Enum<T>> value(name: String, value: T) {
|
||||||
|
values[name] = value.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun evaluationType(type: KotlinDebuggerEvaluator.EvaluationType) {
|
||||||
|
evaluationTypeSet = true
|
||||||
|
value("evaluationType", type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send() {
|
||||||
|
if (!evaluationTypeSet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val statusName = error?.name ?: "Success"
|
||||||
|
KotlinFUSLogger.log(FUSEventGroups.DebugEval, statusName, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EvaluatorType {
|
||||||
|
Bytecode, Eval4j
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EvaluationContextLanguage {
|
||||||
|
Java, Kotlin, Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EvaluationError {
|
||||||
|
NoFrameProxy,
|
||||||
|
ThreadNotAvailable,
|
||||||
|
ThreadNotSuspended,
|
||||||
|
|
||||||
|
ProcessCancelledException,
|
||||||
|
InterpretingException,
|
||||||
|
EvaluateException,
|
||||||
|
SpecialException,
|
||||||
|
GenericException,
|
||||||
|
|
||||||
|
FrontendException,
|
||||||
|
BackendException,
|
||||||
|
ErrorsInCode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EvaluationStatus.classLoadingFailed() {
|
||||||
|
flag("classLoadingFailed", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EvaluationStatus.compilingEvaluatorFailed() {
|
||||||
|
flag("compilingEvaluatorFailed", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EvaluationStatus.usedEvaluator(evaluator: EvaluationStatus.EvaluatorType) {
|
||||||
|
value("evaluator", evaluator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EvaluationStatus.contextLanguage(language: EvaluationStatus.EvaluationContextLanguage) {
|
||||||
|
value("contextLanguage", language)
|
||||||
|
}
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.JavaDebuggerEvaluator
|
||||||
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
|
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.xdebugger.XExpression
|
||||||
|
import com.intellij.xdebugger.XSourcePosition
|
||||||
|
import com.intellij.xdebugger.frame.XValue
|
||||||
|
import com.intellij.xdebugger.impl.evaluate.quick.XValueHint
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.nodes.EvaluatingExpressionRootNode
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.nodes.WatchNodeImpl
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private data class Key(val text: String, val imports: String) {
|
||||||
|
constructor(expr: XExpression) : this(expr.expression, StringUtil.notNullize(expr.customInfo))
|
||||||
|
constructor(text: TextWithImports) : this(text.text, text.imports)
|
||||||
|
}
|
||||||
|
|
||||||
|
class KotlinDebuggerEvaluator(
|
||||||
|
debugProcess: DebugProcessImpl?,
|
||||||
|
stackFrame: JavaStackFrame?
|
||||||
|
) : JavaDebuggerEvaluator(debugProcess, stackFrame) {
|
||||||
|
private val types = HashMap<Key, EvaluationType>()
|
||||||
|
|
||||||
|
fun getType(text: TextWithImports): EvaluationType {
|
||||||
|
return types[Key(text)] ?: EvaluationType.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun evaluate(expression: XExpression, callback: XEvaluationCallback, expressionPosition: XSourcePosition?) {
|
||||||
|
val key = Key(expression)
|
||||||
|
val type = getType(callback)
|
||||||
|
if (type != null) {
|
||||||
|
types[key] = type
|
||||||
|
}
|
||||||
|
|
||||||
|
val wrappedCallback = object : XEvaluationCallback {
|
||||||
|
override fun errorOccurred(errorMessage: String) {
|
||||||
|
types.remove(key)
|
||||||
|
callback.errorOccurred(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun evaluated(result: XValue) {
|
||||||
|
types.remove(key)
|
||||||
|
callback.evaluated(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.evaluate(expression, wrappedCallback, expressionPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getType(callback: XEvaluationCallback): EvaluationType? {
|
||||||
|
val name = callback.javaClass.name
|
||||||
|
for (value in EvaluationType.values()) {
|
||||||
|
val clazz = value.clazz ?: continue
|
||||||
|
if (name.startsWith(clazz.name)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EvaluationType(val clazz: Class<*>?) {
|
||||||
|
WATCH(WatchNodeImpl::class.java),
|
||||||
|
WINDOW(EvaluatingExpressionRootNode::class.java),
|
||||||
|
POPUP(XValueHint::class.java),
|
||||||
|
UNKNOWN(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user