IDEA-226191 Use Java dataflow to assist the step-by-step debugging: draft implementation

For now switched off by default (use debugger.show.values.from.dfa)

GitOrigin-RevId: a983ef0b73ddec7e4987a2c162035e74c72a7b73
This commit is contained in:
Tagir Valeev
2019-11-05 16:22:00 +07:00
committed by intellij-monorepo-bot
parent dbb3aea632
commit 1731245617
9 changed files with 593 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ package com.intellij.debugger.engine;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.actions.DebuggerActions;
import com.intellij.debugger.actions.JvmSmartStepIntoActionHandler;
import com.intellij.debugger.engine.dfaassist.DfaAssist;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.impl.*;
@@ -186,6 +187,9 @@ public class JavaDebugProcess extends XDebugProcess {
if (Registry.is("debugger.show.values.between.lines") && session instanceof XDebugSessionImpl) {
((XDebugSessionImpl)session).getSessionData().putUserData(XDebuggerInlayUtil.HELPER_KEY, new JavaDebuggerInlayUtil.Helper());
}
if (Registry.is("debugger.show.values.from.dfa")) {
DfaAssist.installDfaAssist(myJavaSession);
}
mySmartStepIntoActionHandler = new JvmSmartStepIntoActionHandler(javaSession);
}

View File

