[debugger] Convert JavaDfaAssistProvider to Kotlin

GitOrigin-RevId: 1ad4e08de387adee04d5dd5792dd604c90747fd0
This commit is contained in:
Maksim Zuev
2025-06-13 16:50:41 +00:00
committed by intellij-monorepo-bot
parent 65e13c2135
commit ad35b8a70b
2 changed files with 130 additions and 135 deletions
@@ -56,7 +56,7 @@ interface DfaAssistProvider {
/**
* @param proxy proxy to create JDI values
* @param var DfaVariableValue to find value for
* @param dfaVar DfaVariableValue to find value for
* @param anchor anchor previously returned by [.getAnchor] call, where analysis takes place
* @return JDI value for a variable; null if value is not known; NullConst if value is known to be null
* (use [.wrap] utility method for this purpose).
@@ -65,7 +65,7 @@ interface DfaAssistProvider {
@Throws(EvaluateException::class)
fun getJdiValueForDfaVariable(
proxy: StackFrameProxyEx,
`var`: DfaVariableValue,
dfaVar: DfaVariableValue,
anchor: PsiElement,
): Value?
@@ -1,196 +1,191 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.dfaassist.java;
package com.intellij.debugger.engine.dfaassist.java
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.codeInspection.dataFlow.TypeConstraints;
import com.intellij.codeInspection.dataFlow.jvm.SpecialField;
import com.intellij.codeInspection.dataFlow.jvm.descriptors.ArrayElementDescriptor;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.JVMNameUtil;
import com.intellij.debugger.engine.dfaassist.DebuggerDfaListener;
import com.intellij.debugger.engine.dfaassist.DfaAssistProvider;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.expression.CaptureTraverser;
import com.intellij.debugger.engine.jdi.LocalVariableProxy;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.StackFrameProxyEx;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.intellij.codeInspection.dataFlow.TypeConstraint
import com.intellij.codeInspection.dataFlow.TypeConstraints
import com.intellij.codeInspection.dataFlow.jvm.SpecialField
import com.intellij.codeInspection.dataFlow.jvm.descriptors.ArrayElementDescriptor
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue
import com.intellij.debugger.engine.DebuggerUtils
import com.intellij.debugger.engine.JVMNameUtil
import com.intellij.debugger.engine.dfaassist.DebuggerDfaListener
import com.intellij.debugger.engine.dfaassist.DfaAssistProvider
import com.intellij.debugger.engine.dfaassist.DfaAssistProvider.Companion.wrap
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.expression.CaptureTraverser
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.StackFrameProxyEx
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.ContainerUtil
import com.sun.jdi.*
public final class JavaDfaAssistProvider implements DfaAssistProvider {
@Override
public boolean locationMatches(@NotNull PsiElement element, @NotNull Location location) {
Method method = location.method();
PsiElement context = DebuggerUtilsEx.getContainingMethod(element);
if (context instanceof PsiMethod psiMethod) {
String name = psiMethod.isConstructor() ? "<init>" : psiMethod.getName();
return name.equals(method.name()) && psiMethod.getParameterList().getParametersCount() == method.argumentTypeNames().size();
class JavaDfaAssistProvider : DfaAssistProvider {
override fun locationMatches(element: PsiElement, location: Location): Boolean {
val method = location.method()
val context = DebuggerUtilsEx.getContainingMethod(element)
if (context is PsiMethod) {
val name = if (context.isConstructor()) "<init>" else context.getName()
return name == method.name() && context.getParameterList().getParametersCount() == method.argumentTypeNames().size
}
if (context instanceof PsiLambdaExpression lambda) {
if (context is PsiLambdaExpression) {
return DebuggerUtilsEx.isLambda(method) &&
method.argumentTypeNames().size() >= lambda.getParameterList().getParametersCount();
method.argumentTypeNames().size >= context.getParameterList().getParametersCount()
}
if (context instanceof PsiClassInitializer initializer) {
String expectedMethod = initializer.hasModifierProperty(PsiModifier.STATIC) ? "<clinit>" : "<init>";
return method.name().equals(expectedMethod);
if (context is PsiClassInitializer) {
val expectedMethod = if (context.hasModifierProperty(PsiModifier.STATIC)) "<clinit>" else "<init>"
return method.name() == expectedMethod
}
return false;
return false
}
@Override
public @Nullable PsiElement getAnchor(@NotNull PsiElement element) {
while (element instanceof PsiWhiteSpace || element instanceof PsiComment) {
element = element.getNextSibling();
override fun getAnchor(element: PsiElement): PsiElement? {
var element = element
while (element is PsiWhiteSpace || element is PsiComment) {
element = element.getNextSibling()
}
while (!(element instanceof PsiStatement)) {
PsiElement parent = element.getParent();
if (!(parent instanceof PsiStatement) && (parent == null || element.getTextRangeInParent().getStartOffset() > 0)) {
if (parent instanceof PsiCodeBlock && ((PsiCodeBlock)parent).getRBrace() == element) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiBlockStatement) {
return PsiTreeUtil.getNextSiblingOfType(grandParent, PsiStatement.class);
while (element !is PsiStatement) {
val parent = element.getParent()
if (parent !is PsiStatement && (parent == null || element.textRangeInParent.startOffset > 0)) {
if (parent is PsiCodeBlock && parent.getRBrace() === element) {
val grandParent = parent.getParent()
if (grandParent is PsiBlockStatement) {
return PsiTreeUtil.getNextSiblingOfType(grandParent, PsiStatement::class.java)
}
}
if (parent instanceof PsiPolyadicExpression) {
if (parent is PsiPolyadicExpression) {
// If we are inside the expression we can position only at locations where the stack is empty
// currently only && and || chains inside if/return/yield are allowed
IElementType tokenType = ((PsiPolyadicExpression)parent).getOperationTokenType();
if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiIfStatement || grandParent instanceof PsiYieldStatement ||
grandParent instanceof PsiReturnStatement) {
if (element instanceof PsiExpression) {
return element;
val tokenType = parent.getOperationTokenType()
if (tokenType == JavaTokenType.ANDAND || tokenType == JavaTokenType.OROR) {
val grandParent = parent.getParent()
if (grandParent is PsiIfStatement || grandParent is PsiYieldStatement ||
grandParent is PsiReturnStatement
) {
if (element is PsiExpression) {
return element
}
return PsiTreeUtil.getNextSiblingOfType(element, PsiExpression.class);
return PsiTreeUtil.getNextSiblingOfType(element, PsiExpression::class.java)
}
}
}
return null;
return null
}
element = parent;
element = parent
}
return element;
return element
}
@Override
public @Nullable PsiElement getCodeBlock(@NotNull PsiElement anchor) {
if (anchor instanceof PsiSwitchLabelStatementBase) {
return null; // unsupported yet
override fun getCodeBlock(anchor: PsiElement): PsiElement? {
if (anchor is PsiSwitchLabelStatementBase) {
return null // unsupported yet
}
PsiElement e = anchor;
while (e != null && !(e instanceof PsiClass) && !(e instanceof PsiFileSystemItem)) {
e = e.getParent();
if (e instanceof PsiCodeBlock) {
PsiElement parent = e.getParent();
if (parent instanceof PsiMethod || parent instanceof PsiLambdaExpression || parent instanceof PsiClassInitializer ||
var e: PsiElement? = anchor
while (e != null && (e !is PsiClass) && (e !is PsiFileSystemItem)) {
e = e.getParent()
if (e is PsiCodeBlock) {
val parent = e.getParent()
if (parent is PsiMethod || parent is PsiLambdaExpression || parent is PsiClassInitializer ||
// We cannot properly restore context if we started from finally, so let's analyze just finally block
parent instanceof PsiTryStatement && ((PsiTryStatement)parent).getFinallyBlock() == e ||
parent instanceof PsiBlockStatement &&
(parent.getParent() instanceof PsiSwitchLabeledRuleStatement &&
((PsiSwitchLabeledRuleStatement)parent.getParent()).getEnclosingSwitchBlock() instanceof PsiSwitchExpression)) {
return e;
parent is PsiTryStatement && parent.getFinallyBlock() === e || parent is PsiBlockStatement &&
(parent.getParent() is PsiSwitchLabeledRuleStatement &&
(parent.getParent() as PsiSwitchLabeledRuleStatement).getEnclosingSwitchBlock() is PsiSwitchExpression)) {
return e
}
}
}
return null;
return null
}
@Override
public @Nullable Value getJdiValueForDfaVariable(@NotNull StackFrameProxyEx proxy,
@NotNull DfaVariableValue var,
@NotNull PsiElement anchor) throws EvaluateException {
if (var.getQualifier() != null) {
VariableDescriptor descriptor = var.getDescriptor();
if (descriptor instanceof SpecialField) {
@Throws(EvaluateException::class)
override fun getJdiValueForDfaVariable(
proxy: StackFrameProxyEx,
dfaVar: DfaVariableValue,
anchor: PsiElement,
): Value? {
val qualifier = dfaVar.qualifier
if (qualifier != null) {
val descriptor = dfaVar.descriptor
if (descriptor is SpecialField) {
// Special fields facts are applied from qualifiers
return null;
return null
}
Value qualifierValue = getJdiValueForDfaVariable(proxy, var.getQualifier(), anchor);
if (qualifierValue == null) return null;
PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiField psiField && qualifierValue instanceof ObjectReference objectRef) {
ReferenceType type = objectRef.referenceType();
PsiClass psiClass = psiField.getContainingClass();
if (psiClass != null && type.name().equals(JVMNameUtil.getClassVMName(psiClass))) {
Field field = DebuggerUtils.findField(type, psiField.getName());
val qualifierValue = getJdiValueForDfaVariable(proxy, qualifier, anchor)
if (qualifierValue == null) return null
val element = 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())
if (field != null) {
return DfaAssistProvider.wrap(objectRef.getValue(field));
return wrap(qualifierValue.getValue(field))
}
}
}
if (descriptor instanceof ArrayElementDescriptor arrayDesc && qualifierValue instanceof ArrayReference arrayRef) {
int index = arrayDesc.getIndex();
int length = arrayRef.length();
if (index >= 0 && index < length) {
return DfaAssistProvider.wrap(arrayRef.getValue(index));
if (descriptor is ArrayElementDescriptor && qualifierValue is ArrayReference) {
val index = descriptor.index
val length = qualifierValue.length()
if (index in 0..<length) {
return wrap(qualifierValue.getValue(index))
}
}
return null;
return null
}
PsiElement psi = var.getPsiVariable();
if (psi instanceof PsiClass) {
val psi = dfaVar.psiVariable
if (psi is PsiClass) {
// this; probably qualified
PsiClass currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass.class);
return CaptureTraverser.create((PsiClass)psi, currentClass, true).traverse(proxy.thisObject());
val currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass::class.java)
return CaptureTraverser.create(psi, currentClass, true).traverse(proxy.thisObject())
}
if (psi instanceof PsiLocalVariable || psi instanceof PsiParameter) {
String varName = ((PsiVariable)psi).getName();
if (PsiResolveHelper.getInstance(psi.getProject()).resolveReferencedVariable(varName, anchor) != psi) {
if (psi is PsiLocalVariable || psi is PsiParameter) {
val varName: String = psi.getName()!!
if (PsiResolveHelper.getInstance(psi.getProject()).resolveReferencedVariable(varName, anchor) !== 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;
return null
}
LocalVariableProxy variable = proxy.visibleVariableByName(varName);
val variable = proxy.visibleVariableByName(varName)
if (variable != null) {
return DfaAssistProvider.wrap(proxy.getVariableValue(variable));
return wrap(proxy.getVariableValue(variable))
}
PsiClass currentClass = PsiTreeUtil.getParentOfType(anchor, PsiClass.class);
PsiClass varClass = PsiTreeUtil.getParentOfType(psi, PsiClass.class);
ObjectReference thisRef = CaptureTraverser.create(varClass, currentClass, false)
.oneLevelLess().traverse(proxy.thisObject());
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())
if (thisRef != null) {
ReferenceType type = thisRef.referenceType();
if (type instanceof ClassType && type.isPrepared()) {
Field field = DebuggerUtils.findField(type, "val$" + varName);
val type = thisRef.referenceType()
if (type is ClassType && type.isPrepared) {
val field = DebuggerUtils.findField(type, "val$$varName")
if (field != null) {
return DfaAssistProvider.wrap(thisRef.getValue(field));
return wrap(thisRef.getValue(field))
}
}
}
}
if (psi instanceof PsiField psiField && psiField.hasModifierProperty(PsiModifier.STATIC)) {
PsiClass psiClass = psiField.getContainingClass();
if (psi is PsiField && psi.hasModifierProperty(PsiModifier.STATIC)) {
val psiClass = psi.getContainingClass()
if (psiClass != null) {
String name = psiClass.getQualifiedName();
val name = psiClass.getQualifiedName()
if (name != null) {
ReferenceType type = ContainerUtil.getOnlyItem(proxy.getVirtualMachine().classesByName(name));
if (type != null && type.isPrepared()) {
Field field = DebuggerUtils.findField(type, psiField.getName());
if (field != null && field.isStatic()) {
return DfaAssistProvider.wrap(type.getValue(field));
val type = ContainerUtil.getOnlyItem(proxy.getVirtualMachine().classesByName(name))
if (type != null && type.isPrepared) {
val field = DebuggerUtils.findField(type, psi.getName())
if (field != null && field.isStatic) {
return wrap(type.getValue(field))
}
}
}
}
}
return null;
return null
}
@Override
public @NotNull DebuggerDfaListener createListener() {
return new JavaDebuggerDfaListener();
override fun createListener(): DebuggerDfaListener {
return JavaDebuggerDfaListener()
}
@Override
public @NotNull TypeConstraint constraintFromJvmClassName(@NotNull PsiElement anchor, @NotNull String jvmClassName) {
PsiClass aClass = DebuggerUtils.findClass(jvmClassName.replace('/', '.'), anchor.getProject(), anchor.getResolveScope());
return aClass != null ? TypeConstraints.exactClass(aClass) : TypeConstraints.TOP;
override fun constraintFromJvmClassName(anchor: PsiElement, jvmClassName: String): TypeConstraint {
val aClass = DebuggerUtils.findClass(jvmClassName.replace('/', '.'), anchor.getProject(), anchor.getResolveScope())
return if (aClass != null) TypeConstraints.exactClass(aClass) else TypeConstraints.TOP
}
}