From 7634a84ff46383df1f3203d04fd464914394c0a5 Mon Sep 17 00:00:00 2001 From: "Egor.Ushakov" Date: Thu, 19 Jan 2017 16:29:17 +0300 Subject: [PATCH] async debugger: capture locals values, show captured stack inside the frames view --- .../debugger/engine/JavaExecutionStack.java | 35 +++- .../debugger/engine/PositionManagerImpl.java | 6 +- .../tracking/ConstructorInstancesTracker.java | 5 +- .../debugger/memory/utils/StackFrameItem.java | 189 +++++++++++++++++- .../StackCapturingLineBreakpoint.java | 2 +- .../ui/tree/render/ClassRenderer.java | 7 +- 6 files changed, 224 insertions(+), 20 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java index 68672eb0c0d8..8af7bc9528af 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,18 @@ import com.intellij.debugger.impl.DebuggerUtilsEx; import com.intellij.debugger.jdi.StackFrameProxyImpl; import com.intellij.debugger.jdi.ThreadGroupReferenceProxyImpl; import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; +import com.intellij.debugger.memory.utils.StackFrameItem; +import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint; import com.intellij.debugger.ui.impl.watch.MethodsTracker; import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl; import com.intellij.icons.AllIcons; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.frame.XExecutionStack; import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.settings.XDebuggerSettingsManager; import com.sun.jdi.Location; +import com.sun.jdi.ObjectReference; import com.sun.jdi.ThreadReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,6 +41,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collections; import java.util.Iterator; +import java.util.List; +import java.util.Map; /** * @author egor @@ -184,7 +190,16 @@ public class JavaExecutionStack extends XExecutionStack { myStackFramesIterator.next(); } else { - frame = createStackFrame(myStackFramesIterator.next(), myTracker); + StackFrameProxyImpl frameProxy = myStackFramesIterator.next(); + + // replace the rest with the related stack + List relatedStack = getRelatedStack(frameProxy, myDebugProcess); + if (!ContainerUtil.isEmpty(relatedStack)) { + myContainer.addStackFrames(relatedStack, true); + return; + } + + frame = createStackFrame(frameProxy, myTracker); if (first && !myTopFrameReady) { myTopFrame = frame; myTopFrameReady = true; @@ -204,6 +219,22 @@ public class JavaExecutionStack extends XExecutionStack { } } + @Nullable + static List getRelatedStack(StackFrameProxyImpl frame, DebugProcessImpl process) { + Map> data = process.getUserData(StackCapturingLineBreakpoint.CAPTURED_STACKS); + if (data != null) { + try { + ObjectReference thisObject = frame.thisObject(); + if (thisObject != null) { + return data.get(thisObject); + } + } + catch (EvaluateException ignore) { + } + } + return null; + } + private static boolean showFrame(@NotNull XStackFrame frame) { if (XDebuggerSettingsManager.getInstance().getDataViewSettings().isShowLibraryStackFrames()) return true; if (frame instanceof JavaStackFrame) { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerImpl.java index 83871413ea84..3cd04c53f2f1 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -411,7 +411,7 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio } @Nullable - private static PsiClass findClass(Project project, String originalQName, GlobalSearchScope searchScope) { + public static PsiClass findClass(Project project, String originalQName, GlobalSearchScope searchScope) { PsiClass psiClass = DebuggerUtils.findClass(originalQName, project, searchScope); // try to lookup original name first if (psiClass == null) { int dollar = originalQName.indexOf('$'); @@ -684,7 +684,7 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio } } - private static class ClsSourcePosition extends RemappedSourcePosition { + public static class ClsSourcePosition extends RemappedSourcePosition { private final int myOriginalLine; public ClsSourcePosition(SourcePosition delegate, int originalLine) { diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/tracking/ConstructorInstancesTracker.java b/java/debugger/impl/src/com/intellij/debugger/memory/tracking/ConstructorInstancesTracker.java index 549da0518f93..239d5699f476 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/tracking/ConstructorInstancesTracker.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/tracking/ConstructorInstancesTracker.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -243,7 +243,8 @@ public class ConstructorInstancesTracker implements TrackerForNewInstances, Disp if (myReference.equals(thisRef.referenceType()) && data != null) { thisRef.disableCollection(); myTrackedObjects.add(thisRef); - final List frame = StackFrameItem.createFrames(suspendContext.getThread()); + final List frame = + StackFrameItem.createFrames(suspendContext.getThread(), suspendContext.getDebugProcess(), false); data.getTrackedStacks().addStack(thisRef, frame); } } diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java index 43093813158c..86966dd8e8c2 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,79 @@ */ package com.intellij.debugger.memory.utils; +import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.PositionManagerImpl; import com.intellij.debugger.engine.evaluation.EvaluateException; -import com.intellij.debugger.jdi.StackFrameProxyImpl; +import com.intellij.debugger.impl.DebuggerUtilsEx; import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; +import com.intellij.debugger.settings.NodeRendererSettings; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.NullableLazyValue; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiCompiledFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.ui.ColoredTextContainer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.PlatformIcons; +import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.JBUI; +import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.frame.*; import com.sun.jdi.Location; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.PrimitiveValue; +import com.sun.jdi.Value; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import javax.swing.*; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class StackFrameItem { +public class StackFrameItem extends XStackFrame { + private static final Logger LOG = Logger.getInstance(StackFrameItem.class); + + private final Project myProject; + private final GlobalSearchScope mySearchScope; + private final String myFilePath; private final String myMethodName; private final int myLineNumber; + private List myVariables = null; - public StackFrameItem(@NotNull String path, @NotNull String methodName, int line) { + private final NullableLazyValue mySourcePosition; + + public StackFrameItem(Project project, + GlobalSearchScope scope, + @NotNull String path, + @NotNull String methodName, + int line) { + myProject = project; + mySearchScope = scope; myFilePath = path.replace('\\', '.'); myMethodName = methodName; myLineNumber = line; + + //TODO: need to reuse PositionManager somehow + mySourcePosition = NullableLazyValue.createValue(() -> { + PsiClass psiClass = PositionManagerImpl.findClass(myProject, myFilePath, mySearchScope); + if (psiClass == null) { + return null; + } + SourcePosition position = SourcePosition.createFromLine(psiClass.getContainingFile(), myLineNumber - 1); + PsiFile psiFile = psiClass.getContainingFile().getOriginalFile(); + if (psiFile instanceof PsiCompiledFile) { + position = new PositionManagerImpl.ClsSourcePosition(position, myLineNumber - 1); + } + return DebuggerUtilsEx.toXSourcePosition(position); + }); } @NotNull @@ -61,20 +114,138 @@ public class StackFrameItem { return myLineNumber; } - public static List createFrames(ThreadReferenceProxyImpl threadReferenceProxy) throws EvaluateException { - List stack = threadReferenceProxy == null ? null : threadReferenceProxy.frames(); + @Override + public void customizePresentation(@NotNull ColoredTextContainer component) { + component.setIcon(JBUI.scale(EmptyIcon.create(6))); + component.append(String.format("%s:%d, %s", myMethodName, myLineNumber, myFilePath), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } - if (stack != null) { - return StreamEx.of(stack).map(frame -> { + @Nullable + @Override + public XSourcePosition getSourcePosition() { + return mySourcePosition.getValue(); + } + + void addVariable(VariableItem var) { + if (myVariables == null) { + myVariables = new ArrayList<>(); + } + myVariables.add(var); + } + + public static List createFrames(@Nullable ThreadReferenceProxyImpl threadReferenceProxy, + DebugProcessImpl process, + boolean withVars) + throws EvaluateException { + if (threadReferenceProxy != null) { + return StreamEx.of(threadReferenceProxy.frames()).map(frame -> { try { Location loc = frame.location(); - return new StackFrameItem(loc.declaringType().name(), loc.method().name(), loc.lineNumber()); + StackFrameItem frameItem = new StackFrameItem(process.getProject(), + process.getSearchScope(), + loc.declaringType().name(), + loc.method().name(), + loc.lineNumber()); + if (withVars) { + try { + ObjectReference thisObject = frame.thisObject(); + if (thisObject != null) { + frameItem.addVariable(createVariable(thisObject, "this", VariableItem.VarType.OBJECT)); + } + } + catch (EvaluateException e) { + LOG.error(e); + } + + try { + frame.visibleVariables().forEach(v -> { + try { + Value value = frame.getValue(v); + VariableItem.VarType varType = VariableItem.VarType.OBJECT; + if (v.getVariable().isArgument()) { + varType = VariableItem.VarType.PARAM; + } + else if (value instanceof PrimitiveValue) { + varType = VariableItem.VarType.PRIMITIVE; + } + frameItem.addVariable(createVariable(value, v.name(), varType)); + } + catch (EvaluateException e) { + LOG.error(e); + } + }); + } + catch (EvaluateException ignore) { + } + } + return frameItem; } catch (EvaluateException e) { + LOG.error(e); return null; } }).nonNull().toList(); } return Collections.emptyList(); } + + private static VariableItem createVariable(Value value, String name, VariableItem.VarType varType) { + String type = null; + String valueText = "null"; + if (value instanceof ObjectReference) { + valueText = ""; + type = value.type().name() + "@" + ((ObjectReference)value).uniqueID(); + } + else if (value != null) { + valueText = value.toString(); + type = value.type().name(); + } + return new VariableItem(name, type, valueText, varType); + } + + @Override + public void computeChildren(@NotNull XCompositeNode node) { + if (myVariables != null) { + XValueChildrenList children = new XValueChildrenList(); + myVariables.forEach(v -> children.add(v.myName, new XValue() { + @Override + public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) { + String type = NodeRendererSettings.getInstance().getClassRenderer().renderTypeName(v.myType); + Icon icon; + switch (v.myVarType) { + case PARAM: + icon = PlatformIcons.PARAMETER_ICON; + break; + case PRIMITIVE: + icon = AllIcons.Debugger.Db_primitive; + type = null; + break; + default: + icon = AllIcons.Debugger.Value; + } + node.setPresentation(icon, type, v.myValue, false); + } + })); + node.addChildren(children, true); + } + else { + node.addChildren(XValueChildrenList.EMPTY, true); + } + } + + private static class VariableItem { + enum VarType {PARAM, PRIMITIVE, OBJECT} + + private final String myName; + private final String myType; + private final String myValue; + private final VarType myVarType; + + public VariableItem(String name, String type, String value, VarType varType) { + myName = name; + myType = type; + myValue = value; + myVarType = varType; + } + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java index 49ad2cc50c1b..046e88f82d22 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StackCapturingLineBreakpoint.java @@ -97,7 +97,7 @@ public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint { } Value key = ContainerUtil.getOrElse(frameProxy.getArgumentValues(), myParamNo, null); if (key instanceof ObjectReference) { - stacks.put((ObjectReference)key, StackFrameItem.createFrames(suspendContext.getThread())); + stacks.put((ObjectReference)key, StackFrameItem.createFrames(suspendContext.getThread(), process, true)); } } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java index 3650a0f1f91f..41a80705a065 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/render/ClassRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,8 +76,9 @@ public class ClassRenderer extends NodeRendererImpl{ myProperties.setEnabled(true); } - public final String renderTypeName(final String typeName) { - if (SHOW_FQ_TYPE_NAMES) { + @Nullable + public final String renderTypeName(@Nullable final String typeName) { + if (SHOW_FQ_TYPE_NAMES || typeName == null) { return typeName; } String baseLambdaClassName = DebuggerUtilsEx.getLambdaBaseClassName(typeName);