@@ -0,0 +1,248 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.engine.dfaassist;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.*;
import one.util.streamex.EntryStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType.EQ;
class DebuggerDfaRunner extends DataFlowRunner {
private static final Value NullConst = new Value() {
@Override
public VirtualMachine virtualMachine() { return null; }
@Override
public Type type() { return null; }
@Override
public String toString() { return "null"; }
};
private static final Set<String> COLLECTIONS_WITH_SIZE_FIELD =
ContainerUtil.immutableSet(CommonClassNames.JAVA_UTIL_ARRAY_LIST,
"java.util.LinkedList",
CommonClassNames.JAVA_UTIL_HASH_MAP,
"java.util.TreeMap");
private static final Map<String, Integer> COLLECTIONS_WITH_KNOWN_SIZE =
EntryStream.of("java.util.Collections$EmptyList", 0,
"java.util.Collections$EmptySet", 0,
"java.util.Collections$EmptyMap", 0,
"java.util.Collections$Singleton", 1,
"java.util.Collections$SingletonList", 1,
"java.util.Collections$SingletonMap", 1).toImmutableMap();
private final PsiCodeBlock myBody;
private final PsiStatement myStatement;
private final Project myProject;
private final StackFrame myFrame;
DebuggerDfaRunner(@NotNull PsiCodeBlock body, @NotNull PsiStatement statement, @NotNull StackFrame frame) {
super(false, body);
myBody = body;
myStatement = statement;
myProject = body.getProject();
myFrame = frame;
}
@NotNull
@Override
protected List<DfaInstructionState> createInitialInstructionStates(@NotNull PsiElement psiBlock,
@NotNull Collection<? extends DfaMemoryState> memStates,
@NotNull ControlFlow flow) {
if (psiBlock == myBody) {
int offset = flow.getStartOffset(myStatement).getInstructionOffset();
if (offset >= 0) {
boolean changed = false;
DfaMemoryState state = super.createMemoryState();
PsiElementFactory psiFactory = JavaPsiFacade.getElementFactory(myProject);
DfaValueFactory factory = getFactory();
for (DfaValue dfaValue : factory.getValues().toArray(new DfaValue[0])) {
if (dfaValue instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue)dfaValue;
Value jdiValue = findJdiValue(var);
if (jdiValue != null) {
addToState(psiFactory, factory, state, var, jdiValue);
changed = true;
}
}
}
if (changed) {
return Collections.singletonList(new DfaInstructionState(flow.getInstruction(offset), state));
}
}
}
return Collections.emptyList(); // Cancel analysis: no reason to continue
}
@Nullable
private Value findJdiValue(@NotNull DfaVariableValue var) {
if (var.getQualifier() != null) {
VariableDescriptor descriptor = var.getDescriptor();
if (descriptor instanceof SpecialField) {
// Special fields facts are applied from qualifiers
return null;
}
Value qualifierValue = findJdiValue(var.getQualifier());
if (qualifierValue == null) return null;
PsiModifierListOwner element = descriptor.getPsiElement();
if (element instanceof PsiField && qualifierValue instanceof ObjectReference) {
ReferenceType type = ((ObjectReference)qualifierValue).referenceType();
PsiClass psiClass = ((PsiField)element).getContainingClass();
if (psiClass != null && type.name().equals(psiClass.getQualifiedName())) {
Field field = type.fieldByName(((PsiField)element).getName());
if (field != null) {
return wrap(((ObjectReference)qualifierValue).getValue(field));
}
}
}
if (descriptor instanceof DfaExpressionFactory.ArrayElementDescriptor && qualifierValue instanceof ArrayReference) {
int index = ((DfaExpressionFactory.ArrayElementDescriptor)descriptor).getIndex();
int length = ((ArrayReference)qualifierValue).length();
if (index >= 0 && index < length) {
return wrap(((ArrayReference)qualifierValue).getValue(index));
}
}
return null;
}
PsiModifierListOwner psi = var.getPsiVariable();
if (psi instanceof PsiClass) {
// this
if (PsiTreeUtil.getParentOfType(myBody, PsiClass.class) == psi) {
return myFrame.thisObject();
}
// TODO: support references to outer classes
}
if (psi instanceof PsiLocalVariable || psi instanceof PsiParameter) {
try {
LocalVariable variable = myFrame.visibleVariableByName(((PsiVariable)psi).getName());
if (variable != null) {
return wrap(myFrame.getValue(variable));
}
}
catch (AbsentInformationException ignore) {
}
}
return null;
}
private void addToState(PsiElementFactory psiFactory,
DfaValueFactory factory,
DfaMemoryState state, DfaVariableValue var,
Value jdiValue) {
DfaConstValue val = getConstantValue(psiFactory, factory, jdiValue);
if (val != null) {
state.applyCondition(factory.createCondition(var, EQ, val));
}
if (jdiValue instanceof ObjectReference) {
ObjectReference ref = (ObjectReference)jdiValue;
ReferenceType type = ref.referenceType();
PsiType psiType = getPsiReferenceType(psiFactory, type);
if (psiType == null) return;
TypeConstraint exactType = TypeConstraint.exact(factory.createDfaType(psiType));
String name = type.name();
state.applyFact(var, DfaFactType.NULLABILITY, DfaNullability.NOT_NULL);
state.applyFact(var, DfaFactType.TYPE_CONSTRAINT, exactType);
if (jdiValue instanceof ArrayReference) {
DfaValue dfaLength = SpecialField.ARRAY_LENGTH.createValue(factory, var);
int jdiLength = ((ArrayReference)jdiValue).length();
state.applyCondition(factory.createCondition(dfaLength, EQ, factory.getInt(jdiLength)));
}
else if (TypeConversionUtil.isPrimitiveWrapper(name)) {
setSpecialField(psiFactory, factory, state, var, ref, type, "value", SpecialField.UNBOX);
}
else if (COLLECTIONS_WITH_SIZE_FIELD.contains(name)) {
setSpecialField(psiFactory, factory, state, var, ref, type, "size", SpecialField.COLLECTION_SIZE);
}
else if (name.startsWith("java.util.Collections$Empty")) {
state.applyCondition(factory.createCondition(
SpecialField.COLLECTION_SIZE.createValue(factory, var), EQ, factory.getInt(0)));
}
else if (name.startsWith("java.util.Collections$Singleton")) {
state.applyCondition(factory.createCondition(
SpecialField.COLLECTION_SIZE.createValue(factory, var), EQ, factory.getInt(1)));
}
else if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(name) && !(var.getDescriptor() instanceof SpecialField)) {
setSpecialField(psiFactory, factory, state, var, ref, type, "value", SpecialField.OPTIONAL_VALUE);
}
}
}
@Nullable
private PsiType getPsiReferenceType(PsiElementFactory psiFactory, ReferenceType jdiType) {
Type componentType = jdiType;
int depth = 0;
while (componentType instanceof ArrayType) {
try {
componentType = ((ArrayType)componentType).componentType();
depth++;
}
catch (ClassNotLoadedException e) {
return null;
}
}
PsiType psiType = psiFactory.createTypeByFQClassName(componentType.name(), myBody.getResolveScope());
while (depth > 0) {
psiType = psiType.createArrayType();
depth--;
}
return psiType;
}
private void setSpecialField(PsiElementFactory psiFactory,
DfaValueFactory factory,
DfaMemoryState state,
DfaVariableValue dfaQualifier,
ObjectReference jdiQualifier,
ReferenceType type,
String fieldName,
SpecialField specialField) {
Field value = type.fieldByName(fieldName);
if (value != null) {
DfaVariableValue dfaUnboxed = ObjectUtils.tryCast(specialField.createValue(factory, dfaQualifier), DfaVariableValue.class);
Value jdiUnboxed = jdiQualifier.getValue(value);
if (jdiUnboxed != null && dfaUnboxed != null) {
addToState(psiFactory, factory, state, dfaUnboxed, jdiUnboxed);
}
}
}
@Nullable
private DfaConstValue getConstantValue(PsiElementFactory psiFactory, DfaValueFactory factory, Value jdiValue) {
if (jdiValue == NullConst) {
return factory.getConstFactory().getNull();
}
if (jdiValue instanceof BooleanValue) {
return factory.getBoolean(((BooleanValue)jdiValue).value());
}
if (jdiValue instanceof ShortValue || jdiValue instanceof CharValue ||
jdiValue instanceof ByteValue || jdiValue instanceof IntegerValue) {
return factory.getConstFactory().createFromValue(((PrimitiveValue)jdiValue).intValue(), PsiType.LONG);
}
if (jdiValue instanceof FloatValue) {
return factory.getConstFactory().createFromValue(((FloatValue)jdiValue).floatValue(), PsiType.FLOAT);
}
if (jdiValue instanceof DoubleValue) {
return factory.getConstFactory().createFromValue(((DoubleValue)jdiValue).doubleValue(), PsiType.DOUBLE);
}
if (jdiValue instanceof StringReference) {
return factory.getConstFactory().createFromValue(
((StringReference)jdiValue).value(), psiFactory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING,
myBody.getResolveScope()));
}
return null;
}
private static Value wrap(Value value) {
return value == null ? NullConst : value;
}
}

