From 173124561755986c8c852bfcc6946e490ed8803f Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 5 Nov 2019 16:22:00 +0700 Subject: [PATCH] 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 --- .../debugger/engine/JavaDebugProcess.java | 4 + .../engine/dfaassist/DebuggerDfaRunner.java | 248 ++++++++++++++++++ .../dfaassist/DebuggerInstructionVisitor.java | 105 ++++++++ .../debugger/engine/dfaassist/DfaAssist.java | 190 ++++++++++++++ .../debugger/engine/dfaassist/DfaHint.java | 35 +++ .../dataFlow/DataFlowRunner.java | 6 +- .../dataFlow/StandardInstructionVisitor.java | 2 +- .../dataFlow/value/DfaExpressionFactory.java | 6 +- .../util/resources/misc/registry.properties | 2 + 9 files changed, 593 insertions(+), 5 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssist.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaHint.java diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java index 5aba0d920b5b..1062ad1db63c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java @@ -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); } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java new file mode 100644 index 000000000000..c3d49b4567db --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java @@ -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 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 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 createInitialInstructionStates(@NotNull PsiElement psiBlock, + @NotNull Collection 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; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java new file mode 100644 index 000000000000..171323b4f8ed --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerInstructionVisitor.java @@ -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 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 getHints() { + return myHints; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssist.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssist.java new file mode 100644 index 000000000000..098ca4516e2c --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaAssist.java @@ -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> 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 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 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> 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 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())); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaHint.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaHint.java new file mode 100644 index 000000000000..272a78a8c317 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DfaHint.java @@ -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; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java index c19b4ed931fa..ec7e828bdb02 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java @@ -305,9 +305,9 @@ public class DataFlowRunner { } @NotNull - private List createInitialInstructionStates(@NotNull PsiElement psiBlock, - @NotNull Collection memStates, - @NotNull ControlFlow flow) { + protected List createInitialInstructionStates(@NotNull PsiElement psiBlock, + @NotNull Collection memStates, + @NotNull ControlFlow flow) { initializeVariables(psiBlock, memStates, flow); return ContainerUtil.map(memStates, s -> new DfaInstructionState(flow.getInstruction(0), s)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 58b9e52a74a7..8791c9953939 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -39,7 +39,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { myStopAnalysisOnNpe = false; } - StandardInstructionVisitor(boolean stopAnalysisOnNpe) { + protected StandardInstructionVisitor(boolean stopAnalysisOnNpe) { myStopAnalysisOnNpe = stopAnalysisOnNpe; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java index d15a6a3ad261..ca472d80d3d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -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) { diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 2f6a2ddb47ff..899da8f296a4 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -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