[debugger dfa] IDEA-373993 Move getJdiValueForDfaVariable computation to BGT

GitOrigin-RevId: 3a386ea3fa6f8c2c39a3c77e90ced78f9899dce5
This commit is contained in:
Maksim Zuev
2025-06-13 14:18:27 +02:00
committed by intellij-monorepo-bot
parent 3f2444f6d9
commit cd9e39eb6e
6 changed files with 128 additions and 85 deletions

View File

@@ -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<PsiElement?>): Pupa? {
@@ -195,12 +192,3 @@ private data class LarvaData(
val offset: Int,
val dfaVariableValues: List<DfaVariableValue>,
)
private suspend fun <T> syncReadAction(action: () -> T): T =
try {
ReadAction.nonBlocking(action).executeSynchronously()
}
catch (e: ExecutionException) {
throw e.cause ?: e
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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<KtFunction>() 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<KtFunction>() 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))
}