View File

@@ -0,0 +1,105 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.engine.dfaassist;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind;
import com.intellij.codeInspection.dataFlow.StandardInstructionVisitor;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
class DebuggerInstructionVisitor extends StandardInstructionVisitor {
private final Map<PsiExpression, DfaHint> myHints = new HashMap<>();
DebuggerInstructionVisitor() {
super(true);
}
private void addHint(@NotNull PsiExpression expression, @Nullable DfaHint hint) {
if (hint != null) {
myHints.merge(expression, hint, DfaHint::merge);
}
}
@Override
protected void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expression,
@Nullable TextRange range,
@NotNull DfaMemoryState state) {
if (range != null) return;
DfaHint hint = DfaHint.ANY_VALUE;
if (value instanceof DfaConstValue) {
Object constVal = ((DfaConstValue)value).getValue();
if (Boolean.TRUE.equals(constVal)) {
hint = DfaHint.TRUE;
}
else if (Boolean.FALSE.equals(constVal)) {
hint = DfaHint.FALSE;
}
else if (DfaConstValue.isContractFail(value)) {
hint = DfaHint.FAIL;
}
}
addHint(expression, hint);
}
@Override
protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) {
if (!castPossible) {
addHint(castExpression, DfaHint.CCE);
}
super.onTypeCast(castExpression, state, castPossible);
}
@Override
protected void processArrayAccess(PsiArrayAccessExpression expression, boolean alwaysOutOfBounds) {
if (alwaysOutOfBounds) {
addHint(expression, DfaHint.AIOOBE);
}
super.processArrayAccess(expression, alwaysOutOfBounds);
}
@Override
protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) {
addHint(assignmentExpression.getLExpression(), DfaHint.ASE);
super.processArrayStoreTypeMismatch(assignmentExpression, fromType, toType);
}
@Override
protected boolean checkNotNullable(DfaMemoryState state,
DfaValue value,
@Nullable NullabilityProblemKind.NullabilityProblem<?> problem) {
if (problem != null) {
PsiExpression expression = problem.getDereferencedExpression();
if (expression != null && problem.thrownException() != null) {
if (state.isNull(value)) {
DfaHint hint = problem.thrownException().equals(CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION)
? DfaHint.NPE
: DfaHint.NULL_AS_NOT_NULL;
addHint(expression, hint);
}
}
}
return super.checkNotNullable(state, value, problem);
}
void cleanup() {
myHints.values().removeIf(h -> h.getTitle() == null);
myHints.keySet().removeIf(expr -> {
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expr);
return result != null && result.getExpressionValues(expr).size() == 1;
});
}
@NotNull
Map<PsiExpression, DfaHint> getHints() {
return myHints;
}
}

View File

