[debugger] Improve statistics about Kotlin code fragment evaluation

IDEA-362361

(cherry picked from commit be355d22d5edae92a009c687dc7c083eb0dff335)


IJ-CR-151042

GitOrigin-RevId: 089dd4e20439c541ee8da7f9105d55399c77d1fa
This commit is contained in:
Alexey.Merkulov
2024-11-01 12:54:39 +01:00
committed by intellij-monorepo-bot
parent a9a6b63c37
commit 9afb971dd9
5 changed files with 115 additions and 16 deletions

View File

@@ -118,7 +118,7 @@ class K1KotlinCodeFragmentCompiler : KotlinCodeFragmentCompiler {
EvaluationCompilerResult.COMPILATION_FAILURE,
stats
)
evaluationException(DefaultErrorMessages.render(it))
throw IncorrectCodeFragmentException(DefaultErrorMessages.render(it))
}
}

View File

@@ -3,8 +3,11 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.compile.CodeFragmentCapturedValue
@@ -22,12 +25,12 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_CLASS_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_FUNCTION_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
import java.util.concurrent.ExecutionException
private class IncorrectCodeFragmentException(message: String) : EvaluateException(message)
interface KotlinCodeFragmentCompiler {
fun compileCodeFragment(context: ExecutionContext, codeFragment: KtCodeFragment): CompiledCodeFragmentData
@@ -61,7 +64,8 @@ class K2KotlinCodeFragmentCompiler : KotlinCodeFragmentCompiler {
stats.compilerFailExceptionClass = extractExceptionCauseClass(e)
onFinish(if (cause is IncorrectCodeFragmentException) EvaluationCompilerResult.COMPILATION_FAILURE
val isJustInvalidUserCode = cause is IncorrectCodeFragmentException
onFinish(if (isJustInvalidUserCode) EvaluationCompilerResult.COMPILATION_FAILURE
else EvaluationCompilerResult.COMPILER_INTERNAL_ERROR)
throw e
}
@@ -172,3 +176,17 @@ fun isCodeFragmentClassPath(path: String): Boolean {
@KaExperimentalApi
val KaCompiledFile.isCodeFragmentClassFile: Boolean
get() = isCodeFragmentClassPath(path)
fun hasCastOperator(codeFragment: KtCodeFragment): Boolean {
var result = false
runReadAction {
codeFragment.accept(object : PsiRecursiveElementVisitor() {
override fun visitElement(element: PsiElement) {
if (result) return
result = element is KtOperationReferenceExpression && element.operationSignTokenType == KtTokens.AS_KEYWORD
super.visitElement(element)
}
})
}
return result
}

View File

@@ -19,7 +19,7 @@ object KotlinDebuggerEvaluatorStatisticsCollector : CounterUsagesCollector() {
override fun getGroup(): EventLogGroup = GROUP
private val GROUP = EventLogGroup("kotlin.debugger.evaluator", 8)
private val GROUP = EventLogGroup("kotlin.debugger.evaluator", 9)
// fields
private val compilerField = EventFields.Enum<CompilerType>("compiler")
@@ -39,7 +39,7 @@ object KotlinDebuggerEvaluatorStatisticsCollector : CounterUsagesCollector() {
wrapTimeMsField, analysisTimeMsField, compilationTimeMsField, wholeTimeField, interruptionsField, compilerExceptionField
)
// no need to record evaluation time, as it reflects what user evaluates, not how effective our evaluation is
private val evaluationEvent = GROUP.registerEvent("evaluation.result", resultField, compilerField)
private val evaluationEvent = GROUP.registerEvent("evaluation.result", resultField, compilerField, originField)
@JvmStatic
fun logAnalysisAndCompilationResult(
@@ -60,8 +60,8 @@ object KotlinDebuggerEvaluatorStatisticsCollector : CounterUsagesCollector() {
}
@JvmStatic
internal fun logEvaluationResult(project: Project?, evaluationResult: StatisticsEvaluationResult, compilerType: CompilerType) {
evaluationEvent.log(project, evaluationResult, compilerType)
internal fun logEvaluationResult(project: Project?, evaluationResult: StatisticsEvaluationResult, compilerType: CompilerType, origin: XEvaluationOrigin) {
evaluationEvent.log(project, evaluationResult, compilerType, origin)
}
}
@@ -70,7 +70,18 @@ enum class EvaluationCompilerResult {
}
enum class StatisticsEvaluationResult {
SUCCESS, FAILURE
SUCCESS,
USER_EXCEPTION,
COMPILATION_FAILURE,
COMPILER_INTERNAL_ERROR,
UNCLASSIFIED_COMPILATION_PROBLEM,
UNCLASSIFIED_EVALUATION_PROBLEM,
MISCOMPILED,
ERROR_DURING_PARSING_EXCEPTION,
WRONG_JVM_STATE,
UNRELATED_EXCEPTION,
}
@ApiStatus.Internal

View File

@@ -11,6 +11,7 @@ import com.intellij.debugger.engine.evaluation.expression.*
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.IndexNotReadyException
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.idea.debugger.base.util.safeLocation
import org.jetbrains.kotlin.idea.debugger.base.util.safeMethod
import org.jetbrains.kotlin.idea.debugger.base.util.safeVisibleVariableByName
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineStackFrameProxyImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_CLASS_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.isEvaluationEntryPoint
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
@@ -125,21 +127,58 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePositi
}
private fun evaluateSafe(context: ExecutionContext, codeFragment: KtCodeFragment): Any? {
val compiledData = getCompiledCodeFragment(context)
val hasCast = hasCastOperator(codeFragment)
val compiledData = try {
getCompiledCodeFragment(context)
} catch (e: Throwable) {
if (e !is ProcessCanceledException) {
val evaluationResultValue = when (e) {
is IncorrectCodeFragmentException -> StatisticsEvaluationResult.COMPILATION_FAILURE
is EvaluateException -> StatisticsEvaluationResult.COMPILER_INTERNAL_ERROR
else -> StatisticsEvaluationResult.UNCLASSIFIED_COMPILATION_PROBLEM
}
KotlinDebuggerEvaluatorStatisticsCollector.logEvaluationResult(codeFragment.project, evaluationResultValue, CompilerType.K2, context.evaluationContext.origin)
}
throw e
}
return try {
runEvaluation(context, compiledData).also {
KotlinDebuggerEvaluatorStatisticsCollector.logEvaluationResult(codeFragment.project, StatisticsEvaluationResult.SUCCESS, compiledData.compilerType)
if (!compiledData.statisticReported) {
compiledData.statisticReported = true
KotlinDebuggerEvaluatorStatisticsCollector.logEvaluationResult(
codeFragment.project,
StatisticsEvaluationResult.SUCCESS,
compiledData.compilerType,
context.evaluationContext.origin
)
}
}
} catch (e: Throwable) {
if (!isUnitTestMode()) {
if (e !is EvaluateException && e !is Eval4JInterpretingException) {
val cause = e.cause
val errorType = when {
e is ControlFlowException || e is IndexNotReadyException -> StatisticsEvaluationResult.UNRELATED_EXCEPTION
e is Eval4JInterpretingException ->
if (!hasCast && e.cause is ClassCastException) StatisticsEvaluationResult.MISCOMPILED
else StatisticsEvaluationResult.USER_EXCEPTION
e is EvaluateException && cause != null -> checkCauseOfEvaluateException(cause, hasCast)
isSpecialException(e) -> StatisticsEvaluationResult.WRONG_JVM_STATE
else -> StatisticsEvaluationResult.UNCLASSIFIED_EVALUATION_PROBLEM
}
if (!compiledData.statisticReported) {
if (errorType != StatisticsEvaluationResult.UNRELATED_EXCEPTION && errorType != StatisticsEvaluationResult.WRONG_JVM_STATE) {
compiledData.statisticReported = true
}
KotlinDebuggerEvaluatorStatisticsCollector.logEvaluationResult(
codeFragment.project,
StatisticsEvaluationResult.FAILURE,
compiledData.compilerType
errorType,
compiledData.compilerType,
context.evaluationContext.origin
)
}
if (isApplicationInternalMode()) {
reportErrorWithAttachments(context, codeFragment, e,
prepareBytecodes(compiledData),
@@ -150,6 +189,32 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePositi
}
}
private fun checkCauseOfEvaluateException(cause: Throwable, hasCast: Boolean): StatisticsEvaluationResult {
if (cause is InvocationException) {
try {
val exceptionFromCodeFragment = cause.exception()
val type = exceptionFromCodeFragment.type()
if (type.signature().startsWith("Ljava/lang/invoke/") || type.isSubTypeOrSame("java.lang.ReflectiveOperationException")) {
return StatisticsEvaluationResult.MISCOMPILED
}
if (type.isSubTypeOrSame("java.lang.ClassCastException")) {
return if (hasCast) StatisticsEvaluationResult.USER_EXCEPTION else StatisticsEvaluationResult.MISCOMPILED
}
}
catch (e: Throwable) {
LOG.error("Can't extract error type from InvocationException", e)
return StatisticsEvaluationResult.ERROR_DURING_PARSING_EXCEPTION
}
return StatisticsEvaluationResult.USER_EXCEPTION
}
if (isSpecialException(cause)) {
return StatisticsEvaluationResult.WRONG_JVM_STATE
}
return StatisticsEvaluationResult.MISCOMPILED
}
private fun prepareBytecodes(compiledData: CompiledCodeFragmentData): List<Pair<String, String>> {
// TODO run javap, if found valid java home?
val result = buildString {
@@ -498,6 +563,10 @@ fun createCompiledDataDescriptor(result: CompilationResult): CompiledCodeFragmen
fun evaluationException(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
fun evaluationException(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
@ApiStatus.Internal
class IncorrectCodeFragmentException(message: String) : EvaluateException(message)
enum class CompilerType {
OLD, IR, K2
}

View File

@@ -10,12 +10,13 @@ internal sealed interface CompilationCodeFragmentResult
internal class FailedCompilationCodeFragment(val evaluateException: EvaluateException): CompilationCodeFragmentResult
data class CompiledCodeFragmentData(
class CompiledCodeFragmentData(
val classes: List<ClassToLoad>,
val parameters: List<CodeFragmentParameter.Dumb>,
val crossingBounds: Set<CodeFragmentParameter.Dumb>,
val mainMethodSignature: MethodSignature,
val compilerType: CompilerType
val compilerType: CompilerType,
var statisticReported: Boolean = false,
): CompilationCodeFragmentResult {
data class MethodSignature(val parameterTypes: List<Type>, val returnType: Type)
}