From cd9e39eb6eedbb548af213df5cba4a58234d9ab5 Mon Sep 17 00:00:00 2001 From: Maksim Zuev Date: Fri, 13 Jun 2025 14:18:27 +0200 Subject: [PATCH] [debugger dfa] IDEA-373993 Move getJdiValueForDfaVariable computation to BGT GitOrigin-RevId: 3a386ea3fa6f8c2c39a3c77e90ced78f9899dce5 --- .../dfaassist/DebuggerDfaRunnerUtils.kt | 14 +- .../engine/dfaassist/DfaAssistProvider.kt | 2 +- .../dfaassist/java/JavaDfaAssistProvider.kt | 40 ++--- .../mockJDI/types/MockPsiReferenceType.java | 3 +- .../dfaassist/KotlinDfaAssistProvider.kt | 14 +- .../debugger/dfaassist/K2DfaAssistProvider.kt | 140 ++++++++++++------ 6 files changed, 128 insertions(+), 85 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunnerUtils.kt b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunnerUtils.kt index 8b15d5376817..e7cb464f5f93 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunnerUtils.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunnerUtils.kt @@ -14,10 +14,8 @@ import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.jdi.StackFrameProxyEx import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.ReadConstraint import com.intellij.openapi.application.constrainedReadAction -import com.intellij.openapi.progress.blockingContext import com.intellij.openapi.project.DumbService.Companion.isDumb import com.intellij.psi.PsiElement import com.intellij.psi.SmartPointerManager @@ -30,7 +28,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext import org.jetbrains.annotations.VisibleForTesting -import java.util.concurrent.ExecutionException @Throws(EvaluateException::class) @@ -96,7 +93,7 @@ private suspend fun resolveJdiValue( // Assume that assertions are enabled if we cannot fetch the status return location.virtualMachine().mirrorOf(status == ThreeState.NO) } - return syncReadAction { provider.getJdiValueForDfaVariable(proxy, variableValue, anchor) } + return provider.getJdiValueForDfaVariable(proxy, variableValue, anchor) } private suspend fun makePupa(proxy: StackFrameProxyEx, pointer: SmartPsiElementPointer): Pupa? { @@ -195,12 +192,3 @@ private data class LarvaData( val offset: Int, val dfaVariableValues: List, ) - -private suspend fun syncReadAction(action: () -> T): T = - try { - ReadAction.nonBlocking(action).executeSynchronously() - } - catch (e: ExecutionException) { - throw e.cause ?: e - } - diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssistProvider.kt b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssistProvider.kt index eaa504024359..1e541cc7172b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssistProvider.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssistProvider.kt @@ -65,7 +65,7 @@ interface DfaAssistProvider { * @throws EvaluateException if proxy throws */ @Throws(EvaluateException::class) - fun getJdiValueForDfaVariable( + suspend fun getJdiValueForDfaVariable( proxy: StackFrameProxyEx, dfaVar: DfaVariableValue, anchor: PsiElement, diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/java/JavaDfaAssistProvider.kt b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/java/JavaDfaAssistProvider.kt index 6f0173b4c3df..0a9ee9bd00a7 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/java/JavaDfaAssistProvider.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/java/JavaDfaAssistProvider.kt @@ -102,7 +102,7 @@ private class JavaDfaAssistProvider : DfaAssistProvider { } @Throws(EvaluateException::class) - override fun getJdiValueForDfaVariable( + override suspend fun getJdiValueForDfaVariable( proxy: StackFrameProxyEx, dfaVar: DfaVariableValue, anchor: PsiElement, @@ -116,12 +116,12 @@ private class JavaDfaAssistProvider : DfaAssistProvider { } val qualifierValue = getJdiValueForDfaVariable(proxy, qualifier, anchor) if (qualifierValue == null) return null - val element = descriptor.psiElement + val element = readAction { descriptor.psiElement } if (element is PsiField && qualifierValue is ObjectReference) { val type = qualifierValue.referenceType() - val psiClass = element.getContainingClass() - if (psiClass != null && type.name() == JVMNameUtil.getClassVMName(psiClass)) { - val field = DebuggerUtils.findField(type, element.getName()) + val psiClass = readAction { element.getContainingClass() } + if (psiClass != null && type.name() == readAction { JVMNameUtil.getClassVMName(psiClass) }) { + val field = DebuggerUtils.findField(type, readAction { element.getName() }) if (field != null) { return wrap(qualifierValue.getValue(field)) } @@ -136,15 +136,19 @@ private class JavaDfaAssistProvider : DfaAssistProvider { } return null } - val psi = dfaVar.psiVariable + val psi = readAction { dfaVar.psiVariable } if (psi is PsiClass) { // this; probably qualified - val currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass::class.java) - return CaptureTraverser.create(psi, currentClass, true).traverse(proxy.thisObject()) + val captureTraverser = readAction { + val currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass::class.java) + CaptureTraverser.create(psi, currentClass, true) + } + return captureTraverser.traverse(proxy.thisObject()) } if (psi is PsiLocalVariable || psi is PsiParameter) { - val varName: String = psi.getName()!! - if (PsiResolveHelper.getInstance(psi.getProject()).resolveReferencedVariable(varName, anchor) !== psi) { + val varName: String = readAction { psi.getName()!! } + val resolveVariable = readAction { PsiResolveHelper.getInstance(psi.getProject()).resolveReferencedVariable(varName, anchor) } + if (resolveVariable !== psi) { // Another variable with the same name could be tracked by DFA in different code branch but not visible at current code location return null } @@ -152,10 +156,12 @@ private class JavaDfaAssistProvider : DfaAssistProvider { if (variable != null) { return wrap(proxy.getVariableValue(variable)) } - val currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass::class.java) - val varClass = PsiTreeUtil.getParentOfType(psi, PsiClass::class.java) - val thisRef = CaptureTraverser.create(varClass, currentClass, false) - .oneLevelLess().traverse(proxy.thisObject()) + val captureTraverser = readAction { + val currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass::class.java) + val varClass = PsiTreeUtil.getParentOfType(psi, PsiClass::class.java) + CaptureTraverser.create(varClass, currentClass, false).oneLevelLess() + } + val thisRef = captureTraverser.traverse(proxy.thisObject()) if (thisRef != null) { val type = thisRef.referenceType() if (type is ClassType && type.isPrepared) { @@ -166,10 +172,10 @@ private class JavaDfaAssistProvider : DfaAssistProvider { } } } - if (psi is PsiField && psi.hasModifierProperty(PsiModifier.STATIC)) { - val psiClass = psi.getContainingClass() + if (psi is PsiField && readAction { psi.hasModifierProperty(PsiModifier.STATIC) }) { + val psiClass = readAction { psi.getContainingClass() } if (psiClass != null) { - val name = psiClass.getQualifiedName() + val name = readAction { psiClass.getQualifiedName() } if (name != null) { val type = ContainerUtil.getOnlyItem(proxy.getVirtualMachine().classesByName(name)) if (type != null && type.isPrepared) { diff --git a/java/testFramework/src/com/intellij/debugger/mockJDI/types/MockPsiReferenceType.java b/java/testFramework/src/com/intellij/debugger/mockJDI/types/MockPsiReferenceType.java index 2fa23b795610..7310f7d78e7d 100644 --- a/java/testFramework/src/com/intellij/debugger/mockJDI/types/MockPsiReferenceType.java +++ b/java/testFramework/src/com/intellij/debugger/mockJDI/types/MockPsiReferenceType.java @@ -4,6 +4,7 @@ package com.intellij.debugger.mockJDI.types; import com.intellij.debugger.mockJDI.MockVirtualMachine; import com.intellij.debugger.mockJDI.members.MockPsiMethod; import com.intellij.debugger.mockJDI.values.MockClassLoaderReference; +import com.intellij.openapi.application.ReadAction; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiModifier; import com.intellij.psi.util.ClassUtil; @@ -28,7 +29,7 @@ public class MockPsiReferenceType extends MockType implements ReferenceType { @Override public String name() { - return ClassUtil.getJVMClassName(myClass); + return ReadAction.compute(() -> ClassUtil.getJVMClassName(myClass)); } @Override diff --git a/plugins/kotlin/jvm-debugger/evaluation/k1/src/org/jetbrains/kotlin/idea/debugger/dfaassist/KotlinDfaAssistProvider.kt b/plugins/kotlin/jvm-debugger/evaluation/k1/src/org/jetbrains/kotlin/idea/debugger/dfaassist/KotlinDfaAssistProvider.kt index e24d4c0be669..572fe09ac4ee 100644 --- a/plugins/kotlin/jvm-debugger/evaluation/k1/src/org/jetbrains/kotlin/idea/debugger/dfaassist/KotlinDfaAssistProvider.kt +++ b/plugins/kotlin/jvm-debugger/evaluation/k1/src/org/jetbrains/kotlin/idea/debugger/dfaassist/KotlinDfaAssistProvider.kt @@ -72,15 +72,15 @@ private class KotlinDfaAssistProvider : DfaAssistProvider { } } - override fun getJdiValueForDfaVariable(proxy: StackFrameProxyEx, dfaVar: DfaVariableValue, anchor: PsiElement): Value? { + override suspend fun getJdiValueForDfaVariable(proxy: StackFrameProxyEx, dfaVar: DfaVariableValue, anchor: PsiElement): Value? { val qualifier = dfaVar.qualifier - val psiVariable = dfaVar.psiVariable + val psiVariable = readAction { dfaVar.psiVariable } if (qualifier == null) { val descriptor = dfaVar.descriptor if (descriptor is KtThisDescriptor) { val declarationDescriptor = descriptor.descriptor if (declarationDescriptor is FunctionDescriptor) { - val thisName = "\$this\$${declarationDescriptor.name}" + val thisName = readAction { "\$this\$${declarationDescriptor.name}" } val thisVar = proxy.visibleVariableByName(thisName) if (thisVar != null) { return postprocess(proxy.getVariableValue(thisVar)) @@ -90,7 +90,7 @@ private class KotlinDfaAssistProvider : DfaAssistProvider { val thisObject = proxy.thisObject() if (thisObject != null) { val signature = AsmType.getType(thisObject.referenceType().signature()).className - val jvmName = KotlinPsiHeuristics.getJvmName(declarationDescriptor.fqNameSafe) + val jvmName = readAction { KotlinPsiHeuristics.getJvmName(declarationDescriptor.fqNameSafe) } if (signature == jvmName) { return thisObject } @@ -100,7 +100,8 @@ private class KotlinDfaAssistProvider : DfaAssistProvider { } else if (descriptor is KtVariableDescriptor && psiVariable is KtCallableDeclaration) { // TODO: check/support inlined functions - val variable = proxy.visibleVariableByName((psiVariable as KtNamedDeclaration).name) + val name = readAction { (psiVariable as KtNamedDeclaration).name } + val variable = proxy.visibleVariableByName(name) if (variable != null) { return postprocess(proxy.getVariableValue(variable)) } @@ -108,7 +109,8 @@ private class KotlinDfaAssistProvider : DfaAssistProvider { } else { val jdiQualifier = getJdiValueForDfaVariable(proxy, qualifier, anchor) if (jdiQualifier is ObjectReference && psiVariable is KtCallableDeclaration) { - val field = psiVariable.name?.let { DebuggerUtils.findField(jdiQualifier.referenceType(), it) } + val name = readAction { psiVariable.name } + val field = name?.let { DebuggerUtils.findField(jdiQualifier.referenceType(), it) } if (field != null) { return postprocess(jdiQualifier.getValue(field)) } diff --git a/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt b/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt index 4aa9fa611f95..32005156c87b 100644 --- a/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt +++ b/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt @@ -86,14 +86,14 @@ private class K2DfaAssistProvider : DfaAssistProvider { } } - override fun getJdiValueForDfaVariable( + override suspend fun getJdiValueForDfaVariable( proxy: StackFrameProxyEx, dfaVar: DfaVariableValue, anchor: PsiElement ): Value? { if (anchor !is KtElement) return null - if ((dfaVar.descriptor as? KtBaseDescriptor)?.isInlineClassReference() == true) return null - return runDumbAction(anchor.project, null) { + if (readAction { (dfaVar.descriptor as? KtBaseDescriptor)?.isInlineClassReference() == true }) return null + return runDumbAction(readAction { anchor.project }, null) { getJdiValueInner(proxy, dfaVar, anchor) } } @@ -124,7 +124,7 @@ private class K2DfaAssistProvider : DfaAssistProvider { } } - private fun getJdiValueInner( + private suspend fun getJdiValueInner( proxy: StackFrameProxyEx, dfaVar: DfaVariableValue, anchor: KtElement @@ -136,10 +136,12 @@ private class K2DfaAssistProvider : DfaAssistProvider { val inlineSuffix = KotlinDebuggerConstants.INLINE_FUN_VAR_SUFFIX.repeat(inlineDepth) if (qualifier == null) { if (descriptor is KtLambdaThisVariableDescriptor) { - val scopeName = (descriptor.lambda.parentOfType() as? KtNamedFunction)?.name - val scopePart = scopeName?.replace(Regex("[^\\p{L}\\d]"), "_")?.let(Regex::escape) ?: ".+" - val inlinedPart = Regex.escape(inlineSuffix) - val regex = Regex("\\\$this\\$${scopePart}(_\\w+)?_u\\d+lambda_u\\d+$inlinedPart") + val regex = readAction { + val scopeName = (descriptor.lambda.parentOfType() as? KtNamedFunction)?.name + val scopePart = scopeName?.replace(Regex("[^\\p{L}\\d]"), "_")?.let(Regex::escape) ?: ".+" + val inlinedPart = Regex.escape(inlineSuffix) + Regex("\\\$this\\$${scopePart}(_\\w+)?_u\\d+lambda_u\\d+$inlinedPart") + } val lambdaThis = proxy.stackFrame.visibleVariables().filter { it.name().matches(regex) } if (lambdaThis.size == 1) { return postprocess(proxy.stackFrame.getValue(lambdaThis.first())) @@ -155,7 +157,9 @@ private class K2DfaAssistProvider : DfaAssistProvider { return postprocess(proxy.getVariableValue(thisVar)) } } - val nameString = analyze(anchor) { (pointer?.restoreSymbol() as? KaNamedClassSymbol)?.classId?.asSingleFqName() } + val nameString = readAction { + analyze(anchor) { (pointer?.restoreSymbol() as? KaNamedClassSymbol)?.classId?.asSingleFqName() } + } if (nameString != null) { if (inlineDepth > 0) { val thisName = AsmUtil.INLINE_DECLARATION_SITE_THIS + inlineSuffix @@ -187,32 +191,50 @@ private class K2DfaAssistProvider : DfaAssistProvider { } if (descriptor is KtVariableDescriptor) { val pointer = descriptor.pointer - analyze(anchor) { - val symbol = pointer.restoreSymbol() - if (symbol is KaJavaFieldSymbol && symbol.isStatic) { - val classId = (symbol.containingDeclaration as? KaNamedClassSymbol)?.classId - if (classId != null) { - val declaringClasses = proxy.virtualMachine.classesByName(JvmClassName.byClassId(classId).internalName.replace("/", ".")) - if (declaringClasses.size == 1) { - val declaringClass = declaringClasses.first() - val field = DebuggerUtils.findField(declaringClass, symbol.name.identifier) - if (field != null && field.isStatic) { - return postprocess(declaringClass.getValue(field)) - } + val result = readAction { + analyze(anchor) { + val symbol = pointer.restoreSymbol() + if (symbol is KaJavaFieldSymbol && symbol.isStatic) { + val classId = (symbol.containingDeclaration as? KaNamedClassSymbol)?.classId + if (classId != null) { + val className = JvmClassName.byClassId(classId).internalName.replace("/", ".") + val fieldName = symbol.name.identifier + return@readAction VariableResult.JavaField(className, fieldName) + } + return@readAction null + } + if (symbol is KaVariableSymbol) { + val name = symbol.name.asString() + inlineSuffix + val expectedType = symbol.returnType + val isNonNullPrimitiveType = expectedType.isPrimitive && !expectedType.canBeNull + return@readAction VariableResult.Variable(name, symbol.psi, isNonNullPrimitiveType) + } + } + null + } + when (result) { + is VariableResult.JavaField -> { + val declaringClasses = proxy.virtualMachine.classesByName(result.className) + if (declaringClasses.size == 1) { + val declaringClass = declaringClasses.first() + val field = DebuggerUtils.findField(declaringClass, result.fieldName) + if (field != null && field.isStatic) { + return postprocess(declaringClass.getValue(field)) } } - return null } - if (symbol is KaVariableSymbol) { - val name = symbol.name.asString() + inlineSuffix - var variable = proxy.visibleVariableByName(name) + + is VariableResult.Variable -> { + var variable = proxy.visibleVariableByName(result.name) var value: Value? = null if (variable == null) { - val psi = symbol.psi - val scope = anchor.getScope() - if (psi != null && scope != null && psi.containingFile == scope.containingFile && !scope.isAncestor(psi)) { + val psi = result.psi + val scope = readAction { anchor.getScope() } + if (psi != null && scope != null + && readAction { psi.containingFile == scope.containingFile && !scope.isAncestor(psi) } + ) { // Captured variable - val capturedName = AsmUtil.CAPTURED_PREFIX + name + val capturedName = AsmUtil.CAPTURED_PREFIX + result.name variable = proxy.visibleVariableByName(capturedName) if (variable == null) { // Captured variable in Kotlin 1.x @@ -232,8 +254,7 @@ private class K2DfaAssistProvider : DfaAssistProvider { value = postprocess(proxy.getVariableValue(variable)) } if (value != null) { - val expectedType = symbol.returnType - if (inlineDepth > 0 && value.type() is PrimitiveType && !(expectedType.isPrimitive && !expectedType.canBeNull)) { + if (inlineDepth > 0 && value.type() is PrimitiveType && !result.isNonNullPrimitiveReturnType) { val typeKind = JvmPrimitiveTypeKind.getKindByName(value.type().name()) if (typeKind != null) { val referenceType = proxy.virtualMachine.classesByName(typeKind.boxedFqn).firstOrNull() @@ -242,37 +263,62 @@ private class K2DfaAssistProvider : DfaAssistProvider { } } } + return value } - return value - } + } + + null -> {} } } } else { val jdiQualifier = getJdiValueInner(proxy, qualifier, anchor) if (descriptor is KtVariableDescriptor) { - val type = (jdiQualifier as? ObjectReference)?.referenceType() val pointer = descriptor.pointer - analyze(anchor) { - val symbol = pointer.restoreSymbol() - if (symbol is KaPropertySymbol) { - val parent = symbol.containingDeclaration - if (parent is KaNamedClassSymbol && parent.isInline) { - // Inline class sole property is represented by inline class itself - return jdiQualifier - } - } - if (symbol is KaVariableSymbol && type != null) { - val field = DebuggerUtils.findField(type, symbol.name.asString()) - if (field != null) { - return postprocess(jdiQualifier.getValue(field)) + val result = readAction { + analyze(anchor) { + val symbol = pointer.restoreSymbol() + if (symbol is KaPropertySymbol) { + val parent = symbol.containingDeclaration + if (parent is KaNamedClassSymbol && parent.isInline) { + // Inline class sole property is represented by inline class itself + return@readAction QualifierVariableResult.InlineClassProperty + } + } + if (symbol is KaVariableSymbol) { + return@readAction QualifierVariableResult.NamedVariable(symbol.name.asString()) + } + null + } + } + when (result) { + QualifierVariableResult.InlineClassProperty -> return jdiQualifier + is QualifierVariableResult.NamedVariable -> { + val type = (jdiQualifier as? ObjectReference)?.referenceType() + if (type != null) { + val field = DebuggerUtils.findField(type, result.name) + if (field != null) { + return postprocess(jdiQualifier.getValue(field)) + } } } + + else -> {} } } } return null } + private sealed interface VariableResult { + data class JavaField(val className: String, val fieldName: String) : VariableResult + data class Variable(val name: String, val psi: PsiElement?, val isNonNullPrimitiveReturnType: Boolean) : VariableResult + } + + private sealed interface QualifierVariableResult { + object InlineClassProperty : QualifierVariableResult + data class NamedVariable(val name: String) : QualifierVariableResult + } + private fun postprocess(value: Value?): Value { return DfaAssistProvider.wrap(EvaluatorValueConverter.unref(value)) }