@@ -0,0 +1,190 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.engine.dfaassist;
import com.intellij.codeInsight.hints.presentation.MenuOnClickPresentation;
import com.intellij.codeInsight.hints.presentation.PresentationFactory;
import com.intellij.codeInsight.hints.presentation.PresentationRenderer;
import com.intellij.codeInspection.dataFlow.RunnerResult;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.JavaDebugProcess;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.debugger.impl.DebuggerContextListener;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.DebuggerStateManager;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.InlayModel;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Method;
import com.sun.jdi.StackFrame;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
public class DfaAssist implements DebuggerContextListener {
private final Project myProject;
private final Queue<Inlay<?>> myInlays = new ConcurrentLinkedQueue<>();
private DfaAssist(Project project) {
myProject = project;
}
@Override
public void changeEvent(@NotNull DebuggerContextImpl newContext, DebuggerSession.Event event) {
if (event == DebuggerSession.Event.DETACHED || event == DebuggerSession.Event.DISPOSE) {
disposeInlays();
}
if (event != DebuggerSession.Event.PAUSE) return;
SourcePosition sourcePosition = newContext.getSourcePosition();
if (sourcePosition == null) {
disposeInlays();
return;
}
PsiJavaFile file = ObjectUtils.tryCast(sourcePosition.getFile(), PsiJavaFile.class);
DebugProcessImpl debugProcess = JavaDebugProcess.getCurrentDebugProcess(myProject);
if (debugProcess == null || file == null) {
disposeInlays();
return;
}
PsiElement element = sourcePosition.getElementAt();
debugProcess.getManagerThread().schedule(new SuspendContextCommandImpl(newContext.getSuspendContext()) {
@Override
public void contextAction(@NotNull SuspendContextImpl suspendContext) throws Exception {
StackFrameProxyImpl proxy = suspendContext.getFrameProxy();
if (proxy == null) {
disposeInlays();
return;
}
StackFrame frame = proxy.getStackFrame();
Map<PsiExpression, DfaHint> hints = ReadAction.compute(() -> computeHints(frame, element));
displayInlays(hints, sourcePosition);
}
});
}
private void disposeInlays() {
ApplicationManager.getApplication().invokeLater(this::doDisposeInlays);
}
private void doDisposeInlays() {
while (true) {
Inlay<?> inlay = myInlays.poll();
if (inlay == null) break;
Disposer.dispose(inlay);
}
}
private void displayInlays(Map<PsiExpression, DfaHint> hints, SourcePosition sourcePosition) {
if (hints.isEmpty()) {
disposeInlays();
return;
}
ApplicationManager.getApplication().invokeLater(
() -> {
doDisposeInlays();
EditorImpl editor = ObjectUtils.tryCast(sourcePosition.openEditor(true), EditorImpl.class);
if (editor == null) return;
InlayModel model = editor.getInlayModel();
List<Inlay<?>> newInlays = new ArrayList<>();
AnAction turnOffDfaProcessor = new TurnOffDfaProcessorAction();
hints.forEach((expression, hint) -> {
TextRange range = expression.getTextRange();
PresentationFactory factory = new PresentationFactory(editor);
MenuOnClickPresentation presentation = new MenuOnClickPresentation(
factory.roundWithBackground(factory.smallText(hint.getTitle())), myProject,
() -> Collections.singletonList(turnOffDfaProcessor));
newInlays.add(model.addInlineElement(range.getEndOffset(), new PresentationRenderer(presentation)));
});
myInlays.addAll(newInlays);
}
);
}
@NotNull
static Map<PsiExpression, DfaHint> computeHints(StackFrame frame, PsiElement element) throws AbsentInformationException {
Method method = frame.location().method();
if (!element.isValid()) return Collections.emptyMap();
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
if (psiMethod == null || !psiMethod.getName().equals(method.name()) ||
psiMethod.getParameterList().getParametersCount() != method.arguments().size()) {
return Collections.emptyMap();
}
PsiStatement statement = getAnchorStatement(element);
if (statement == null) return Collections.emptyMap();
// TODO: support class initializers
// TODO: check/improve lambdas support
PsiCodeBlock body = getCodeBlock(statement);
if (body == null) return Collections.emptyMap();
DebuggerInstructionVisitor visitor = new DebuggerInstructionVisitor();
// TODO: read assertion status
RunnerResult result = new DebuggerDfaRunner(body, statement, frame).analyzeMethod(body, visitor);
if (result != RunnerResult.OK) return Collections.emptyMap();
visitor.cleanup();
return visitor.getHints();
}
@Nullable
private static PsiStatement getAnchorStatement(@NotNull PsiElement element) {
PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class, false, PsiMethod.class);
if (statement instanceof PsiBlockStatement && ((PsiBlockStatement)statement).getCodeBlock().getRBrace() == element) {
statement = PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class);
}
return statement;
}
@Nullable
private static PsiCodeBlock getCodeBlock(@NotNull PsiStatement statement) {
PsiElement e = statement;
while (e != null && !(e instanceof PsiClass) && !(e instanceof PsiFileSystemItem)) {
e = e.getParent();
if (e instanceof PsiCodeBlock) {
if (e.getParent() instanceof PsiMethod ||
e.getParent() instanceof PsiBlockStatement && e.getParent().getParent() instanceof PsiLoopStatement) {
return (PsiCodeBlock)e;
}
}
}
return null;
}
private class TurnOffDfaProcessorAction extends AnAction {
private TurnOffDfaProcessorAction() {
super("Turn Off Dataflow Assist", "Switch off dataflow aided debugging for this session", AllIcons.Actions.Cancel);
}
@Override
public void actionPerformed(@NotNull AnActionEvent evt) {
DebugProcessImpl process = JavaDebugProcess.getCurrentDebugProcess(myProject);
if (process != null) {
process.getSession().getContextManager().removeListener(DfaAssist.this);
disposeInlays();
}
}
}
/**
* Install dataflow assistant to the specified debugging session
* @param javaSession JVM debugger session to install an assistant to
*/
public static void installDfaAssist(@NotNull DebuggerSession javaSession) {
DebuggerStateManager manager = javaSession.getContextManager();
manager.addListener(new DfaAssist(manager.getContext().getProject()));
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.engine.dfaassist;
import org.jetbrains.annotations.NotNull;
enum DfaHint {
NONE(null), ANY_VALUE(null, true), TRUE("= true", true), FALSE("= false", true),
NPE("[NullPointerException]"), NULL_AS_NOT_NULL("[Null passed where not-null expected]"), CCE("[ClassCastException]"),
ASE("[ArrayStoreException]"), AIOOBE("[ArrayIndexOutOfBoundsException]"), FAIL("[Method will fail]", true);
private final String myTitle;
private final boolean myValue;
DfaHint(String title) {
this(title, false);
}
DfaHint(String title, boolean value) {
myTitle = title;
myValue = value;
}
String getTitle() {
return myTitle;
}
@NotNull
DfaHint merge(@NotNull DfaHint other) {
if (other == this) return this;
if (this.myValue && other.myValue) return ANY_VALUE;
if (this.myValue) return other;
if (other.myValue) return this;
return NONE;
}
}

View File

@@ -305,9 +305,9 @@ public class DataFlowRunner {
}
@NotNull
private List<DfaInstructionState> createInitialInstructionStates(@NotNull PsiElement psiBlock,
@NotNull Collection<? extends DfaMemoryState> memStates,
@NotNull ControlFlow flow) {
protected List<DfaInstructionState> createInitialInstructionStates(@NotNull PsiElement psiBlock,
@NotNull Collection<? extends DfaMemoryState> memStates,
@NotNull ControlFlow flow) {
initializeVariables(psiBlock, memStates, flow);
return ContainerUtil.map(memStates, s -> new DfaInstructionState(flow.getInstruction(0), s));
}

View File

@@ -39,7 +39,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
myStopAnalysisOnNpe = false;
}
StandardInstructionVisitor(boolean stopAnalysisOnNpe) {
protected StandardInstructionVisitor(boolean stopAnalysisOnNpe) {
myStopAnalysisOnNpe = stopAnalysisOnNpe;
}

View File

@@ -425,13 +425,17 @@ public class DfaExpressionFactory {
}
}
private static final class ArrayElementDescriptor implements VariableDescriptor {
public static final class ArrayElementDescriptor implements VariableDescriptor {
private final int myIndex;
ArrayElementDescriptor(int index) {
myIndex = index;
}
public int getIndex() {
return myIndex;
}
@Nullable
@Override
public PsiType getType(@Nullable DfaVariableValue qualifier) {

View File

@@ -1482,6 +1482,8 @@ debugger.show.values.inplace=false
debugger.show.values.inplace.description=Show primitive values near variables inside current execution line
debugger.show.values.between.lines=false
debugger.show.values.between.lines.description=Show variable values below their definition place
debugger.show.values.from.dfa=false
debugger.show.values.from.dfa.description=Enrich debug session with dataflow output (Java only)
ide.projectView.ProjectViewPaneTreeStructure.BuildChildrenInBackground=false
ide.projectView.ProjectViewPaneTreeStructure.BuildChildrenInBackground.description=Temporary ability to control a tree building for the Project View pane